[英]why use 'ref' when you use useCallback instead of using useCallback directly
我正在研究 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 hell'是什么意思? 以这种方式使用“useCallback”有什么好处?
// 顺便说一句:我在反应文档中找到了一个类似的示例。 但我仍然无法理解https://en.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-chang-value-from-usecallback
当您执行正常的useCallback
时,您必须传入一个依赖数组,其中包含 function 使用的变量。 当其中一个发生变化时,记忆就会中断。 在许多情况下这很好,但有时你的记忆总是在中断(因为你依赖于一直在变化的值)。 发生这种情况时, useCallback
提供的好处很少或根本没有。
您展示的代码的目标是记忆化永不中断,即使您有复杂的依赖关系。 请注意,当它调用useCallback
时,它传入一个空的依赖数组[]
。 结合使用 ref 能够跟踪最新的handler
是什么。 然后当最终调用 function 时,它将检查 ref 以获取最新的handler
并调用它。 最新的handler
在其闭包中具有最新的值,因此它的行为符合预期。
这段代码确实实现了永不破坏记忆的目标。 但是,需要谨慎使用。 如果你使用react的并发渲染,在渲染过程中调用useEventCallback返回的useEventCallback
,会得到一些意想不到的结果。 只有在渲染之外调用 function 是安全的,例如在事件回调中,这就是他们将其命名为useEventCallback
的原因。
举例说明:
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
state),就会创建一个新的bodyFn
。
如果<SomeChildComponent>
监视它的onClick
引用(通常在依赖数组中),它将每次都看到新的创建。
但是事件回调行为是预期的:每当bodyFn
时,它都是 function 的“最新创建”,特别是它正确使用了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 创建一个新的更新ucbFn
。
与前一种情况一样, <SomeChildComponent>
每次都会看到这种变化。 而且useCallback
的使用似乎毫无意义!
但至少,行为也符合预期:function 始终是最新的,并显示正确的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 的值,该值在其创建时的 scope 中lastRenderTime
即第一次呈现<App>
!
useEventCallback
钩子function App() {
const lastRenderTime = new Date().toString();
const uecbFn = useEventCallback(
() => alert("uecbFn: " + lastRenderTime),
);
return (
<>
Last render time: {lastRenderTime}
<SomeChildComponent onClick={uecbFn} />
</>
);
}
每当<App>
重新渲染时,都会创建一个新箭头 function( useEventCallback
自定义挂钩的参数)。 但是钩子内部只是将它存储在它的useRef
当前占位符中。
钩子返回 function,在uecbFn
中,永远不会改变。 所以<SomeChildComponent>
看不到任何变化。
但恢复了最初预期的行为:回调执行时,会查找当前占位符内容,即最近创建的箭头 function。 因此使用最新的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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.