]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): support deep: false when watch reactive (#9928)
authoryangxiuxiu <79584569+RicardoErii@users.noreply.github.com>
Sat, 30 Dec 2023 10:52:17 +0000 (18:52 +0800)
committerGitHub <noreply@github.com>
Sat, 30 Dec 2023 10:52:17 +0000 (18:52 +0800)
close #9916

---------

Co-authored-by: RicardoErii <‘1974364190@qq.com’>
Co-authored-by: Evan You <yyx990803@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/apiWatch.ts

index 3bc614ef9b7aeecc293af08980b67af527da000b..b25635e664e741b17f44df7443a2ba340729937e 100644 (file)
@@ -25,9 +25,11 @@ import {
   type DebuggerEvent,
   ITERATE_KEY,
   type Ref,
+  type ShallowRef,
   TrackOpTypes,
   TriggerOpTypes,
   effectScope,
+  shallowReactive,
   shallowRef,
   toRef,
   triggerRef,
@@ -156,6 +158,59 @@ describe('api: watch', () => {
     expect(dummy).toBe(1)
   })
 
+  it('directly watching reactive object with explicit deep: false', async () => {
+    const src = reactive({
+      state: {
+        count: 0,
+      },
+    })
+    let dummy
+    watch(
+      src,
+      ({ state }) => {
+        dummy = state?.count
+      },
+      {
+        deep: false,
+      },
+    )
+
+    // nested should not trigger
+    src.state.count++
+    await nextTick()
+    expect(dummy).toBe(undefined)
+
+    // root level should trigger
+    src.state = { count: 1 }
+    await nextTick()
+    expect(dummy).toBe(1)
+  })
+
+  // #9916
+  it('directly watching shallow reactive array', async () => {
+    class foo {
+      prop1: ShallowRef<string> = shallowRef('')
+      prop2: string = ''
+    }
+
+    const obj1 = new foo()
+    const obj2 = new foo()
+
+    const collection = shallowReactive([obj1, obj2])
+    const cb = vi.fn()
+    watch(collection, cb)
+
+    collection[0].prop1.value = 'foo'
+    await nextTick()
+    // should not trigger
+    expect(cb).toBeCalledTimes(0)
+
+    collection.push(new foo())
+    await nextTick()
+    // should trigger on array self mutation
+    expect(cb).toBeCalledTimes(1)
+  })
+
   it('watching multiple sources', async () => {
     const state = reactive({ count: 1 })
     const count = ref(1)
index a3cd3894f41ba59fd5f86ebcd9713165c3546b27..0c13e72988f6d18f717f6e5436b7a00ab5895371 100644 (file)
@@ -231,8 +231,11 @@ function doWatch(
     getter = () => source.value
     forceTrigger = isShallow(source)
   } else if (isReactive(source)) {
-    getter = () => source
-    deep = true
+    getter =
+      isShallow(source) || deep === false
+        ? () => traverse(source, 1)
+        : () => traverse(source)
+    forceTrigger = true
   } else if (isArray(source)) {
     isMultiSource = true
     forceTrigger = source.some(s => isReactive(s) || isShallow(s))
@@ -241,7 +244,7 @@ function doWatch(
         if (isRef(s)) {
           return s.value
         } else if (isReactive(s)) {
-          return traverse(s)
+          return traverse(s, isShallow(s) || deep === false ? 1 : undefined)
         } else if (isFunction(s)) {
           return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
         } else {
@@ -460,28 +463,41 @@ export function createPathGetter(ctx: any, path: string) {
   }
 }
 
-export function traverse(value: unknown, seen?: Set<unknown>) {
+export function traverse(
+  value: unknown,
+  depth?: number,
+  currentDepth = 0,
+  seen?: Set<unknown>,
+) {
   if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
     return value
   }
+
+  if (depth && depth > 0) {
+    if (currentDepth >= depth) {
+      return value
+    }
+    currentDepth++
+  }
+
   seen = seen || new Set()
   if (seen.has(value)) {
     return value
   }
   seen.add(value)
   if (isRef(value)) {
-    traverse(value.value, seen)
+    traverse(value.value, depth, currentDepth, seen)
   } else if (isArray(value)) {
     for (let i = 0; i < value.length; i++) {
-      traverse(value[i], seen)
+      traverse(value[i], depth, currentDepth, seen)
     }
   } else if (isSet(value) || isMap(value)) {
     value.forEach((v: any) => {
-      traverse(v, seen)
+      traverse(v, depth, currentDepth, seen)
     })
   } else if (isPlainObject(value)) {
     for (const key in value) {
-      traverse(value[key], seen)
+      traverse(value[key], depth, currentDepth, seen)
     }
   }
   return value