[英]How do I implement debounce in ramda
我很确定答案是不可能的,但我想知道是否可以使用 Ramda 实现lodash.debounce
,这样我就可以摆脱我的应用程序中的lodash
依赖项,因为它就是这样。
这是我正在使用的代码
import debounce from "lodash.debounce";
import { Dispatch, useCallback, useState } from "react";
/**
* This is a variant of set state that debounces rapid changes to a state.
* This perform a shallow state check, use {@link useDebouncedDeepState}
* for a deep comparison. Internally this uses
* [lodash debounce](https://lodash.com/docs/#debounce) to perform
* the debounce operation.
* @param initialValue initial value
* @param wait debounce wait
* @param debounceSettings debounce settings.
* @returns state and setter
*
*/
export function useDebouncedState<S>(
initialValue: S,
wait: number,
debounceSettings?: Parameters<typeof debounce>[2]
): [S, Dispatch<S>] {
const [state, setState] = useState<S>(initialValue);
const debouncedSetState = useCallback(
debounce(setState, wait, debounceSettings),
[wait, debounceSettings]
);
useEffect(()=> {
return () => debouncedSetState.cancel();
}, []);
return [state, debouncedSetState];
}
去抖不取消
VLAZ 链接有人可以解释 Javascript 中的“去抖动”功能吗? 但是您似乎很失望并正在寻找具有取消机制的东西。 我对那个问题提供的答案实现了一个香草debounce
——
✅ | 在任何给定时间最多有一个待处理的承诺(每个去抖任务) |
✅ | 通过正确取消待处理的 Promise 来阻止内存泄漏 |
✅ | 只解决最新的承诺 |
❌ | 暴露取消机制 |
我们用两个参数编写了debounce
,要 debounce 的task
和要延迟的毫秒数ms
。 我们为其本地状态引入了一个本地绑定, t
-
// original implementation
function debounce(task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return async (...args) => { // ⚠️ does not return cancel mechanism
try {
t.cancel()
t = deferred(ms)
await t.promise
await task(...args)
}
catch (_) { /* prevent memory leak */ }
}
}
// original usage
// ⚠️ how to cancel?
myform.mybutton.addEventListener("click", debounce(clickCounter, 1000))
现在有外部取消
原始代码大小平易近人,不到 10 行,旨在供您修改以满足您的特定需求。 我们可以通过简单地将取消机制包含在另一个返回值中来公开取消机制 -
// revised implementation
function debounce(task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return [
async (...args) => {
try {
t.cancel()
t = deferred(ms)
await t.promise
await task(...args)
}
catch (_) { /* prevent memory leak */ }
},
_ => t.cancel() // ✅ return cancellation mechanism
]
}
// revised usage
const [inc, cancel] = debounce(clickCounter, 1000) // ✅ two controls
myform.mybutton.addEventListener("click", inc)
myform.mycancel.addEventListener("click", cancel)
延期
debounce
依赖于一个可重用的deferred
函数,它创建了一个在ms
毫秒内解析的新承诺。 在链接的问答中阅读更多相关信息 -
function deferred(ms) {
let cancel, promise = new Promise((resolve, reject) => {
cancel = reject
setTimeout(resolve, ms)
})
return { promise, cancel }
}
取消演示
运行下面的代码片段。 Click将消除一 (1) 秒的抖动。 去抖定时器到期后,计数器递增。 但是,如果您在inc
去抖动时单击取消,则挂起的功能将被取消,并且计数器不会增加。
// debounce, compressed for demo function debounce(task, ms) { let t = { promise: null, cancel: _ => void 0 } return [ async (...args) => { try { t.cancel(); t = deferred(ms); await t.promise; await task(...args) } catch (_) { /* prevent memory leak */ } }, _ => t.cancel() ] } // deferred, compressed for demo function deferred(ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } } // dom references const myform = document.forms.myform const mycounter = myform.mycounter // event handler function clickCounter (event) { mycounter.value = Number(mycounter.value) + 1 } // debounced listener [inc, cancel] = debounce(clickCounter, 1000) myform.myclicker.addEventListener("click", inc) myform.mycancel.addEventListener("click", cancel)
<form id="myform"> <input name="myclicker" type="button" value="click" /> <input name="mycancel" type="button" value="cancel" /> <output name="mycounter">0</output> </form>
类型
对于deferred
和debounce
的一些合理注释,供考虑类型的人使用。
// cancel : () -> void
//
// waiting : {
// promise: void promise,
// cancel: cancel
// }
//
// deferred : int -> waiting
function deferred(ms) {
let cancel, promise = new Promise((resolve, reject) => {
cancel = reject
setTimeout(resolve, ms)
})
return { promise, cancel }
}
// 'a task : (...any -> 'a)
//
// debounce : ('a task, int) -> ('a task, cancel)
function debounce(task, ms) {
let t = { promise: null, cancel: _ => void 0 }
return [
async (...args) => {
try {
t.cancel()
t = deferred(ms)
await t.promise
await task(...args)
}
catch (_) { /* prevent memory leak */ }
},
_ => t.cancel()
]
}
反应钩子
使用debounce
实现useDebounce
非常简单。 请记住在卸载组件时cancel
以防止任何悬空的去抖动操作 -
function useDebounce(task, ms) {
const [f, cancel] = debounce(task, ms)
useEffect(_ => cancel) // ✅ auto-cancel when component unmounts
return [f, cancel]
}
将useDebounce
添加到您的组件与我们在上面使用 vanilla debounce
的方式相同。 如果消除状态突变,请确保使用功能更新,因为 setter 将被异步调用 -
function App() {
const [count, setCount] = React.useState(0)
const [inc, cancel] = useDebounce(
_ => setCount(x => x + 1), // ✅ functional update
1000
)
return <div>
<button onClick={inc}>click</button>
<button onClick={cancel}>cancel</button>
<span>{count}</span>
</div>
}
反应去抖演示
这个演示和上面唯一的一样,只使用了 React 和我们的useDebounce
钩子——
// debounce, compressed for demo function debounce(task, ms) { let t = { promise: null, cancel: _ => void 0 } return [ (...args) => { t.cancel(); t = deferred(ms); t.promise.then(_ => task(...args)).catch(_ => {}) }, _ => t.cancel() ] } // deferred, compressed for demo function deferred(ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } } function useDebounce(task, ms) { const [f, cancel] = debounce(task, ms) React.useEffect(_ => cancel) return [f, cancel] } function App() { const [count, setCount] = React.useState(0) const [inc, cancel] = useDebounce( _ => setCount(x => x + 1), 1000 ) return <div> <button onClick={inc}>click</button> <button onClick={cancel}>cancel</button> <span>{count}</span> </div> } ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script> <div id="app"></div>
多次去抖动
让我们仔细检查一切是否正确,并显示在同一页面上使用了多个 debounce。 我们将通过添加更多调用相同去抖动函数的单击按钮来扩展计数器示例。 我们将在同一页面上放置多个计数器,以表明多个去抖动器保持单独控制并且不会中断其他去抖动器。 这是应用程序的预览 -
运行演示并验证这些行为 -
✅ | 3 个计数器,每个计数器都有自己的计数器状态 |
✅ | 每个计数器都有 3 个去抖动的Click按钮和一个Cancel按钮 |
✅ | 每次点击都可以用来增加计数器的值 |
✅ | 每次点击都会中断来自属于该计数器的其他点击的任何反跳增量 |
✅ | Cancel按钮将取消来自该计数器的任何Click的去抖动增量 |
✅ | 取消不会取消属于其他计数器的去抖动增量 |
function debounce(task, ms) { let t = { promise: null, cancel: _ => void 0 }; return [ (...args) => { t.cancel(); t = deferred(ms); t.promise.then(_ => task(...args)).catch(_ => {}) }, _ => t.cancel() ] } function deferred(ms) { let cancel, promise = new Promise((resolve, reject) => { cancel = reject; setTimeout(resolve, ms) }); return { promise, cancel } } function useDebounce(task, ms) {const [f, cancel] = debounce(task, ms); React.useEffect(_ => cancel); return [f, cancel] } function useCounter() { const [count, setCount] = React.useState(0) const [inc, cancel] = useDebounce( _ => setCount(x => x + 1), 1000 ) return [count, <div className="counter"> <button onClick={inc}>click</button> <button onClick={inc}>click</button> <button onClick={inc}>click</button> <button onClick={cancel}>cancel</button> <span>{count}</span> </div>] } function App() { const [a, counterA] = useCounter() const [b, counterB] = useCounter() const [c, counterC] = useCounter() return <div> {counterA} {counterB} {counterC} <pre>Total: {a+b+c}</pre> </div> } ReactDOM.render(<App/>, document.querySelector("#app"))
.counter { padding: 0.5rem; margin-top: 0.5rem; background-color: #ccf; } pre { padding: 0.5rem; background-color: #ffc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script> <div id="app"></div>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.