React Hook useEffect在使用函数时出现了缺失依赖的警告

6

我有一个使用useEffect()钩子的React组件:

const [stateItem, setStateItem] = useState(0);

useEffect(() => {
  if (condition) {
    myFunction();
  }
}, [stateItem]);

const myFunction = () => {
  return 'hello';
}

React提醒我'myFunction'是一个缺失的依赖项。我(认为我)理解为什么会出现这个警告,我已经阅读了许多类似的问题,询问更多或更少相同的事情,但答案总是“将您的函数移到useEffect hook”。如果不考虑myFunction被从不同的地方调用,例如:

...
return (
  <Button onClick={() => myFunction()} />
);

因此,我不能将我的函数放在useEffect钩子内部。
对于类似的问题,一种解决方法是将函数放在组件外部,但这需要我传递大量数据给我的函数,例如 const myFunction(stateItem, setStateItem, someProp) => { stuff }; 当有多个带有许多props、state hooks等的函数时,这变得非常繁琐。
除了在useEffect钩子上方放置linter ignore注释之外,还有什么更实际的做法吗?我发现这些东西使使用React hooks变得非常不实用。

1
你能发布整个组件吗? - CptKicks
将您的函数移到 useEffect 的上方,因为它被声明为const。 - ZiiMakc
@CptChix 这两段代码一起构成了一个完整但简单的组件示例,该组件可能会出现此问题。 - paddotk
@ RTW 这样做很好,不确定这是否是新的 React 支持。 - paddotk
5个回答

5
我遇到了这个问题。
React 一直试图保持你的 effects 最新。如果你没有传递一个依赖数组,React 将在每次渲染后运行该 effect,以防万一。
这将在每次渲染时都运行。
useEffect(()=> {
  // DO SOMETHING
});

如果传递一个空数组,基本上是在告诉效果它不依赖于任何东西,并且只运行一次是安全的。 这将仅运行一次。
useEffect(()=> {
  // DO SOMETHING
},[]);

如果您填充依赖项数组,就是告诉您的 effect 依赖于那些特定内容,如果其中任何一个发生更改,该effect需要再次运行,否则,它不必执行。
这只会在someProp或者someFunction发生变化时运行。
useEffect(()=> {
  // DO SOMETHING
},[someProp,someFuction]);

注意:请记住函数、对象和数组是按引用比较的。

因此,基本上您的选项是:

  • 将函数移动到effect的主体中。
  • 将其添加到依赖数组中

如果选择将其添加到数组中,则需要决定以下内容:

如果该函数被修改,您是否需要重新运行您的effect?

如果是这样的话,只需将其添加到依赖项数组中,React将负责在每次更改该函数时重新运行您的effect。

如果不是这样的话,将您的函数包装到useCallback中,以便您可以在渲染过程中保持其引用不变。您还可以向useCallback添加依赖项数组,以控制何时需要重新创建函数。

额外提示:函数需要重新创建,但您不想重新运行。

  • 使用useRef()添加一些变量来跟踪effect是否已经运行了一次,并将检查写入您的effect中,以便在它之前运行该effect。例如:
const effectHasRun_ref = useRef(false);
useEffect(()=>{
  if (effectHasRun_ref.current === true) {
    return;
  }
  else {
    // RUN YOUR EFFECT
    effectHasRun_ref.current = true;
  }
},[yourFunction]);

1
我对这两件事感到好奇:1)这如何帮助<Button onClick={() => myFunction()/>的例子?2)与使用常规变量相比,这有什么不同之处吗? - paddotk

2

所以你的目标似乎是将函数保留在组件内,且:

  1. 你不想将它移动到 useEffect 中,因为你想在其他地方使用它
  2. 你不想将它移动到函数外部,因为你想避免从组件中传递参数

在这种情况下,我认为最好的解决方案是使用 useCallback 钩子,如下所示:


function YourComponent(props){

  const [stateItem, setStateItem] = useState(0);

  //wrap your logic in useCallback hook
  const myFunction = React.useCallback(
    () => {

      //if you use any dependencies in this function add them to the deps array of useCallback
      //so if any of the dependencies change thats only when the function changes

      return 'hello'
    }, [deps])

    useEffect(() => { 
       if(condition) {
          myFunction();
       }

    //add your function to the dependency array as well
    //the useCallback hook will ensure your function is always constant on every rerender thus you wont have any issues by putting it in the deps array, besides the #1 rule is NEVER LIE ABOUT YOUR DEPENDENCIES
    //the function only changes if the dependencies to useCallback hook change!!
    }, [stateItem, myFunction ])

   return (
     <Button onClick={() => myFunction()} />
   );
}


useCallback钩子将确保您的函数在每次重新渲染时始终保持不变,因此将其放入依赖项数组中不会有任何问题。只有当依赖于useCallback钩子的依赖关系发生更改时,函数才会发生更改。通过这样做,我们遵守了钩子的黄金规则,即永远不要说谎你的依赖关系。希望这可以帮助您。您可能想阅读Dan Abramov的博客文章

0

我建议您不要使用useEffect,因为据我所了解,您想在特定状态更新时调用某个函数。为此,我建议您编写一个自定义函数,在需要更新的地方(例如输入框)调用它。

现在,您可以直接调用此函数并更新状态,因为您知道只有在要更新特定状态时才会调用此函数,然后您可以在之后调用其他函数。

如果您有像changeHandler这样的东西,也可以在其中执行,但我更建议编写自定义函数。

小例子代码:

const [stateItem, setStateItem] = useState(0);

const myFunction = () => {
   // do something
};

const myOtherFunc = (value) => {
   setStateItem(value);

   if (condition) {
      myFunction();
   }
};

我希望我正确理解了你的问题,并且这对你有所帮助。


我的意思是,你可以在状态的onChange函数中调用你的函数,并在其中添加额外的逻辑,这样每当你更新状态时,它就会始终运行,而不需要实际的useEffect。但是,如果你需要在初始渲染时也调用此函数,则我误解了你的问题。否则,像我说的那样,我建议完全删除useEffect,并将你的逻辑放在更改处理程序中(如果有可能)。 - Emre
好的,我明白了,你是指在任何现在有“setStateItem”的地方调用 myOtherFunction 吧。然而,这种方法存在问题,我猜这就是为什么 useEffect 首先存在的原因,也就是 setState 函数是异步运行的。因此,在你的例子中,myFunction 可能会在 setstate 完成更新之前运行。 - paddotk

0
我以前在将函数添加到useEffect的依赖数组中时遇到了问题和错误。我创建了一个简单的自定义钩子来解决JavaScript闭包的问题(在useEffect中的函数需要指向新的对象),我将使用响应式元素和需要保持新鲜的元素分开,只将我想要响应的内容添加到useEffect的依赖数组中,并且仍然可以使用组件中的其他状态/函数,而无需将其添加到useEffect的依赖数组中。

hooks.ts

export const useObjectRef = <T extends any[]>(...objects: T): React.MutableRefObject<T> => {
    const objectsRef = useRef<T>(objects);
    objectsRef.current = objects;
    return objectsRef;
};

ExampleComponent.tsx

export const ExampleComponent: FC = () => {
    const [stateA, setStateA] = useState()
    const [stateB, setStateB] = useState()

    const doSomething = () => {
        // Do something
    }
    
    // Run only on stateB change
    // But still have access to fresh stateA and doSomething function
    const objRef = useObjectRef(stateA, doSomething)
    useEffect(() => {
        const [stateA, doSomething] = objRef.current
        doSomething();
        console.log(stateA);
    }, [objRef, stateB])

    return (
        ... JSX ...
    )
}

也许这会对某人有所帮助 :)

-1

简而言之:像下面这样将myFunction添加到依赖项数组中

React的useEffect有一个叫做依赖项数组的东西,它基本上帮助你和React知道何时重新运行effect。基本上,你应该把所有在effect外定义的东西都放在里面。

在这个effect中,你将stateItem作为这个effect的依赖项,这意味着每次它改变时,React都会重新运行这个effect。现在,正如你可能已经猜到的那样,你正在使用在effect外定义的myFunction,这意味着React应该知道何时更新,因此它是知道的。要解决这个警告,只需将函数作为一个项目放入依赖项数组中,就像这样。

const [stateItem, setStateItem] = useState(0);

useEffect(() => {
  if (condition) {
    myFunction();
  }
}, [stateItem, myFunction]);

const myFunction = () => {
  return 'hello';
}

4
我从警告信息中怀疑这就是React建议的做法。但是,对于函数来说,我想知道是否有意义,因为函数并不会改变/更新,因此跟踪函数只是为了抑制警告而已。 - paddotk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接