]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(watch): use maximum depth for duplicates (#13434)
authorskirtle <65301168+skirtles-code@users.noreply.github.com>
Tue, 2 Sep 2025 09:29:08 +0000 (10:29 +0100)
committerGitHub <noreply@github.com>
Tue, 2 Sep 2025 09:29:08 +0000 (17:29 +0800)
packages/reactivity/src/watch.ts
packages/runtime-core/__tests__/apiWatch.spec.ts

index 648e6481b18faaabd27d600f0f64b20bef0fb4e8..6bc009985e0628a4bd16bc60fe328e08eb3c1d3d 100644 (file)
@@ -331,17 +331,17 @@ export function watch(
 export function traverse(
   value: unknown,
   depth: number = Infinity,
-  seen?: Set<unknown>,
+  seen?: Map<unknown, number>,
 ): unknown {
   if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
     return value
   }
 
-  seen = seen || new Set()
-  if (seen.has(value)) {
+  seen = seen || new Map()
+  if ((seen.get(value) || 0) >= depth) {
     return value
   }
-  seen.add(value)
+  seen.set(value, depth)
   depth--
   if (isRef(value)) {
     traverse(value.value, depth, seen)
index ff06fbea77439b06b4931a61e75b336f8c271fa1..07a589fb0f5c09d5e811ac0bbd61f542ff4913b9 100644 (file)
@@ -1689,6 +1689,57 @@ describe('api: watch', () => {
     expect(cb).toHaveBeenCalledTimes(4)
   })
 
+  test('watching the same object at different depths', async () => {
+    const arr1: any[] = reactive([[[{ foo: {} }]]])
+    const arr2 = arr1[0]
+    const arr3 = arr2[0]
+    const obj = arr3[0]
+    arr1.push(arr3)
+
+    const cb1 = vi.fn()
+    const cb2 = vi.fn()
+    const cb3 = vi.fn()
+    const cb4 = vi.fn()
+    watch(arr1, cb1, { deep: 1 })
+    watch(arr1, cb2, { deep: 2 })
+    watch(arr1, cb3, { deep: 3 })
+    watch(arr1, cb4, { deep: 4 })
+
+    await nextTick()
+    expect(cb1).toHaveBeenCalledTimes(0)
+    expect(cb2).toHaveBeenCalledTimes(0)
+    expect(cb3).toHaveBeenCalledTimes(0)
+    expect(cb4).toHaveBeenCalledTimes(0)
+
+    obj.foo = {}
+    await nextTick()
+    expect(cb1).toHaveBeenCalledTimes(0)
+    expect(cb2).toHaveBeenCalledTimes(0)
+    expect(cb3).toHaveBeenCalledTimes(1)
+    expect(cb4).toHaveBeenCalledTimes(1)
+
+    obj.foo.bar = 1
+    await nextTick()
+    expect(cb1).toHaveBeenCalledTimes(0)
+    expect(cb2).toHaveBeenCalledTimes(0)
+    expect(cb3).toHaveBeenCalledTimes(1)
+    expect(cb4).toHaveBeenCalledTimes(2)
+
+    arr3.push(obj.foo)
+    await nextTick()
+    expect(cb1).toHaveBeenCalledTimes(0)
+    expect(cb2).toHaveBeenCalledTimes(1)
+    expect(cb3).toHaveBeenCalledTimes(2)
+    expect(cb4).toHaveBeenCalledTimes(3)
+
+    obj.foo.bar = 2
+    await nextTick()
+    expect(cb1).toHaveBeenCalledTimes(0)
+    expect(cb2).toHaveBeenCalledTimes(1)
+    expect(cb3).toHaveBeenCalledTimes(3)
+    expect(cb4).toHaveBeenCalledTimes(4)
+  })
+
   test('pause / resume', async () => {
     const count = ref(0)
     const cb = vi.fn()