From aad376cc25f1992bbda04a56d7544ec403da9d05 Mon Sep 17 00:00:00 2001 From: Matt Garrett Date: Fri, 13 Sep 2024 10:18:18 -0700 Subject: [PATCH 1/2] fix(runtime-core): unwatch should be callable during SSR --- packages/global.d.ts | 1 + .../runtime-core/__tests__/apiWatch.spec.ts | 50 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 10 ++-- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/packages/global.d.ts b/packages/global.d.ts index 79b55171384..57dd00a9fab 100644 --- a/packages/global.d.ts +++ b/packages/global.d.ts @@ -9,6 +9,7 @@ declare var __ESM_BUNDLER__: boolean declare var __ESM_BROWSER__: boolean declare var __CJS__: boolean declare var __SSR__: boolean +declare var __VUE_SSR_SETTERS__: Array<(v: boolean) => void> declare var __COMMIT__: string declare var __VERSION__: string declare var __COMPAT__: boolean diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 7a800949eea..5696964671d 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -373,6 +373,56 @@ describe('api: watch', () => { expect(dummy).toBe(0) }) + it('stopping the watcher (SSR)', async () => { + type SetBoolean = (v: boolean) => void + const setSSR = (ssr: boolean) => { + __SSR__ = ssr + __VUE_SSR_SETTERS__.forEach((setInSSRSetupState: SetBoolean) => { + setInSSRSetupState(ssr) + }) + } + setSSR(true) + + let dummy = 0 + const count = ref(1) + const captureValue = (value: number) => { + dummy = value + } + const watchCallback = vi.fn(newValue => { + captureValue(newValue) + }) + const scenario = () => { + const Comp = defineComponent({ + created() { + const getter = () => this.count + captureValue(getter()) // sets dummy to 1 + const stop = this.$watch(getter, watchCallback) + stop() + this.count = 2 // shouldn't trigger side effect + }, + render() { + return h('div', this.count) + }, + setup() { + return { count } + }, + }) + const root = nodeOps.createElement('div') + render(h(Comp), root) + } + + expect(scenario).not.toThrowError(/stop is not a function/) + expect(watchCallback).not.toHaveBeenCalled() + expect(dummy).toBe(1) + await nextTick() + count.value = 3 // shouldn't trigger side effect + await nextTick() + expect(watchCallback).not.toHaveBeenCalled() + expect(dummy).toBe(1) + + setSSR(false) + }) + it('stopping the watcher (with source)', async () => { const state = reactive({ count: 0 }) let dummy diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index a14823beb62..798b6e7261b 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -179,11 +179,11 @@ function doWatch( // immediately watch or watchEffect baseWatchOptions.once = true } else { - return { - stop: NOOP, - resume: NOOP, - pause: NOOP, - } as WatchHandle + const watchStopHandle = () => {} + watchStopHandle.stop = NOOP + watchStopHandle.resume = NOOP + watchStopHandle.pause = NOOP + return watchStopHandle } } From 3d993ecb396e6a8770d6d84a006dd5cb8cbaa978 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 16 Sep 2024 10:46:30 +0800 Subject: [PATCH 2/2] chore: update test --- packages/global.d.ts | 1 - .../runtime-core/__tests__/apiWatch.spec.ts | 52 +++++++------------ 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/packages/global.d.ts b/packages/global.d.ts index 57dd00a9fab..79b55171384 100644 --- a/packages/global.d.ts +++ b/packages/global.d.ts @@ -9,7 +9,6 @@ declare var __ESM_BUNDLER__: boolean declare var __ESM_BROWSER__: boolean declare var __CJS__: boolean declare var __SSR__: boolean -declare var __VUE_SSR_SETTERS__: Array<(v: boolean) => void> declare var __COMMIT__: string declare var __VERSION__: string declare var __COMPAT__: boolean diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 5696964671d..082d585b852 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -37,6 +37,7 @@ import { toRef, triggerRef, } from '@vue/reactivity' +import { renderToString } from '@vue/server-renderer' describe('api: watch', () => { it('effect', async () => { @@ -374,15 +375,6 @@ describe('api: watch', () => { }) it('stopping the watcher (SSR)', async () => { - type SetBoolean = (v: boolean) => void - const setSSR = (ssr: boolean) => { - __SSR__ = ssr - __VUE_SSR_SETTERS__.forEach((setInSSRSetupState: SetBoolean) => { - setInSSRSetupState(ssr) - }) - } - setSSR(true) - let dummy = 0 const count = ref(1) const captureValue = (value: number) => { @@ -391,27 +383,25 @@ describe('api: watch', () => { const watchCallback = vi.fn(newValue => { captureValue(newValue) }) - const scenario = () => { - const Comp = defineComponent({ - created() { - const getter = () => this.count - captureValue(getter()) // sets dummy to 1 - const stop = this.$watch(getter, watchCallback) - stop() - this.count = 2 // shouldn't trigger side effect - }, - render() { - return h('div', this.count) - }, - setup() { - return { count } - }, - }) - const root = nodeOps.createElement('div') - render(h(Comp), root) - } - - expect(scenario).not.toThrowError(/stop is not a function/) + const Comp = defineComponent({ + created() { + const getter = () => this.count + captureValue(getter()) // sets dummy to 1 + const stop = this.$watch(getter, watchCallback) + stop() + this.count = 2 // shouldn't trigger side effect + }, + render() { + return h('div', this.count) + }, + setup() { + return { count } + }, + }) + let html + html = await renderToString(h(Comp)) + // should not throw here + expect(html).toBe(`
2
`) expect(watchCallback).not.toHaveBeenCalled() expect(dummy).toBe(1) await nextTick() @@ -419,8 +409,6 @@ describe('api: watch', () => { await nextTick() expect(watchCallback).not.toHaveBeenCalled() expect(dummy).toBe(1) - - setSSR(false) }) it('stopping the watcher (with source)', async () => {