Skip to content

Commit

Permalink
feat(watch): support passing number to deep option to control the w…
Browse files Browse the repository at this point in the history
…atch depth (#9572)
  • Loading branch information
Alfred-Skyblue authored Aug 2, 2024
1 parent 321d807 commit 22f7d96
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 15 deletions.
141 changes: 141 additions & 0 deletions packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,147 @@ describe('api: watch', () => {
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", () => {
const num = ref(0)
let unwatch: () => void
Expand Down
27 changes: 12 additions & 15 deletions packages/runtime-core/src/apiWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export interface WatchOptionsBase extends DebuggerOptions {

export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate
deep?: boolean
deep?: boolean | number
once?: boolean
}

Expand Down Expand Up @@ -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 (immediate !== undefined) {
warn(
Expand Down Expand Up @@ -228,11 +220,15 @@ function doWatch(
}

const instance = currentInstance
const reactiveGetter = (source: object) =>
deep === true
? source // traverse will happen in wrapped getter below
: // for deep: false, only traverse root-level properties
traverse(source, deep === false ? 1 : undefined)
const reactiveGetter = (source: object) => {
// traverse will happen in wrapped getter below
if (deep) return source
// for `deep: false | 0` or shallow reactive, only traverse root-level properties
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 forceTrigger = false
Expand Down Expand Up @@ -300,7 +296,8 @@ function doWatch(

if (cb && deep) {
const baseGetter = getter
getter = () => traverse(baseGetter())
const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
}

let cleanup: (() => void) | undefined
Expand Down

0 comments on commit 22f7d96

Please sign in to comment.