Skip to content

Commit b094c72

Browse files
authoredOct 4, 2024··
fix(watch): watchEffect clean-up with SSR (#12097)
close #11956
1 parent 6e4de8d commit b094c72

File tree

2 files changed

+184
-6
lines changed

2 files changed

+184
-6
lines changed
 

‎packages/runtime-core/src/apiWatch.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,14 @@ function doWatch(
170170

171171
if (__DEV__) baseWatchOptions.onWarn = warn
172172

173+
// immediate watcher or watchEffect
174+
const runsImmediately = (cb && immediate) || (!cb && flush !== 'post')
173175
let ssrCleanup: (() => void)[] | undefined
174176
if (__SSR__ && isInSSRComponentSetup) {
175177
if (flush === 'sync') {
176178
const ctx = useSSRContext()!
177179
ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
178-
} else if (!cb || immediate) {
179-
// immediately watch or watchEffect
180-
baseWatchOptions.once = true
181-
} else {
180+
} else if (!runsImmediately) {
182181
const watchStopHandle = () => {}
183182
watchStopHandle.stop = NOOP
184183
watchStopHandle.resume = NOOP
@@ -226,7 +225,14 @@ function doWatch(
226225

227226
const watchHandle = baseWatch(source, cb, baseWatchOptions)
228227

229-
if (__SSR__ && ssrCleanup) ssrCleanup.push(watchHandle)
228+
if (__SSR__ && isInSSRComponentSetup) {
229+
if (ssrCleanup) {
230+
ssrCleanup.push(watchHandle)
231+
} else if (runsImmediately) {
232+
watchHandle()
233+
}
234+
}
235+
230236
return watchHandle
231237
}
232238

‎packages/server-renderer/__tests__/ssrWatch.spec.ts

+173-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { createSSRApp, defineComponent, h, ref, watch } from 'vue'
1+
import {
2+
createSSRApp,
3+
defineComponent,
4+
h,
5+
nextTick,
6+
ref,
7+
watch,
8+
watchEffect,
9+
} from 'vue'
210
import { type SSRContext, renderToString } from '../src'
311

412
describe('ssr: watch', () => {
@@ -27,4 +35,168 @@ describe('ssr: watch', () => {
2735

2836
expect(html).toMatch('hello world')
2937
})
38+
39+
test('should work with flush: sync and immediate: true', async () => {
40+
const text = ref('start')
41+
let msg = 'unchanged'
42+
43+
const App = defineComponent(() => {
44+
watch(
45+
text,
46+
() => {
47+
msg = text.value
48+
},
49+
{ flush: 'sync', immediate: true },
50+
)
51+
expect(msg).toBe('start')
52+
text.value = 'changed'
53+
expect(msg).toBe('changed')
54+
text.value = 'changed again'
55+
expect(msg).toBe('changed again')
56+
return () => h('div', null, msg)
57+
})
58+
59+
const app = createSSRApp(App)
60+
const ctx: SSRContext = {}
61+
const html = await renderToString(app, ctx)
62+
63+
expect(ctx.__watcherHandles!.length).toBe(1)
64+
expect(html).toMatch('changed again')
65+
await nextTick()
66+
expect(msg).toBe('changed again')
67+
})
68+
69+
test('should run once with immediate: true', async () => {
70+
const text = ref('start')
71+
let msg = 'unchanged'
72+
73+
const App = defineComponent(() => {
74+
watch(
75+
text,
76+
() => {
77+
msg = String(text.value)
78+
},
79+
{ immediate: true },
80+
)
81+
text.value = 'changed'
82+
expect(msg).toBe('start')
83+
return () => h('div', null, msg)
84+
})
85+
86+
const app = createSSRApp(App)
87+
const ctx: SSRContext = {}
88+
const html = await renderToString(app, ctx)
89+
90+
expect(ctx.__watcherHandles).toBeUndefined()
91+
expect(html).toMatch('start')
92+
await nextTick()
93+
expect(msg).toBe('start')
94+
})
95+
96+
test('should run once with immediate: true and flush: post', async () => {
97+
const text = ref('start')
98+
let msg = 'unchanged'
99+
100+
const App = defineComponent(() => {
101+
watch(
102+
text,
103+
() => {
104+
msg = String(text.value)
105+
},
106+
{ immediate: true, flush: 'post' },
107+
)
108+
text.value = 'changed'
109+
expect(msg).toBe('start')
110+
return () => h('div', null, msg)
111+
})
112+
113+
const app = createSSRApp(App)
114+
const ctx: SSRContext = {}
115+
const html = await renderToString(app, ctx)
116+
117+
expect(ctx.__watcherHandles).toBeUndefined()
118+
expect(html).toMatch('start')
119+
await nextTick()
120+
expect(msg).toBe('start')
121+
})
122+
})
123+
124+
describe('ssr: watchEffect', () => {
125+
test('should run with flush: sync', async () => {
126+
const text = ref('start')
127+
let msg = 'unchanged'
128+
129+
const App = defineComponent(() => {
130+
watchEffect(
131+
() => {
132+
msg = text.value
133+
},
134+
{ flush: 'sync' },
135+
)
136+
expect(msg).toBe('start')
137+
text.value = 'changed'
138+
expect(msg).toBe('changed')
139+
text.value = 'changed again'
140+
expect(msg).toBe('changed again')
141+
return () => h('div', null, msg)
142+
})
143+
144+
const app = createSSRApp(App)
145+
const ctx: SSRContext = {}
146+
const html = await renderToString(app, ctx)
147+
148+
expect(ctx.__watcherHandles!.length).toBe(1)
149+
expect(html).toMatch('changed again')
150+
await nextTick()
151+
expect(msg).toBe('changed again')
152+
})
153+
154+
test('should run once with default flush (pre)', async () => {
155+
const text = ref('start')
156+
let msg = 'unchanged'
157+
158+
const App = defineComponent(() => {
159+
watchEffect(() => {
160+
msg = text.value
161+
})
162+
text.value = 'changed'
163+
expect(msg).toBe('start')
164+
return () => h('div', null, msg)
165+
})
166+
167+
const app = createSSRApp(App)
168+
const ctx: SSRContext = {}
169+
const html = await renderToString(app, ctx)
170+
171+
expect(ctx.__watcherHandles).toBeUndefined()
172+
expect(html).toMatch('start')
173+
await nextTick()
174+
expect(msg).toBe('start')
175+
})
176+
177+
test('should not run for flush: post', async () => {
178+
const text = ref('start')
179+
let msg = 'unchanged'
180+
181+
const App = defineComponent(() => {
182+
watchEffect(
183+
() => {
184+
msg = text.value
185+
},
186+
{ flush: 'post' },
187+
)
188+
text.value = 'changed'
189+
expect(msg).toBe('unchanged')
190+
return () => h('div', null, msg)
191+
})
192+
193+
const app = createSSRApp(App)
194+
const ctx: SSRContext = {}
195+
const html = await renderToString(app, ctx)
196+
197+
expect(ctx.__watcherHandles).toBeUndefined()
198+
expect(html).toMatch('unchanged')
199+
await nextTick()
200+
expect(msg).toBe('unchanged')
201+
})
30202
})

0 commit comments

Comments
 (0)
Please sign in to comment.