在 useEffect 中调用事件处理程序道具的好模式是什么?

时间:2021-03-18 21:16:23

标签: reactjs react-hooks

让我们假设一个组件:

const Foo = ({id, onError}) => {
    useEffect(() => {
        subscribe(id).catch(error => onError(error));
        return () => cleanup(id);
    }, [id, onError]);

  return <div>...</div>;
}

这个想法很简单——运行一个使用当前“id”订阅的效果。如果订阅失败,则调用作为 prop 传递的事件处理程序 onError

但是,要使其正常工作,传递的 onError 属性必须是引用稳定的。换句话说,如果我的组件的使用者尝试了以下操作,他们可能会遇到每次渲染都运行效果的问题:

const Parent = () => {
   // handleError is recreated for each render, causing the effect to run each time
   const handleError = error => {
     console.log("error", error);
   }

   return <Foo id="test" onError={handleError} />
}

相反,他们需要执行以下操作:

const Parent = () => {
   // handleError identity is stable
   const handleError = useCallback(error => {
     console.log("error", error);
   },[]);

   return <Foo id="test" onError={handleError} />
}

有效,但我对它不满意。 Foo 的使用者需要意识到 onError 必须是稳定的,除非您查看其底层实现,否则这不是显而易见的。它打破了组件封装,消费者很容易在不知不觉中遇到问题。

是否有更好的模式来管理可能在 useEffect 内调用的事件处理程序之类的道具?

1 个答案:

答案 0 :(得分:2)

您需要从您的依赖项列表中删除 onError,但如果它发生变化仍会调用。为此,您可以使用 ref,并在每次渲染时通过 useEffect 更新它。

如果函数是 undefined,您也可以使用 optional chaining ?. 来避免调用该函数。

const Foo = ({ id, onError }) => {
  const onErrorRef = useRef(); 
  
  useEffect(() => {
    onErrorRef.current = onError;
  });

  useEffect(() => {
    subscribe(id).catch(error => onErrorRef.current?.(error));
    return () => cleanup(id);
  }, [id]);

  return <div>...</div>;
}