Skip to content

Commit 17e6a54

Browse files
committed
feat: add equals option to watch
1 parent 9e88707 commit 17e6a54

2 files changed

Lines changed: 65 additions & 10 deletions

File tree

src/v3/apiWatch.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface WatchOptionsBase extends DebuggerOptions {
5353
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
5454
immediate?: Immediate
5555
deep?: boolean
56+
equals?: (value: any, oldValue: any) => boolean
5657
}
5758

5859
export type WatchStopHandle = () => void
@@ -159,7 +160,8 @@ function doWatch(
159160
deep,
160161
flush = 'pre',
161162
onTrack,
162-
onTrigger
163+
onTrigger,
164+
equals
163165
}: WatchOptions = emptyObject
164166
): WatchStopHandle {
165167
if (__DEV__ && !cb) {
@@ -175,6 +177,17 @@ function doWatch(
175177
`watch(source, callback, options?) signature.`
176178
)
177179
}
180+
if (equals !== undefined) {
181+
warn(
182+
`watch() "equals" option is only respected when using the ` +
183+
`watch(source, callback, options?) signature.`
184+
)
185+
}
186+
}
187+
188+
const equalsFn = isFunction(equals) ? equals : undefined
189+
if (__DEV__ && equals !== undefined && !equalsFn) {
190+
warn(`watch() "equals" option must be a function.`)
178191
}
179192

180193
const warnInvalidSource = (s: unknown) => {
@@ -275,7 +288,18 @@ function doWatch(
275288
})
276289
watcher.noRecurse = !cb
277290

278-
let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
291+
let oldValue: any = INITIAL_WATCHER_VALUE
292+
const hasChangedValue = (newValue: any, oldValue: any) => {
293+
if (equalsFn) {
294+
return !equalsFn(newValue, oldValue)
295+
}
296+
if (isMultiSource) {
297+
return (newValue as any[]).some((v, i) =>
298+
hasChanged(v, (oldValue as any[])[i])
299+
)
300+
}
301+
return hasChanged(newValue, oldValue)
302+
}
279303
// overwrite default run
280304
watcher.run = () => {
281305
if (!watcher.active) {
@@ -284,14 +308,12 @@ function doWatch(
284308
if (cb) {
285309
// watch(source, cb)
286310
const newValue = watcher.get()
311+
const isInitialValue = oldValue === INITIAL_WATCHER_VALUE
287312
if (
288-
deep ||
289-
forceTrigger ||
290-
(isMultiSource
291-
? (newValue as any[]).some((v, i) =>
292-
hasChanged(v, (oldValue as any[])[i])
293-
)
294-
: hasChanged(newValue, oldValue))
313+
isInitialValue ||
314+
(equalsFn
315+
? hasChangedValue(newValue, oldValue)
316+
: deep || forceTrigger || hasChangedValue(newValue, oldValue))
295317
) {
296318
// cleanup before running cb again
297319
if (cleanup) {
@@ -300,7 +322,7 @@ function doWatch(
300322
call(cb, WATCHER_CB, [
301323
newValue,
302324
// pass undefined as the old value when it's changed for the first time
303-
oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
325+
isInitialValue ? (isMultiSource ? [] : undefined) : oldValue,
304326
onCleanup
305327
])
306328
oldValue = newValue

test/unit/features/v3/apiWatch.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,39 @@ describe('api: watch', () => {
305305
expect(cleanup).toHaveBeenCalledTimes(2)
306306
})
307307

308+
it('respects equals to skip cleanup when values are equivalent', async () => {
309+
const state = ref({ count: 0 })
310+
const cleanup = vi.fn()
311+
const cb = vi.fn((_value, _oldValue, onCleanup) => {
312+
onCleanup(cleanup)
313+
})
314+
315+
watch(state, cb, {
316+
deep: true,
317+
equals: (value, oldValue) => value.count === oldValue.count
318+
})
319+
320+
state.value = { count: 0 }
321+
await nextTick()
322+
expect(cb).toHaveBeenCalledTimes(0)
323+
expect(cleanup).toHaveBeenCalledTimes(0)
324+
325+
state.value = { count: 1 }
326+
await nextTick()
327+
expect(cb).toHaveBeenCalledTimes(1)
328+
expect(cleanup).toHaveBeenCalledTimes(0)
329+
330+
state.value = { count: 1 }
331+
await nextTick()
332+
expect(cb).toHaveBeenCalledTimes(1)
333+
expect(cleanup).toHaveBeenCalledTimes(0)
334+
335+
state.value = { count: 2 }
336+
await nextTick()
337+
expect(cb).toHaveBeenCalledTimes(2)
338+
expect(cleanup).toHaveBeenCalledTimes(1)
339+
})
340+
308341
it('flush timing: pre (default)', async () => {
309342
const count = ref(0)
310343
const count2 = ref(0)

0 commit comments

Comments
 (0)