From 2c3524cba2abd68e87564d29a8084836c5f763cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 8 Nov 2023 18:40:37 +0800 Subject: [PATCH 1/9] feat(apiWatch): control watch observation depth --- .../runtime-core/__tests__/apiWatch.spec.ts | 32 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 29 ++++++++++++----- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f24ce80b9df..23d4585c0e5 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1205,4 +1205,36 @@ describe('api: watch', () => { expect(countWE).toBe(3) expect(countW).toBe(2) }) + + it('Passing deep as a number for use as depth', async () => { + const state = reactive({ + a: { + b: { + c: { + d: { + e: 1 + } + } + } + } + }) + + const cb = vi.fn() + + watch(state, cb, { deep: 2 }) + + state.a.b = { c: { d: { e: 2 } } } + await nextTick() + expect(cb).toHaveBeenCalledTimes(1) + + state.a.b.c = { d: { e: 3 } } + + await nextTick() + expect(cb).toHaveBeenCalledTimes(1) + + state.a.b = { c: { d: { e: 4 } } } + + await nextTick() + expect(cb).toHaveBeenCalledTimes(2) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 1b85ba12d19..db6d03ba302 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -74,7 +74,7 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate - deep?: boolean + deep?: boolean | number } export type WatchStopHandle = () => void @@ -210,7 +210,7 @@ function doWatch( forceTrigger = isShallow(source) } else if (isReactive(source)) { getter = () => source - deep = true + if (!deep) deep = true } else if (isArray(source)) { isMultiSource = true forceTrigger = source.some(s => isReactive(s) || isShallow(s)) @@ -270,7 +270,7 @@ function doWatch( if (cb && deep) { const baseGetter = getter - getter = () => traverse(baseGetter()) + getter = () => traverse(baseGetter(), deep) } let cleanup: () => void @@ -437,28 +437,41 @@ export function createPathGetter(ctx: any, path: string) { } } -export function traverse(value: unknown, seen?: Set) { +export function traverse( + value: unknown, + deep?: boolean | number, + currentDepth = 0, + seen?: Set +) { if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) { return value } + + if (typeof deep === 'number') { + if (currentDepth >= deep) { + 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, deep, currentDepth, seen) } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { - traverse(value[i], seen) + traverse(value[i], deep, currentDepth, seen) } } else if (isSet(value) || isMap(value)) { value.forEach((v: any) => { - traverse(v, seen) + traverse(v, deep, currentDepth, seen) }) } else if (isPlainObject(value)) { for (const key in value) { - traverse(value[key], seen) + traverse(value[key], deep, currentDepth, seen) } } return value From 36e7d415c7f705bce144dbc90964d534519854dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Mon, 20 Nov 2023 17:22:24 +0800 Subject: [PATCH 2/9] feat(apiWatch): introducing the `depth` option --- .../runtime-core/__tests__/apiWatch.spec.ts | 2 +- packages/runtime-core/src/apiWatch.ts | 28 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 23d4585c0e5..3f62a13bc87 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1221,7 +1221,7 @@ describe('api: watch', () => { const cb = vi.fn() - watch(state, cb, { deep: 2 }) + watch(state, cb, { deep: true, depth: 2 }) state.a.b = { c: { d: { e: 2 } } } await nextTick() diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index db6d03ba302..54d7d60f8a4 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -74,7 +74,8 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate - deep?: boolean | number + deep?: boolean + depth?: number } export type WatchStopHandle = () => void @@ -172,7 +173,14 @@ export function watch = false>( function doWatch( source: WatchSource | WatchSource[] | WatchEffect | object, cb: WatchCallback | null, - { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ + { + immediate, + deep, + flush, + depth, + onTrack, + onTrigger + }: WatchOptions = EMPTY_OBJ ): WatchStopHandle { if (__DEV__ && !cb) { if (immediate !== undefined) { @@ -270,7 +278,7 @@ function doWatch( if (cb && deep) { const baseGetter = getter - getter = () => traverse(baseGetter(), deep) + getter = () => traverse(baseGetter(), depth) } let cleanup: () => void @@ -439,7 +447,7 @@ export function createPathGetter(ctx: any, path: string) { export function traverse( value: unknown, - deep?: boolean | number, + depth?: number, currentDepth = 0, seen?: Set ) { @@ -447,8 +455,8 @@ export function traverse( return value } - if (typeof deep === 'number') { - if (currentDepth >= deep) { + if (depth && depth > 0) { + if (currentDepth >= depth) { return value } currentDepth++ @@ -460,18 +468,18 @@ export function traverse( } seen.add(value) if (isRef(value)) { - traverse(value.value, deep, currentDepth, seen) + traverse(value.value, depth, currentDepth, seen) } else if (isArray(value)) { for (let i = 0; i < value.length; i++) { - traverse(value[i], deep, currentDepth, seen) + traverse(value[i], depth, currentDepth, seen) } } else if (isSet(value) || isMap(value)) { value.forEach((v: any) => { - traverse(v, deep, currentDepth, seen) + traverse(v, depth, currentDepth, seen) }) } else if (isPlainObject(value)) { for (const key in value) { - traverse(value[key], deep, currentDepth, seen) + traverse(value[key], depth, currentDepth, seen) } } return value From 1741978e00c031405615b9b26643c459ce2e5524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Tue, 21 Nov 2023 09:48:44 +0800 Subject: [PATCH 3/9] test(apiWatch): add array test --- .../runtime-core/__tests__/apiWatch.spec.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 38d4a62cd6e..c3b43f52428 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1272,4 +1272,41 @@ describe('api: watch', () => { await nextTick() expect(cb).toHaveBeenCalledTimes(2) }) + + it('observation array depth', async () => { + const arr = ref([ + { + a: { + b: 2 + } + }, + { + a: { + b: 3 + } + } + ]) + const cb = vi.fn() + watch(arr, cb, { deep: true, depth: 2 }) + + arr.value[0].a.b = 3 + await nextTick() + expect(cb).toHaveBeenCalledTimes(0) + + arr.value[0].a = { b: 3 } + await nextTick() + expect(cb).toHaveBeenCalledTimes(1) + + arr.value[1].a = { b: 4 } + await nextTick() + expect(cb).toHaveBeenCalledTimes(2) + + arr.value.push({ a: { b: 5 } }) + await nextTick() + expect(cb).toHaveBeenCalledTimes(3) + + arr.value.pop() + await nextTick() + expect(cb).toHaveBeenCalledTimes(4) + }) }) From bf84dc6fcaa73d382478b816a1af6cdebeb97fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 27 Dec 2023 10:54:42 +0800 Subject: [PATCH 4/9] fix(apiWatch): only monitor the first layer changes of shallowReactive --- .../runtime-core/__tests__/apiWatch.spec.ts | 35 ++++++++++++++++++- packages/runtime-core/src/apiWatch.ts | 7 ++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index dbdf940bc1c..a28501245d9 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -30,7 +30,8 @@ import { shallowRef, Ref, effectScope, - toRef + toRef, + shallowReactive } from '@vue/reactivity' // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch @@ -1439,4 +1440,36 @@ describe('api: watch', () => { await nextTick() expect(cb).toHaveBeenCalledTimes(4) }) + + it('shallowReactive', async () => { + const state = shallowReactive({ + msg: ref('hello'), + foo: { + a: ref(1), + b: 2 + }, + bar: 'bar' + }) + + const spy = vi.fn() + + watch(state, spy) + + state.msg.value = 'hi' + await nextTick() + expect(spy).not.toHaveBeenCalled() + + state.bar = 'bar2' + await nextTick() + expect(spy).toHaveBeenCalledTimes(1) + + state.foo.a.value++ + state.foo.b++ + await nextTick() + expect(spy).toHaveBeenCalledTimes(1) + + state.bar = 'bar3' + await nextTick() + expect(spy).toHaveBeenCalledTimes(2) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 40a5ceeb87a..2c4699f9088 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -234,7 +234,8 @@ function doWatch( forceTrigger = isShallow(source) } else if (isReactive(source)) { getter = () => source - if (!deep) deep = true + deep = true + if (isShallow(source)) depth = 1 } else if (isArray(source)) { isMultiSource = true forceTrigger = source.some(s => isReactive(s) || isShallow(s)) @@ -243,7 +244,7 @@ function doWatch( if (isRef(s)) { return s.value } else if (isReactive(s)) { - return traverse(s) + return traverse(s, isShallow(s) ? 1 : depth) } else if (isFunction(s)) { return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER) } else { @@ -286,7 +287,7 @@ function doWatch( isArray(val) && checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance) ) { - traverse(val) + traverse(val, depth) } return val } From 26a640ce503c5584aede5617ce833d2ae4a4c96b Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 27 Dec 2023 03:08:51 +0000 Subject: [PATCH 5/9] [autofix.ci] apply automated fixes --- packages/runtime-core/src/apiWatch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 5406e67752a..7b44acee5dd 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -467,7 +467,7 @@ export function traverse( value: unknown, depth?: number, currentDepth = 0, - seen?: Set + seen?: Set, ) { if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) { return value From a067f4f4dd43c563ad7473a933c6dd9ee0d54848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 27 Dec 2023 12:28:20 +0800 Subject: [PATCH 6/9] fix(apiWatch): allows passing deep: false to listen to the first layer --- .../runtime-core/__tests__/apiWatch.spec.ts | 20 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 7089d4cc341..625f9ee5c3b 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1472,4 +1472,24 @@ describe('api: watch', () => { await nextTick() expect(spy).toHaveBeenCalledTimes(2) }) + it('watching reactive with deep: false', async () => { + const state = reactive({ + foo: { + a: 2, + }, + bar: 'bar', + }) + + const spy = vi.fn() + + watch(state, spy, { deep: false }) + + state.foo.a++ + await nextTick() + expect(spy).toHaveBeenCalledTimes(0) + + state.bar = 'bar2' + await nextTick() + expect(spy).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 7b44acee5dd..6ffc52cb449 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -234,8 +234,8 @@ function doWatch( forceTrigger = isShallow(source) } else if (isReactive(source)) { getter = () => source + if (!deep || isShallow(source)) depth = 1 deep = true - if (isShallow(source)) depth = 1 } else if (isArray(source)) { isMultiSource = true forceTrigger = source.some(s => isReactive(s) || isShallow(s)) From 6b97c567ddbe1bdcc8a4365fa93d452cfaae0572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Thu, 28 Dec 2023 10:47:54 +0800 Subject: [PATCH 7/9] test: add test case --- .../runtime-core/__tests__/apiWatch.spec.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 625f9ee5c3b..4572ac74b51 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1372,7 +1372,7 @@ describe('api: watch', () => { expect(spy2).toHaveBeenCalledTimes(1) }) - it('Control object listening depth using the depth option', async () => { + it('watching reactive depth', async () => { const state = reactive({ a: { b: { @@ -1404,7 +1404,27 @@ describe('api: watch', () => { expect(cb).toHaveBeenCalledTimes(2) }) - it('observation array depth', async () => { + it('watching ref depth', async () => { + const state = ref({ + a: { + b: 2, + }, + }) + + const cb = vi.fn() + + watch(state, cb, { deep: true, depth: 1 }) + + state.value.a.b = 3 + await nextTick() + expect(cb).toHaveBeenCalledTimes(0) + + state.value.a = { b: 3 } + await nextTick() + expect(cb).toHaveBeenCalledTimes(1) + }) + + it('watching array depth', async () => { const arr = ref([ { a: { From b242550d93c045af4f9c533367b6e2cd7a96bae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Sun, 31 Dec 2023 12:34:40 +0800 Subject: [PATCH 8/9] feat(apiWatch): support deep passing number type watching depth --- .../runtime-core/__tests__/apiWatch.spec.ts | 6 +++--- packages/runtime-core/src/apiWatch.ts | 19 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 09e516fde95..6553e233802 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1441,7 +1441,7 @@ describe('api: watch', () => { const cb = vi.fn() - watch(state, cb, { deep: true, depth: 2 }) + watch(state, cb, { deep: 2 }) state.a.b = { c: { d: { e: 2 } } } await nextTick() @@ -1467,7 +1467,7 @@ describe('api: watch', () => { const cb = vi.fn() - watch(state, cb, { deep: true, depth: 1 }) + watch(state, cb, { deep: 1 }) state.value.a.b = 3 await nextTick() @@ -1492,7 +1492,7 @@ describe('api: watch', () => { }, ]) const cb = vi.fn() - watch(arr, cb, { deep: true, depth: 2 }) + watch(arr, cb, { deep: 2 }) arr.value[0].a.b = 3 await nextTick() diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 8fad52709e1..563a257bab8 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -74,8 +74,7 @@ export interface WatchOptionsBase extends DebuggerOptions { export interface WatchOptions extends WatchOptionsBase { immediate?: Immediate - deep?: boolean - depth?: number + deep?: boolean | number once?: boolean } @@ -178,7 +177,6 @@ function doWatch( immediate, deep, flush, - depth, once, onTrack, onTrigger, @@ -233,10 +231,9 @@ function doWatch( getter = () => source.value forceTrigger = isShallow(source) } else if (isReactive(source)) { - getter = - isShallow(source) || deep === false - ? () => traverse(source, 1) - : () => traverse(source) + getter = () => source + if (isShallow(source) || deep === false) deep = 1 + if (deep === void 0) deep = true forceTrigger = true } else if (isArray(source)) { isMultiSource = true @@ -289,7 +286,7 @@ function doWatch( isArray(val) && checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance) ) { - traverse(val, depth) + traverse(val, deep) } return val } @@ -297,7 +294,7 @@ function doWatch( if (cb && deep) { const baseGetter = getter - getter = () => traverse(baseGetter(), depth) + getter = () => traverse(baseGetter(), deep) } let cleanup: (() => void) | undefined @@ -467,7 +464,7 @@ export function createPathGetter(ctx: any, path: string) { export function traverse( value: unknown, - depth?: number, + depth?: number | boolean, currentDepth = 0, seen?: Set, ) { @@ -475,7 +472,7 @@ export function traverse( return value } - if (depth && depth > 0) { + if (typeof depth === 'number' && depth > 0) { if (currentDepth >= depth) { return value } From 4d1d15c444b7b6e900f4067feaee793cd9d34655 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 2 Aug 2024 11:34:45 +0800 Subject: [PATCH 9/9] refactor: separate deep and depth logic --- packages/runtime-core/src/apiWatch.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index ed62a9c7066..fc2c7e4497d 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -189,9 +189,6 @@ function doWatch( } } - // Convert the `deep` option to a number type. - deep = deep === true ? Infinity : deep === false ? 0 : deep - if (__DEV__ && !cb) { if (immediate !== undefined) { warn( @@ -227,7 +224,8 @@ function doWatch( // traverse will happen in wrapped getter below if (deep) return source // for `deep: false | 0` or shallow reactive, only traverse root-level properties - if (isShallow(source) || deep === 0) return traverse(source, 1) + if (isShallow(source) || deep === false || deep === 0) + return traverse(source, 1) // for `deep: undefined` on a reactive object, deeply traverse all properties return traverse(source) } @@ -290,7 +288,7 @@ function doWatch( isArray(val) && checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance) ) { - traverse(val, deep) + traverse(val) } return val } @@ -298,7 +296,8 @@ function doWatch( if (cb && deep) { const baseGetter = getter - getter = () => traverse(baseGetter(), deep) + const depth = deep === true ? Infinity : deep + getter = () => traverse(baseGetter(), depth) } let cleanup: (() => void) | undefined