mirror of https://github.com/vuejs/core.git
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:
parent
dce99c12df
commit
4f703d120d
|
@ -25,9 +25,11 @@ import {
|
||||||
type DebuggerEvent,
|
type DebuggerEvent,
|
||||||
ITERATE_KEY,
|
ITERATE_KEY,
|
||||||
type Ref,
|
type Ref,
|
||||||
|
type ShallowRef,
|
||||||
TrackOpTypes,
|
TrackOpTypes,
|
||||||
TriggerOpTypes,
|
TriggerOpTypes,
|
||||||
effectScope,
|
effectScope,
|
||||||
|
shallowReactive,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
toRef,
|
toRef,
|
||||||
triggerRef,
|
triggerRef,
|
||||||
|
@ -156,6 +158,59 @@ describe('api: watch', () => {
|
||||||
expect(dummy).toBe(1)
|
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 () => {
|
it('watching multiple sources', async () => {
|
||||||
const state = reactive({ count: 1 })
|
const state = reactive({ count: 1 })
|
||||||
const count = ref(1)
|
const count = ref(1)
|
||||||
|
|
|
@ -231,8 +231,11 @@ function doWatch(
|
||||||
getter = () => source.value
|
getter = () => source.value
|
||||||
forceTrigger = isShallow(source)
|
forceTrigger = isShallow(source)
|
||||||
} else if (isReactive(source)) {
|
} else if (isReactive(source)) {
|
||||||
getter = () => source
|
getter =
|
||||||
deep = true
|
isShallow(source) || deep === false
|
||||||
|
? () => traverse(source, 1)
|
||||||
|
: () => traverse(source)
|
||||||
|
forceTrigger = true
|
||||||
} else if (isArray(source)) {
|
} else if (isArray(source)) {
|
||||||
isMultiSource = true
|
isMultiSource = true
|
||||||
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
|
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
|
||||||
|
@ -241,7 +244,7 @@ function doWatch(
|
||||||
if (isRef(s)) {
|
if (isRef(s)) {
|
||||||
return s.value
|
return s.value
|
||||||
} else if (isReactive(s)) {
|
} else if (isReactive(s)) {
|
||||||
return traverse(s)
|
return traverse(s, isShallow(s) || deep === false ? 1 : undefined)
|
||||||
} else if (isFunction(s)) {
|
} else if (isFunction(s)) {
|
||||||
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
|
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
|
||||||
} else {
|
} 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]) {
|
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (depth && depth > 0) {
|
||||||
|
if (currentDepth >= depth) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
currentDepth++
|
||||||
|
}
|
||||||
|
|
||||||
seen = seen || new Set()
|
seen = seen || new Set()
|
||||||
if (seen.has(value)) {
|
if (seen.has(value)) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
seen.add(value)
|
seen.add(value)
|
||||||
if (isRef(value)) {
|
if (isRef(value)) {
|
||||||
traverse(value.value, seen)
|
traverse(value.value, depth, currentDepth, seen)
|
||||||
} else if (isArray(value)) {
|
} else if (isArray(value)) {
|
||||||
for (let i = 0; i < value.length; i++) {
|
for (let i = 0; i < value.length; i++) {
|
||||||
traverse(value[i], seen)
|
traverse(value[i], depth, currentDepth, seen)
|
||||||
}
|
}
|
||||||
} else if (isSet(value) || isMap(value)) {
|
} else if (isSet(value) || isMap(value)) {
|
||||||
value.forEach((v: any) => {
|
value.forEach((v: any) => {
|
||||||
traverse(v, seen)
|
traverse(v, depth, currentDepth, seen)
|
||||||
})
|
})
|
||||||
} else if (isPlainObject(value)) {
|
} else if (isPlainObject(value)) {
|
||||||
for (const key in value) {
|
for (const key in value) {
|
||||||
traverse(value[key], seen)
|
traverse(value[key], depth, currentDepth, seen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
|
Loading…
Reference in New Issue