Skip to content

Commit a645e7a

Browse files
Alfred-Skybluesxzz
authored andcommitted
feat(runtime-core): add once option to watch (#9034)
1 parent edf2572 commit a645e7a

File tree

2 files changed

+61
-8
lines changed

2 files changed

+61
-8
lines changed

packages/runtime-core/__tests__/apiWatch.spec.ts

+38
Original file line numberDiff line numberDiff line change
@@ -1205,4 +1205,42 @@ describe('api: watch', () => {
12051205
expect(countWE).toBe(3)
12061206
expect(countW).toBe(2)
12071207
})
1208+
1209+
const options = [
1210+
{ name: 'only trigger once watch' },
1211+
{
1212+
deep: true,
1213+
name: 'only trigger once watch with deep'
1214+
},
1215+
{
1216+
flush: 'sync',
1217+
name: 'only trigger once watch with flush: sync'
1218+
},
1219+
{
1220+
flush: 'pre',
1221+
name: 'only trigger once watch with flush: pre'
1222+
},
1223+
{
1224+
immediate: true,
1225+
name: 'only trigger once watch with immediate'
1226+
}
1227+
] as const
1228+
test.each(options)('$name', async option => {
1229+
const count = ref(0)
1230+
const cb = vi.fn()
1231+
1232+
watch(count, cb, { once: true, ...option })
1233+
1234+
count.value++
1235+
await nextTick()
1236+
1237+
expect(count.value).toBe(1)
1238+
expect(cb).toHaveBeenCalledTimes(1)
1239+
1240+
count.value++
1241+
await nextTick()
1242+
1243+
expect(count.value).toBe(2)
1244+
expect(cb).toHaveBeenCalledTimes(1)
1245+
})
12081246
})

packages/runtime-core/src/apiWatch.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface WatchOptionsBase extends DebuggerOptions {
7575
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
7676
immediate?: Immediate
7777
deep?: boolean
78+
once?: boolean
7879
}
7980

8081
export type WatchStopHandle = () => void
@@ -172,8 +173,16 @@ export function watch<T = any, Immediate extends Readonly<boolean> = false>(
172173
function doWatch(
173174
source: WatchSource | WatchSource[] | WatchEffect | object,
174175
cb: WatchCallback | null,
175-
{ immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
176+
{ immediate, deep, flush, once, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
176177
): WatchStopHandle {
178+
if (cb && once) {
179+
const _cb = cb
180+
cb = (...args) => {
181+
_cb(...args)
182+
unwatch()
183+
}
184+
}
185+
177186
if (__DEV__ && !cb) {
178187
if (immediate !== undefined) {
179188
warn(
@@ -187,6 +196,12 @@ function doWatch(
187196
`watch(source, callback, options?) signature.`
188197
)
189198
}
199+
if (once !== undefined) {
200+
warn(
201+
`watch() "once" option is only respected when using the ` +
202+
`watch(source, callback, options?) signature.`
203+
)
204+
}
190205
}
191206

192207
const warnInvalidSource = (s: unknown) => {
@@ -363,6 +378,13 @@ function doWatch(
363378

364379
const effect = new ReactiveEffect(getter, scheduler)
365380

381+
const unwatch = () => {
382+
effect.stop()
383+
if (instance && instance.scope) {
384+
remove(instance.scope.effects!, effect)
385+
}
386+
}
387+
366388
if (__DEV__) {
367389
effect.onTrack = onTrack
368390
effect.onTrigger = onTrigger
@@ -384,13 +406,6 @@ function doWatch(
384406
effect.run()
385407
}
386408

387-
const unwatch = () => {
388-
effect.stop()
389-
if (instance && instance.scope) {
390-
remove(instance.scope.effects!, effect)
391-
}
392-
}
393-
394409
if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
395410
return unwatch
396411
}

0 commit comments

Comments
 (0)