Skip to content

refactor(watch): replace WatchStopHandle with WatchHandle in watchAtMost, watchDebounced, watchIgnorable, watchTriggerable, and watchWithFilter for consistency and improved functionality #4852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions packages/shared/watchAtMost/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MaybeRefOrGetter, ShallowRef, WatchCallback, WatchSource, WatchStopHandle } from 'vue'
import type { MaybeRefOrGetter, ShallowRef, WatchCallback, WatchHandle, WatchSource } from 'vue'
import type { MapOldSources, MapSources } from '../utils'
import type { WatchWithFilterOptions } from '../watchWithFilter'
import { nextTick, shallowRef, toValue } from 'vue'
Expand All @@ -8,8 +8,7 @@ export interface WatchAtMostOptions<Immediate> extends WatchWithFilterOptions<Im
count: MaybeRefOrGetter<number>
}

export interface WatchAtMostReturn {
stop: WatchStopHandle
export interface WatchAtMostReturn extends WatchHandle {
count: ShallowRef<number>
}

Expand All @@ -31,17 +30,21 @@ export function watchAtMost<Immediate extends Readonly<boolean> = false>(

const current = shallowRef(0)

const stop = watchWithFilter(
const watchHandle = watchWithFilter(
source,
(...args) => {
current.value += 1
if (current.value >= toValue(count))
nextTick(() => stop())
nextTick(() => watchHandle.stop())

cb(...args)
},
watchOptions,
)

return { count: current, stop }
const res = watchHandle as WatchAtMostReturn

res.count = current

return res
}
9 changes: 8 additions & 1 deletion packages/shared/watchDebounced/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { shallowRef } from 'vue'
const input = shallowRef('')
const updated = shallowRef(0)

watchDebounced(input, () => {
const { pause, resume } = watchDebounced(input, () => {
updated.value += 1
}, { debounce: 1000, maxWait: 5000 })
</script>
Expand All @@ -15,6 +15,13 @@ watchDebounced(input, () => {
<input v-model="input" placeholder="Try to type anything..." type="text">
<note>Delay is set to 1000ms and maxWait is set to 5000ms for this demo.</note>

<button @click="pause">
Pause
</button>
<button @click="resume">
Resume
</button>

<p>Input: {{ input }}</p>
<p>Times Updated: {{ updated }}</p>
</div>
Expand Down
10 changes: 5 additions & 5 deletions packages/shared/watchDebounced/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MaybeRefOrGetter, WatchCallback, WatchOptions, WatchSource, WatchStopHandle } from 'vue'
import type { MaybeRefOrGetter, WatchCallback, WatchHandle, WatchOptions, WatchSource } from 'vue'
import type { DebounceFilterOptions, MapOldSources, MapSources } from '../utils'
import { debounceFilter } from '../utils'
import { watchWithFilter } from '../watchWithFilter'
Expand All @@ -8,16 +8,16 @@ export interface WatchDebouncedOptions<Immediate> extends WatchOptions<Immediate
}

// overloads
export function watchDebounced<T extends Readonly<WatchSource<unknown>[]>, Immediate extends Readonly<boolean> = false>(sources: [...T], cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options?: WatchDebouncedOptions<Immediate>): WatchStopHandle
export function watchDebounced<T, Immediate extends Readonly<boolean> = false>(source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchDebouncedOptions<Immediate>): WatchStopHandle
export function watchDebounced<T extends object, Immediate extends Readonly<boolean> = false>(source: T, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchDebouncedOptions<Immediate>): WatchStopHandle
export function watchDebounced<T extends Readonly<WatchSource<unknown>[]>, Immediate extends Readonly<boolean> = false>(sources: [...T], cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options?: WatchDebouncedOptions<Immediate>): WatchHandle
export function watchDebounced<T, Immediate extends Readonly<boolean> = false>(source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchDebouncedOptions<Immediate>): WatchHandle
export function watchDebounced<T extends object, Immediate extends Readonly<boolean> = false>(source: T, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchDebouncedOptions<Immediate>): WatchHandle

// implementation
export function watchDebounced<Immediate extends Readonly<boolean> = false>(
source: any,
cb: any,
options: WatchDebouncedOptions<Immediate> = {},
): WatchStopHandle {
): WatchHandle {
const {
debounce = 0,
maxWait = undefined,
Expand Down
8 changes: 4 additions & 4 deletions packages/shared/watchDeep/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { WatchCallback, WatchOptions, WatchSource, WatchStopHandle } from 'vue'
import type { WatchCallback, WatchHandle, WatchOptions, WatchSource } from 'vue'
import type { MapOldSources, MapSources } from '../utils/types'

import { watch } from 'vue'
Expand All @@ -11,13 +11,13 @@ export function watchDeep<
source: [...T],
cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>,
options?: Omit<WatchOptions<Immediate>, 'deep'>
): WatchStopHandle
): WatchHandle

export function watchDeep<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: Omit<WatchOptions<Immediate>, 'deep'>
): WatchStopHandle
): WatchHandle

export function watchDeep<
T extends object,
Expand All @@ -26,7 +26,7 @@ export function watchDeep<
source: T,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: Omit<WatchOptions<Immediate>, 'deep'>
): WatchStopHandle
): WatchHandle

/**
* Shorthand for watching value with {deep: true}
Expand Down
8 changes: 7 additions & 1 deletion packages/shared/watchIgnorable/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { shallowRef } from 'vue'
const log = shallowRef('')
const source = shallowRef(0)

const { ignoreUpdates } = watchIgnorable(
const { ignoreUpdates, pause, resume } = watchIgnorable(
source,
v => (log.value += `Changed to "${v}"\n`),
{ flush: 'sync' },
Expand Down Expand Up @@ -33,6 +33,12 @@ function ignoredUpdate() {
<button class="orange" @click="ignoredUpdate">
Ignored Update
</button>
<button @click="pause">
Pause
</button>
<button @click="resume">
Resume
</button>
<button @click="clear">
Reset
</button>
Expand Down
34 changes: 25 additions & 9 deletions packages/shared/watchIgnorable/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { WatchCallback, WatchSource, WatchStopHandle } from 'vue'
import type { Fn, MapOldSources, MapSources } from '../utils'
import type { WatchCallback, WatchHandle, WatchSource } from 'vue'
import type { MapOldSources, MapSources } from '../utils'
import type { WatchWithFilterOptions } from '../watchWithFilter'
import { watch } from 'vue'
import { bypassFilter, createFilterWrapper } from '../utils'
Expand All @@ -11,10 +11,9 @@ import { bypassFilter, createFilterWrapper } from '../utils'
export type IgnoredUpdater = (updater: () => void) => void
export type IgnoredPrevAsyncUpdates = () => void

export interface WatchIgnorableReturn {
export interface WatchIgnorableReturn extends WatchHandle {
ignoreUpdates: IgnoredUpdater
ignorePrevAsyncUpdates: IgnoredPrevAsyncUpdates
stop: WatchStopHandle
}

export function watchIgnorable<T extends Readonly<WatchSource<unknown>[]>, Immediate extends Readonly<boolean> = false>(sources: [...T], cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options?: WatchWithFilterOptions<Immediate>): WatchIgnorableReturn
Expand All @@ -38,7 +37,7 @@ export function watchIgnorable<Immediate extends Readonly<boolean> = false>(

let ignoreUpdates: IgnoredUpdater
let ignorePrevAsyncUpdates: IgnoredPrevAsyncUpdates
let stop: WatchStopHandle
let watchHandle: WatchHandle

if (watchOptions.flush === 'sync') {
let ignore = false
Expand All @@ -54,7 +53,7 @@ export function watchIgnorable<Immediate extends Readonly<boolean> = false>(
ignore = false
}

stop = watch(
watchHandle = watch(
source,
(...args) => {
if (!ignore)
Expand All @@ -66,7 +65,7 @@ export function watchIgnorable<Immediate extends Readonly<boolean> = false>(
else {
// flush 'pre' and 'post'

const disposables: Fn[] = []
const disposables: WatchHandle[] = []

// counters for how many following changes to be ignored
// ignoreCounter is incremented before there is a history operation
Expand Down Expand Up @@ -121,12 +120,29 @@ export function watchIgnorable<Immediate extends Readonly<boolean> = false>(
),
)

stop = () => {
watchHandle = (() => {
disposables.forEach(fn => fn())
}) as WatchHandle

watchHandle.stop = () => {
disposables.forEach(fn => fn())
}

watchHandle.pause = () => {
disposables.forEach(fn => fn.pause())
}

watchHandle.resume = () => {
disposables.forEach(fn => fn.resume())
}
}

return { stop, ignoreUpdates, ignorePrevAsyncUpdates }
const res = watchHandle as WatchIgnorableReturn

res.ignoreUpdates = ignoreUpdates
res.ignorePrevAsyncUpdates = ignorePrevAsyncUpdates

return res
}

// alias
Expand Down
8 changes: 4 additions & 4 deletions packages/shared/watchImmediate/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { WatchCallback, WatchOptions, WatchSource, WatchStopHandle } from 'vue'
import type { WatchCallback, WatchHandle, WatchOptions, WatchSource } from 'vue'
import type { MapOldSources, MapSources } from '../utils/types'

import { watch } from 'vue'
Expand All @@ -8,19 +8,19 @@ export function watchImmediate<T extends Readonly<WatchSource<unknown>[]>>(
source: [...T],
cb: WatchCallback<MapSources<T>, MapOldSources<T, true>>,
options?: Omit<WatchOptions<true>, 'immediate'>
): WatchStopHandle
): WatchHandle

export function watchImmediate<T>(
source: WatchSource<T>,
cb: WatchCallback<T, T | undefined>,
options?: Omit<WatchOptions<true>, 'immediate'>
): WatchStopHandle
): WatchHandle

export function watchImmediate<T extends object>(
source: T,
cb: WatchCallback<T, T | undefined>,
options?: Omit<WatchOptions<true>, 'immediate'>
): WatchStopHandle
): WatchHandle

/**
* Shorthand for watching value with {immediate: true}
Expand Down
8 changes: 4 additions & 4 deletions packages/shared/watchOnce/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { WatchCallback, WatchOptions, WatchSource, WatchStopHandle } from 'vue'
import type { WatchCallback, WatchHandle, WatchOptions, WatchSource } from 'vue'
import type { MapOldSources, MapSources } from '../utils'
import { watch } from 'vue'

Expand All @@ -7,19 +7,19 @@ export function watchOnce<T extends Readonly<WatchSource<unknown>[]>>(
source: [...T],
cb: WatchCallback<MapSources<T>, MapOldSources<T, true>>,
options?: Omit<WatchOptions<true>, 'once'>
): WatchStopHandle
): WatchHandle

export function watchOnce<T>(
source: WatchSource<T>,
cb: WatchCallback<T, T | undefined>,
options?: Omit<WatchOptions<true>, 'once'>
): WatchStopHandle
): WatchHandle

export function watchOnce<T extends object>(
source: T,
cb: WatchCallback<T, T | undefined>,
options?: Omit<WatchOptions<true>, 'once'>
): WatchStopHandle
): WatchHandle

/**
* Shorthand for watching value with { once: true }
Expand Down
9 changes: 8 additions & 1 deletion packages/shared/watchThrottled/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { shallowRef } from 'vue'
const input = shallowRef('')
const updated = shallowRef(0)

watchThrottled(input, () => {
const { pause, resume } = watchThrottled(input, () => {
updated.value += 1
}, { throttle: 1000 })
</script>
Expand All @@ -15,6 +15,13 @@ watchThrottled(input, () => {
<input v-model="input" placeholder="Try to type anything..." type="text">
<note>Delay is set to 1000ms for this demo.</note>

<button @click="pause">
Pause
</button>
<button @click="resume">
Resume
</button>

<p>Input: {{ input }}</p>
<p>Times Updated: {{ updated }}</p>
</div>
Expand Down
10 changes: 5 additions & 5 deletions packages/shared/watchThrottled/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MaybeRefOrGetter, WatchCallback, WatchOptions, WatchSource, WatchStopHandle } from 'vue'
import type { MaybeRefOrGetter, WatchCallback, WatchHandle, WatchOptions, WatchSource } from 'vue'
import type { MapOldSources, MapSources } from '../utils'
import { throttleFilter } from '../utils'
import { watchWithFilter } from '../watchWithFilter'
Expand All @@ -10,16 +10,16 @@ export interface WatchThrottledOptions<Immediate> extends WatchOptions<Immediate
}

// overloads
export function watchThrottled<T extends Readonly<WatchSource<unknown>[]>, Immediate extends Readonly<boolean> = false>(sources: [...T], cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options?: WatchThrottledOptions<Immediate>): WatchStopHandle
export function watchThrottled<T, Immediate extends Readonly<boolean> = false>(source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchThrottledOptions<Immediate>): WatchStopHandle
export function watchThrottled<T extends object, Immediate extends Readonly<boolean> = false>(source: T, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchThrottledOptions<Immediate>): WatchStopHandle
export function watchThrottled<T extends Readonly<WatchSource<unknown>[]>, Immediate extends Readonly<boolean> = false>(sources: [...T], cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options?: WatchThrottledOptions<Immediate>): WatchHandle
export function watchThrottled<T, Immediate extends Readonly<boolean> = false>(source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchThrottledOptions<Immediate>): WatchHandle
export function watchThrottled<T extends object, Immediate extends Readonly<boolean> = false>(source: T, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchThrottledOptions<Immediate>): WatchHandle

// implementation
export function watchThrottled<Immediate extends Readonly<boolean> = false>(
source: any,
cb: any,
options: WatchThrottledOptions<Immediate> = {},
): WatchStopHandle {
): WatchHandle {
const {
throttle = 0,
trailing = true,
Expand Down
8 changes: 7 additions & 1 deletion packages/shared/watchTriggerable/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { shallowRef } from 'vue'
const log = shallowRef('')
const source = shallowRef(0)

const { trigger, ignoreUpdates } = watchTriggerable(
const { trigger, ignoreUpdates, pause, resume } = watchTriggerable(
source,
async (v, _, onCleanup) => {
let canceled = false
Expand Down Expand Up @@ -37,6 +37,12 @@ function update() {
<button class="orange" @click="trigger">
Manual Trigger
</button>
<button @click="pause">
Pause
</button>
<button @click="resume">
Resume
</button>
<button @click="clear">
Reset
</button>
Expand Down
13 changes: 7 additions & 6 deletions packages/shared/watchTriggerable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ export function watchTriggerable<Immediate extends Readonly<boolean> = false>(

return cb(value, oldValue, onCleanup)
}
const res = watchIgnorable(source, _cb, options)
const { ignoreUpdates } = res
const watchHandle = watchIgnorable(source, _cb, options)
const { ignoreUpdates } = watchHandle

const trigger = () => {
let res: any
Expand All @@ -62,10 +62,11 @@ export function watchTriggerable<Immediate extends Readonly<boolean> = false>(
return res
}

return {
...res,
trigger,
}
const res = watchHandle as WatchTriggerableReturn

res.trigger = trigger

return res
}

function getWatchSources(sources: any) {
Expand Down
Loading
Loading