我正在从事React项目,我正在调查一些图书馆。我发现他们使用的‘useCallback’和我使用的不同。下面是代码部分。我仍然认为这段代码与直接使用"useCallback“没有什么区别。
// Saves incoming handler to the ref in order to avoid "useCallback hell"
export function useEventCallback<T, K>(handler?: (value: T, event: K) => void): (value: T, event: K) => void {
const callbackRef = useRef(handler);
useEffect(() => {
callbackRef.current = handler;
});
return useCallback((value: T, event: K) => callbackRef.current && callbackRef.current(value, event), []);
}所以我的问题是,这是什么意思'useCallback地狱‘?以这种方式使用"useCallback“有什么好处呢?
// BTW :我在react文档上找到了一个类似的例子。但我还是听不懂https://en.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback
发布于 2022-08-24 01:22:47
执行普通useCallback时,必须传入包含函数使用的变量的依赖项数组。当其中一个改变的时候,回忆录就会破裂。在许多情况下,这是很好的,但有时你的回忆录总是在破碎(因为你依赖的是不断变化的价值观)。当这种情况发生时,useCallback提供的好处很少或根本没有。
您所展示的代码的目标是,即使您有复杂的依赖关系,回忆录也不会中断。注意,当它调用useCallback时,它传入一个空的依赖数组[]。这与使用ref来跟踪最新的handler是相结合的。然后,当函数最终被调用时,它将检查ref中最新的handler并调用它。这个最新的handler在其闭包中有最新的值,因此它的行为与预期的一样。
这段代码确实实现了永不破坏回忆录的目标。然而,它需要谨慎使用。如果使用function的并发呈现,并且在呈现过程中调用useEventCallback返回的函数,则可以得到一些意想不到的结果。只有在呈现之外调用函数才是安全的,例如在事件回调中,这就是为什么他们将其命名为useEventCallback的原因。
发布于 2022-08-25 22:00:50
举例说明:
部件体中的功能
function App() {
const [count, setCount] = useState(0);
const lastRenderTime = new Date().toString();
function bodyFn() {
alert("bodyFn: " + lastRenderTime);
}
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={bodyFn} />
</>
);
}每当<App>组件重新呈现时(例如,如果修改了count状态),就会创建一个新的bodyFn。
如果<SomeChildComponent>监视它的onClick支柱引用(通常在依赖数组中),它每次都会看到新的创建。
但是事件回调行为与预期相同:每当调用bodyFn时,它都是该函数的“最新创建”,特别是它正确地使用了lastRenderTime的最新值(与已经显示的值相同)。
useCallback的常见用法
function App() {
const lastRenderTime = new Date().toString();
const ucbFn = useCallback(
() => alert("ucbFn: " + lastRenderTime),
[lastRenderTime]
);
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={ucbFn} />
</>
);
}每当<App>重新呈现时,lastRenderTime值就会不同。因此,useCallback依赖数组启动,为ucbFn创建了一个新的更新函数。
与前一种情况一样,<SomeChildComponent>每次都会看到这种变化。useCallback的使用似乎毫无意义!
但至少,这种行为也和预期的一样:函数始终是最新的,并显示正确的lastRenderTime值。
试图避免useCallback依赖
function App() {
const lastRenderTime = new Date().toString();
const ucbFnNoDeps = useCallback(
() => alert("ucbFnNoDeps: " + lastRenderTime),
[] // Attempt to avoid cb modification by emptying the dependency array
);
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={ucbFnNoDeps} />
</>
);
}在试图恢复useCallback优势的“天真”尝试中,人们可能会尝试将lastRenderTime从其依赖数组中删除。
现在,ucbFnNoDeps确实总是一样的,<SomeChildComponent>也不会改变。
但是现在,这种行为不再像人们所期望的那样:ucbFnNoDeps读取它创建时在其范围内的lastRenderTime的值,即呈现first time <App>!
带有自定义useEventCallback钩子
function App() {
const lastRenderTime = new Date().toString();
const uecbFn = useEventCallback(
() => alert("uecbFn: " + lastRenderTime),
);
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={uecbFn} />
</>
);
}每当<App>重新呈现时,就会创建一个新的箭头函数( useEventCallback自定义钩子的参数)。但是钩子内部只是将它存储在它的useRef当前占位符中。
钩子返回函数(在uecbFn中)永远不会改变。所以<SomeChildComponent>看不出任何变化。
但是恢复了最初预期的行为:在执行回调时,它将查找当前占位符内容,这是最近创建的箭头函数。因此,它使用了最近的lastRenderTime值!
<SomeChildComponent>实例
依赖于其回调支持之一的引用的组件的示例可以是:
function SomeChildComponent({
onClick,
}: {
onClick: () => void;
}) {
const countRef = useRef(0);
useEffect(
() => { countRef.current += 1; },
[onClick] // Increment count when onClick reference changes
);
return (
<div>
onClick changed {countRef.current} time(s)
<button onClick={onClick}>click</button>
</div>
)
}CodeSandbox上的演示:https://codesandbox.io/s/nice-dubinsky-qjp3gk
https://stackoverflow.com/questions/73466390
复制相似问题