为什么使用useCallback时要使用'ref'而不是直接使用useCallback

问题描述 投票:0回答:2

我正在研究 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”有什么好处?

// 顺便说一句:我在 React 文档中找到了类似的示例。但我还是不明白 https://en.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback

reactjs typescript react-hooks
2个回答
6
投票

当您执行正常的

useCallback
操作时,您必须传入一个包含函数使用的变量的依赖项数组。当其中之一发生变化时,记忆就会中断。这在很多情况下都很好,但有时你的记忆总是会被破坏(因为你依赖于不断变化的值)。当这种情况发生时,
useCallback
几乎没有任何好处。

您展示的代码的目标是记忆化永远不会中断,即使您有复杂的依赖关系。请注意,当它调用 useCallback

 时,它会传入一个空的依赖项数组 
[]
。与使用 ref 相结合,可以跟踪最新的 
handler
 是什么。然后,当最终调用该函数时,它将检查最新的 
handler
 的 ref 并调用它。最新的 
handler
 在其闭包中具有最新值,因此它的行为符合预期。

这段代码确实实现了永不破坏记忆的目标。然而,它需要小心使用。如果你使用的是react的并发渲染,并且在渲染过程中调用

useEventCallback

返回的函数,你可能会得到一些意想不到的结果。只有在渲染之外调用该函数才是安全的,例如在事件回调中,这就是他们将其命名为 
useEventCallback
 的原因。


2
投票
一些例子的解释:

组件体内的函数

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
创建时在其范围内的值,即<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

© www.soinside.com 2019 - 2024. All rights reserved.