fix(runtime-core): support deep: false when watch reactive (#9928)

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>
This commit is contained in:
yangxiuxiu 2023-12-30 18:52:17 +08:00 committed by GitHub
parent dce99c12df
commit 4f703d120d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 79 additions and 8 deletions

View 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)

View 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