Skip to content

Commit

Permalink
refactor: use depth traversal + improve tests
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 30, 2023
1 parent 2bab130 commit 5ca83cd
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 39 deletions.
45 changes: 34 additions & 11 deletions packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import {
shallowRef,
Ref,
effectScope,
toRef
toRef,
shallowReactive,
type ShallowRef
} from '@vue/reactivity'

// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
Expand Down Expand Up @@ -156,7 +158,7 @@ describe('api: watch', () => {
expect(dummy).toBe(1)
})

it('directly watching reactive object: deep: false', async () => {
it('directly watching reactive object with explicit deep: false', async () => {
const src = reactive({
state: {
count: 0
Expand All @@ -166,26 +168,47 @@ describe('api: watch', () => {
watch(
src,
({ state }) => {
dummy = 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)
})

it('directly watching reactive array', async () => {
const src = reactive([0])
let dummy
watch(src, v => {
dummy = v
})
src.push(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()
expect(dummy).toMatchObject([0, 1])
// should trigger on array self mutation
expect(cb).toBeCalledTimes(1)
})

it('watching multiple sources', async () => {
Expand Down
51 changes: 23 additions & 28 deletions packages/runtime-core/src/apiWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,10 @@ function doWatch(
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
deep = isShallow(source) ? false : deep ?? true
getter = deep ? () => source : () => shallowTraverse(source)
getter =
isShallow(source) || deep === false
? () => traverse(source, 1)
: () => traverse(source)
forceTrigger = true
} else if (isArray(source)) {
isMultiSource = true
Expand All @@ -220,7 +222,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 @@ -439,48 +441,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)
}
} else if (isSet(value) || isMap(value)) {
value.forEach((v: any) => {
traverse(v, seen)
})
} else if (isPlainObject(value)) {
for (const key in value) {
traverse(value[key], seen)
}
}
return value
}

export function shallowTraverse(value: unknown) {
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
return value
}
if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i]
traverse(value[i], depth, currentDepth, seen)
}
} else if (isSet(value) || isMap(value)) {
value.forEach((v: any) => {
v
traverse(v, depth, currentDepth, seen)
})
} else if (isPlainObject(value)) {
for (const key in value) {
value[key]
traverse(value[key], depth, currentDepth, seen)
}
}
return value
Expand Down

0 comments on commit 5ca83cd

Please sign in to comment.