Skip to content

refThrottled fix: 🧩 fixed reference issues for complex data types #3705

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

Closed
wants to merge 13 commits into from
Closed
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
91 changes: 91 additions & 0 deletions packages/shared/refThrottled/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, expect, it } from 'vitest'
import { ref } from 'vue-demi'
import { refThrottled } from '.'

describe('refThrottled', () => {
it('should handle string type', async () => {
const strRef = ref('Hello')
const throttledRef = refThrottled(strRef, 100)
expect(throttledRef.value).toBe('Hello')

strRef.value = 'World'
expect(throttledRef.value).toBe('Hello')
await new Promise(resolve => setTimeout(resolve, 200))

expect(throttledRef.value).toBe('World')
})

it('should handle object type', async () => {
const objRef = ref({ key: 'value' })
const throttledRef = refThrottled(objRef, 100)

expect(throttledRef.value).toEqual({ key: 'value' })

objRef.value.key = 'new value'
expect(throttledRef.value).toEqual({ key: 'value' })
await new Promise(resolve => setTimeout(resolve, 200))

expect(throttledRef.value).toEqual({ key: 'new value' })
})

it('should handle array type', async () => {
const arrRef = ref([1, 2, 3])
const throttledRef = refThrottled(arrRef, 100)
expect(throttledRef.value).toEqual([1, 2, 3])
arrRef.value.push(4)
expect(throttledRef.value).toEqual([1, 2, 3])
await new Promise(resolve => setTimeout(resolve, 200))

expect(throttledRef.value).toEqual([1, 2, 3, 4])
})

it('should handle cloneHandler', async () => {
const objRef = ref({ key: 'value' })
let error = null

try {
refThrottled(objRef, 100, true, true, true, true, () => {
throw new Error('cloneHandler error')
})
}
catch (e) {
error = e as Error
}

expect(error).toBeInstanceOf(Error)
expect(error?.message).toBe('cloneHandler error')
})

it('should handle default cloneHandler error', async () => {
const objRef = ref({}) as any
objRef.value.self = objRef.value
let error = null
let throttledRef = null
try {
throttledRef = refThrottled(objRef, 100, true, true, true)
}
catch (e) {
error = e as Error
}
expect(throttledRef?.value).toEqual(objRef.value)
expect(error).toBeNull()
})

it('should handle object options', async () => {
const strRef = ref('Hello')
const throttledRef = refThrottled(strRef, 100)
const objParamsThrottledRef = refThrottled({
value: strRef,
delay: 100,
})
expect(throttledRef.value).toBe('Hello')
expect(objParamsThrottledRef.value).toBe('Hello')
strRef.value = 'World'
expect(throttledRef.value).toBe('Hello')
expect(objParamsThrottledRef.value).toBe('Hello')
await new Promise(resolve => setTimeout(resolve, 200))

expect(throttledRef.value).toBe('World')
expect(objParamsThrottledRef.value).toBe('World')
})
})
93 changes: 82 additions & 11 deletions packages/shared/refThrottled/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
import type { Ref } from 'vue-demi'
import { ref, watch } from 'vue-demi'
import type { Ref, WatchOptions } from 'vue-demi'
import { isRef, watch } from 'vue-demi'
import { cloneFnJSON, useCloned } from '../../core/useCloned'
import { useThrottleFn } from '../useThrottleFn'

function isReferenceType(value: any) {
return value !== null && (typeof value === 'object' || typeof value === 'function')
}
interface RefThrottledOptions<T> extends WatchOptions {
/**
* Ref value to be watched with throttle effect.
*/
value: Ref<T>
/**
* A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
*
* @default 200
*/
delay?: number
/**
* If true, update the value again after the delay time is up.
*
* @default true
*/
trailing?: boolean
/**
* If true, update the value on the leading edge of the ms timeout.
*
* @default true
*/
leading?: boolean
/**
* By default, it use `JSON.parse(JSON.stringify(value))` to clone
*
*/
cloneHandler?: (value: T) => T
}
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
Expand All @@ -10,20 +43,58 @@ import { useThrottleFn } from '../useThrottleFn'
* @param delay A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
* @param [trailing] if true, update the value again after the delay time is up
* @param [leading] if true, update the value on the leading edge of the ms timeout
* @param [deep] default true, Depth monitoring, if false, only monitor the first level
* @param [immediate] default true, Whether to execute immediately
* @param [cloneHandler] By default, it use `JSON.parse(JSON.stringify(value))` to clone
*/
export function refThrottled<T>(value: Ref<T>, delay = 200, trailing = true, leading = true) {
export function refThrottled<T>(value: Ref<T>, delay?: number, trailing?: boolean, leading?: boolean, deep?: boolean, immediate?: boolean, cloneHandler?: (value: T) => T): Ref<T>
export function refThrottled<T>(options: RefThrottledOptions<T>): Ref<T>
export function refThrottled<T>(...args: any[]): Ref<T> {
const isFirstRef = isRef(args[0])
let value: Ref<T>
let delay: number
let trailing: boolean
let leading: boolean
let deep: boolean
let immediate: boolean
let cloneHandler: (value: T) => T
if (isFirstRef)
[value, delay = 200, trailing = true, leading = true, deep = true, immediate = true, cloneHandler = cloneFnJSON] = args
else
({ value, delay = 200, trailing = true, leading = true, deep = true, immediate = true, cloneHandler = cloneFnJSON } = args[0])
if (delay <= 0)
return value
const isEqualityClone = cloneHandler === cloneFnJSON
const { sync, cloned: throttled } = useCloned<T>(
value,
{
manual: true,
clone: (cloneValue) => {
try {
return isReferenceType(cloneValue) ? cloneHandler(cloneValue) : cloneValue
}
catch (error) {
if (isEqualityClone) {
console.error('Some error occurred in cloneHandler, used uncloned value', error)
return cloneValue
}
else {
throw error
}
}
},
...(isFirstRef ? {} : args[0]),
deep,
immediate,
},
)
const updater = useThrottleFn(sync, delay, trailing, leading)

const throttled: Ref<T> = ref(value.value as T) as Ref<T>

const updater = useThrottleFn(() => {
throttled.value = value.value
}, delay, trailing, leading)

watch(value, () => updater())
watch(value, updater, {
deep,
})

return throttled
return throttled as Ref<T>
}

// alias
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy