Skip to content

Commit

Permalink
fix(runtime-core): support deep: false when watch reactive (#9928)
Browse files Browse the repository at this point in the history
close #9916

---------

Co-authored-by: RicardoErii <‘[email protected]’>
Co-authored-by: Evan You <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 30, 2023
1 parent dce99c1 commit 4f703d1
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 8 deletions.
55 changes: 55 additions & 0 deletions packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import {
type DebuggerEvent,
ITERATE_KEY,
type Ref,
type ShallowRef,
TrackOpTypes,
TriggerOpTypes,
effectScope,
shallowReactive,
shallowRef,
toRef,
triggerRef,
Expand Down Expand Up @@ -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)
Expand Down
32 changes: 24 additions & 8 deletions packages/runtime-core/src/apiWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 4f703d1

Please sign in to comment.