我正在写 useDebounce
实用钩子(Hook)。
function debounce(fn, delay) {
let timer = 0;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, delay);
};
}
function useDebounce(fn, delay) {
const ref = React.useRef(fn);
React.useLayoutEffect(() => {
ref.current = fn;
}, [fn]);
return useMemo(() => debounce(ref.current, delay), [delay]);
}
我用 ref
存储回调并使用 useLayoutEffect
更新它因此 API 的使用者不需要内存他们自己的回调。而且我想先发制人地回答我知道useMemo
有效,我知道您可以备注回调,即 fn
传入 useDebounce
来自外部,但我不想给 API 的用户带来负担,所以我自己做了。这是一个现场演示:https://codesandbox.io/s/closure-bug-xcvyd?file=/src/App.js
现在我要谴责的功能是
const increment = () => {
console.log(count);
setCount(count + 1);
};
所以我把它传给了useDebounce
但似乎该函数以 count
上的陈旧关闭结束因为它只更新 count
来自 0
-> 1
然后在那之后,无论您单击按钮多少次,它都不再更新。是的,我知道我可以写
setCount(c => c + 1);
解决这个问题。但令我困惑的是,如果我重写
useMemo(() => debounce(ref.current, delay), [delay]);
至return useMemo(() => debounce((...args) => ref.current(...args), delay), [ delay ]);
然后这个问题会自动修复。我似乎无法理解
(...args) => ref.current(...args)
正在解决问题。
最佳答案
让我们一步一步来看看发生了什么。
fn
内部引用。 fn
去抖内部useMemo
这就是错误所在。 在下一次渲染时,您将再次更新
ref
,但内存功能根本不使用它。它记得对第一次通过的 fn
的引用只有当你的钩子(Hook)的用户改变延迟时,这才会改变。在固定示例中,使用箭头函数会发生以下情况:
fn
内部引用 ref
的函数并且会在每次调用时查看它,所以它会选择最新鲜的 fn
来自引用的值(value)。 function useDebounce(fn, delay) {
// storing function into ref
const ref = React.useRef(fn);
// updating function after memoization, and on each render when function changed
React.useLayoutEffect(() => {
ref.current = fn;
}, [fn]);
return useMemo(function() {
// here you are referencing current `fn`
// The very first `fn` that was passed into hook
// ref don't play role here - you are passing `ref.current`
let fnToDebounce = ref.current
return debounce(fnToDebounce, delay)
}, [delay]);
}
function useDebounce(fn, delay) {
// storing function into ref
const ref = React.useRef(fn);
// updating function after memoization, and on each render when function changed
React.useLayoutEffect(() => {
ref.current = fn;
}, [fn]);
return useMemo(function() {
// Here you are closuring reference to `ref` and `fnToDebounce` have no idea what is inside it.
// When it would be called, it will get ref and only at that point will retrieve `ref.current`, that will be the latest `fn`
let fnToDebounce = (...args) => ref.current(...args);
return debounce(fnToDebounce, delay);
}, [delay]);
}
关于javascript - react : weird stale closure issue with `useRef` ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66616061/