如何使用 React useEffect 调用加载函数一次

IT技术 javascript reactjs react-hooks
2021-01-20 21:38:02

useEffect阵营钩将运行在功能上传递的每一个变化。这可以优化为仅当所需的属性发生变化时才调用。

如果我想调用初始化函数componentDidMount而不是在更改时再次调用它怎么办?假设我想加载一个实体,但加载函数不需要来自组件的任何数据。我们如何使用useEffect钩子来做到这一点

class MyComponent extends React.PureComponent {
    componentDidMount() {
        loadDataOnlyOnce();
    }
    render() { ... }
}

使用钩子,这可能如下所示:

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire on every change :(
    }, [...???]);
    return (...);
}
6个回答

如果您只想useEffect在初始渲染后运行给定的函数,您可以给它一个空数组作为第二个参数。

function MyComponent() {
  useEffect(() => {
    loadDataOnlyOnce();
  }, []);

  return <div> {/* ... */} </div>;
}
这似乎是最简单的答案,但 ESLint 抱怨......请参阅此线程上的其他答案stackoverflow.com/a/56767883/1550587
2021-03-15 21:38:02
是的……更多关于跳过的信息记录在这里:reactjs.org/docs/……
2021-03-23 21:38:02
不,因为当 loadDataOnlyOnce 改变时(不是在这个例子中,但 lint 无论如何都不会抱怨非局部变量),它会重新运行效果。解决方案是为钩子创建一个单独的函数,就像这里的另一个答案一样(有效地愚弄 ESLint),或者useRef在第一次运行后设置一个布尔值,如果设置了就不再运行。
2021-03-30 21:38:02
只需将 loadDataOnlyOnce 传递到依赖项数组中即可。那样有用吗?
2021-04-02 21:38:02
或者,如果有用于获取数据的参数(例如用户 ID),您可以在该数组中传递用户 ID,如果它发生更改,组件将重新获取数据。许多用例将像这样工作。
2021-04-03 21:38:02

TL; 博士

useEffect(yourCallback, []) - 只有在第一次渲染后才会触发回调。

详细说明

useEffect默认情况下在每次渲染组件后运行(从而产生效果)。

当放置useEffect在你的组件中时,你告诉 React 你想要运行回调作为效果。React 将在渲染和执行 DOM 更新后运行效果。

如果您只传递一个回调 - 回调将在每次渲染后运行。

如果传递第二个参数(数组),React 将在第一次渲染之后运行回调,并且每次更改数组中的一个元素。例如,在放置时useEffect(() => console.log('hello'), [someVar, someOtherVar])- 回调将在第一次渲染之后运行,并且在任何渲染之后运行someVarsomeOtherVar更改。

通过向第二个参数传递一个空数组,React 将在每次渲染数组后进行比较,并且不会看到任何更改,因此仅在第一次渲染后调用回调。

使用MountEffect钩子

在组件挂载后只运行一次函数是一种常见的模式,它证明了自己隐藏实现细节的钩子是合理的。

const useMountEffect = (fun) => useEffect(fun, [])

在任何功能组件中使用它。

function MyComponent() {
    useMountEffect(function) // function will run only once after it has mounted. 
    return <div>...</div>;
}

关于 useMountEffect 钩子

useEffect与第二个数组参数一起使用时,React 将在挂载(初始渲染)后和数组中的值更改后运行回调。由于我们传递了一个空数组,它只会在挂载后运行。

现在,您可以useMount在您的效果函数需要来自 props 的某些东西但永远不需要再次运行时使用,即使该值在没有 linteruseEffect(()=>console.log(props.val),[])警告的情况下更改:将缺少依赖项警告但useMount(()=>console.log(props.val))不会导致警告但“确实有效”。不过,我不确定并发模式是否会出现问题。
2021-03-15 21:38:02
完全同意@Dynalon。这应该是公认的解决方案,因为它不会干扰 ESLint 规则
2021-03-25 21:38:02
我非常喜欢你的回答,因为 ESLint 规则“re​​act-hooks/exhaustive-deps”在空依赖列表上总是会失败。例如,著名的 create-react-app 模板将强制执行该规则。
2021-04-02 21:38:02
谢谢!虽然我认为这表明"react-hooks/exhaustive-deps". 这种“解决方案”在功能上将问题从组件移到别处,而不是从根本上解决空 deps 的问题。
2021-04-02 21:38:02
我不太明白......"react-hooks/exhaustive-deps"仍然抱怨中的空数组const useMountEffect = (fun) => useEffect(fun, [])
2021-04-05 21:38:02

将一个空数组作为第二个参数传递给useEffect. 这有效地告诉 React,引用文档

这告诉 React 你的 effect 不依赖于来自 props 或 state 的任何值,所以它永远不需要重新运行。

这是一个片段,您可以运行它以表明它有效:

function App() {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUser(data.results[0]);
      });
  }, []); // Pass empty array to only run once on mount.
  
  return <div>
    {user ? user.name.first : 'Loading...'}
  </div>;
}

ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

function useOnceCall(cb, condition = true) {
  const isCalledRef = React.useRef(false);

  React.useEffect(() => {
    if (condition && !isCalledRef.current) {
      isCalledRef.current = true;
      cb();
    }
  }, [cb, condition]);
}

并使用它。

useOnceCall(() => {
  console.log('called');
})

或者

useOnceCall(()=>{
  console.log('Fetched Data');
}, isFetched);
谢谢!拯救了我的一天。非常适合调用函数一次,但仅在需要加载某些状态之后。
2021-04-11 21:38:02