feat(watch): support passing number to `deep` option to control the watch depth (#9572)

This commit is contained in:
远方os 2024-08-02 11:38:07 +08:00 committed by GitHub
parent 321d80758c
commit 22f7d96757
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 153 additions and 15 deletions

View File

@ -1532,6 +1532,147 @@ describe('api: watch', () => {
expect(spy2).toHaveBeenCalledTimes(1) expect(spy2).toHaveBeenCalledTimes(1)
}) })
it('watching reactive depth', async () => {
const state = reactive({
a: {
b: {
c: {
d: {
e: 1,
},
},
},
},
})
const cb = vi.fn()
watch(state, cb, { deep: 2 })
state.a.b = { c: { d: { e: 2 } } }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
state.a.b.c = { d: { e: 3 } }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
state.a.b = { c: { d: { e: 4 } } }
await nextTick()
expect(cb).toHaveBeenCalledTimes(2)
})
it('watching ref depth', async () => {
const state = ref({
a: {
b: 2,
},
})
const cb = vi.fn()
watch(state, cb, { deep: 1 })
state.value.a.b = 3
await nextTick()
expect(cb).toHaveBeenCalledTimes(0)
state.value.a = { b: 3 }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
})
it('watching array depth', async () => {
const arr = ref([
{
a: {
b: 2,
},
},
{
a: {
b: 3,
},
},
])
const cb = vi.fn()
watch(arr, cb, { deep: 2 })
arr.value[0].a.b = 3
await nextTick()
expect(cb).toHaveBeenCalledTimes(0)
arr.value[0].a = { b: 3 }
await nextTick()
expect(cb).toHaveBeenCalledTimes(1)
arr.value[1].a = { b: 4 }
await nextTick()
expect(cb).toHaveBeenCalledTimes(2)
arr.value.push({ a: { b: 5 } })
await nextTick()
expect(cb).toHaveBeenCalledTimes(3)
arr.value.pop()
await nextTick()
expect(cb).toHaveBeenCalledTimes(4)
})
it('shallowReactive', async () => {
const state = shallowReactive({
msg: ref('hello'),
foo: {
a: ref(1),
b: 2,
},
bar: 'bar',
})
const spy = vi.fn()
watch(state, spy)
state.msg.value = 'hi'
await nextTick()
expect(spy).not.toHaveBeenCalled()
state.bar = 'bar2'
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)
state.foo.a.value++
state.foo.b++
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)
state.bar = 'bar3'
await nextTick()
expect(spy).toHaveBeenCalledTimes(2)
})
it('watching reactive with deep: false', async () => {
const state = reactive({
foo: {
a: 2,
},
bar: 'bar',
})
const spy = vi.fn()
watch(state, spy, { deep: false })
state.foo.a++
await nextTick()
expect(spy).toHaveBeenCalledTimes(0)
state.bar = 'bar2'
await nextTick()
expect(spy).toHaveBeenCalledTimes(1)
})
test("effect should be removed from scope's effects after it is stopped", () => { test("effect should be removed from scope's effects after it is stopped", () => {
const num = ref(0) const num = ref(0)
let unwatch: () => void let unwatch: () => void

View File

@ -73,7 +73,7 @@ export interface WatchOptionsBase extends DebuggerOptions {
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase { export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate immediate?: Immediate
deep?: boolean deep?: boolean | number
once?: boolean once?: boolean
} }
@ -189,14 +189,6 @@ function doWatch(
} }
} }
// TODO remove in 3.5
if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
warn(
`watch() "deep" option with number value will be used as watch depth in future versions. ` +
`Please use a boolean instead to avoid potential breakage.`,
)
}
if (__DEV__ && !cb) { if (__DEV__ && !cb) {
if (immediate !== undefined) { if (immediate !== undefined) {
warn( warn(
@ -228,11 +220,15 @@ function doWatch(
} }
const instance = currentInstance const instance = currentInstance
const reactiveGetter = (source: object) => const reactiveGetter = (source: object) => {
deep === true // traverse will happen in wrapped getter below
? source // traverse will happen in wrapped getter below if (deep) return source
: // for deep: false, only traverse root-level properties // for `deep: false | 0` or shallow reactive, only traverse root-level properties
traverse(source, deep === false ? 1 : undefined) if (isShallow(source) || deep === false || deep === 0)
return traverse(source, 1)
// for `deep: undefined` on a reactive object, deeply traverse all properties
return traverse(source)
}
let getter: () => any let getter: () => any
let forceTrigger = false let forceTrigger = false
@ -300,7 +296,8 @@ function doWatch(
if (cb && deep) { if (cb && deep) {
const baseGetter = getter const baseGetter = getter
getter = () => traverse(baseGetter()) const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
} }
let cleanup: (() => void) | undefined let cleanup: (() => void) | undefined