]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(watch): support passing number to `deep` option to control the watch depth ...
author远方os <yangpanteng@gmail.com>
Fri, 2 Aug 2024 03:38:07 +0000 (11:38 +0800)
committerGitHub <noreply@github.com>
Fri, 2 Aug 2024 03:38:07 +0000 (11:38 +0800)
packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/apiWatch.ts

index f370a98b7827d8007016a2f2b1d466ac61eb4567..5bf156fd77b6024db93f8102349578d320244130 100644 (file)
@@ -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
index dddc64e2aa73147d92e694b447b8a6128142cd3a..fc2c7e4497de92c4204c361e8786610d31a9f0e5 100644 (file)
@@ -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
 }
 
@@ -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(
@@ -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
@@ -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