反应自定义钩子,没有用于封闭的样板

问题描述

我有一个应侦听某些套接字事件的组件。我按如下方式注册它们,以避免事件监听器的虚假注册/取消注册操作(socketListen返回监听器本身的销毁,因此可以解决)。

useEffect(() => socketListen('name',newName => setName(newName)),[]);

现在,由于我有更多此类监听器,我想通过编写自定义钩子来摆脱样板代码

const useListener = (event,callback) => {
  useEffect(() => socketListen(event,callback),[event,callback]);
};

然后用作

useListener('name',newName => setName(newName));

意在专注于实际执行的操作,而不是useEffect调用。 现在的问题是,我将闭包(调用状态设置器setName)移交给了我的自定义钩子,这意味着callback中的useListener在重新渲染时已更改,因此再次生成虚假的侦听器注册/取消注册。为了挽救生命,我可以将闭包包装在useCallback中,但这违背了减少代码混乱的最初目标。

我认为我可以看到上述问题的功能性原因(并希望在我的写作中能够传达出来),但是我想知道是否有方便的方法编写辅助函数来完成我打算用{{ 1}},而无需其他样板代码。我的实际代码使用了更复杂的闭包,但我希望这个示例足以查明我的问题。

解决方法

您应该能够通过使用引用来摆脱对关闭记录的要求:

const useListener = (event,callback) => {
  const ref = useRef();
  ref.current = callback;
  useEffect(() => (
    socketListen(event,newName => ref.current(newName))
  ),[event]);
};

重要的是,您编写newName => ref.current(newName)而不是只写ref.current,以便在调用ref.current回调之前不会发生属性访问socketListen。这样,ref.current等于传递给callback的最新useListener(),并且不会引用从第一次调用useListener()以来的过时关闭。