当通过钩子传递时,setState 函数是否应该是 useEffect 的依赖项

Should a setState function be a dependency of useEffect when passed via hook

所以,我偶然发现了这种奇怪的情况:

我有一个全局 React 上下文提供程序,提供一个全局状态,就像这样

const Context = createContext();

const ContextProvider = ({children}) => {
  const [state, setState] = useState('');
  return <Context.Provider value={{state, setState}}>{children}</Context.Provider>
}

const useMyState = () => {
  const {state, setState} = useContext(Context);

  return {
   state,
   setState
  }
}

const Component = () => {
  const {setState} = useMyState();

  useEffect(() => {
   elementRef.addEventListener('click', () => {
    setState('someState');
   });

   return () => {
    elementRef.removeEventListener('click', () => null); 
   }
  },[])

return <>
 // ...
</>
}

eslint 建议将我的 setState 添加到 useEffect 的依赖数组中,

useEffect(() => {
   elementRef.addEventListener('click', () => {
    setState('someState');
   });
  },[setState])

我猜测这可能与 useMyState.ts 文件中上下文的解构有某种关系

但这感觉有点奇怪和不直观...

我的问题是 setState 是否真的需要依赖数组?如果是,为什么?

my question is is the setState really required inside the dependency array?

不,它不是,但 ESLint 不知道这一点,因为它无法知道您正在使用的上下文对象的 setState 成员是否稳定。 知道(因为 setter 被 useState 保证稳定,你通过上下文和你的 useMyState 钩子逐字传递它), 但 ESLint 不知道。

您可以将其添加为依赖项以使 ESLint 满意(如果您已经提供数组,它不会有任何区别,因为 setter 永远不会改变;如果您 not 提供数组),或者你可以在注释中告诉 ESLint 跳过检查该代码,或者你可以关闭规则(但它 very 容易错过依赖项,所以如果你这样做要小心)。

(如果您没有提供数组 [因为您希望在每次渲染后效果为 运行],添加一个包含 setter 的数组将阻止这种情况发生,所以在这种情况下,您需要选择禁用 ESLint 错误。或者有一些棘手的解决方案,例如使用其中包含 ever-increasing 数值的 ref。:-) )


不过,该代码存在问题。它反复向元素添加新的事件侦听器而不删除它们,因为使用 no 依赖数组,每次组件呈现时都会调用 useEffect 回调,并且您正在创建每次都有一个新的事件处理函数,所以它们会堆积起来。

因此您需要使 elementRef.current 成为依赖项,并且您需要一个清理回调:

const Component = () => {
    const {setState} = useMyState();
  
    useEffect(() => {
        const handler = () => {
            setState("someState");
        };
        const element = elementRef.current;
        // Note −−−−−−−−−−−−−−−−−−^^^^^^^^
        element.addEventListener("click", handler);
        return () => {
            element.removeEventListener("click", handler);
        };
    }, [elementRef.current]); // <== Optionally add `setState` to this

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