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


205

useEffect阵营钩将运行在功能上传递的每一个变化。可以对其进行优化,使其仅在所需属性更改时才调用。

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

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

使用钩子可能看起来像这样:

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

Answers:


387

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

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

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

27
或者,如果有用于获取数据的参数(例如,用户ID),则可以在该数组中传递用户ID,如果更改,则组件将重新获取数据。许多用例将像这样工作。
trixn

4
是的...更多关于跳绳记录在这里:reactjs.org/docs/...
Melounek

这似乎是最简单的答案,但ESLint抱怨...参见此线程上的其他答案stackoverflow.com/a/56767883/1550587
Simon Hutchison

85

TL; DR

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

详细说明

useEffect默认情况下,在每次渲染组件后运行(因此会产生效果)。

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

如果仅传递回调,则该回调将在每次渲染后运行。

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

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


68

useMountEffect挂钩

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

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

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

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

关于useMountEffect挂钩

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


19
我高度希望您的回答,因为ESLint规则“ react-hooks / exhaustive-deps”在空的依赖项列表上始终会失败。例如,著名的create-react-app模板将执行该规则。
Dynalon

1
完全同意@Dynalon。这应该是公认的解决方案,因为它不会干扰ESLint规则
Mikado68 '19

谢谢@Dynalon和Mikado68 :-)。该决定是OP的特权。我收到了有关您的评论的通知,但没有。您可以通过直接评论该问题来向他建议。
本·卡尔

2
现在,您可以useMount在效果函数需要props的某些东西而无需再次运行的情况下使用它,即使该值在没有linter warnig的情况下发生更改:useEffect(()=>console.log(props.val),[])将会丢失依赖项警告,但useMount(()=>console.log(props.val))不会引起警告,但会“起作用”。我不确定并发模式是否会出现问题。
HMR

1
我喜欢这个。ESLint规则不会对此有所抱怨,而且它的名称比空数组更容易理解
Frexuz

20

将空数组作为第二个参数传递给useEffect。引用docs可以有效地告诉React :

这告诉React,您的效果不依赖于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>


4

我喜欢定义一个mount函数,它以相同的方式欺骗​​EsLint useMount,我发现它更加不言自明。

const mount = () => {
  console.log('mounted')
  // ...

  const unmount = () => {
    console.log('unmounted')
    // ...
  }
  return unmount
}
useEffect(mount, [])

0

诀窍是useEffect需要第二个参数。

第二个参数是一组变量,组件将在重新渲染之前检查这些变量以确保其已更改。您可以在此处放置要检查的道具和状态的任何内容。

或者,什么也不放:

import React, { useEffect } from 'react';

function App() {
  useEffect(() => {

    // Run! Like go get some data from an API.

  }, []); //Empty array as second argument

  return (
    <div>
      {/* Do something with data. */}
    </div>
  );
}

这样可以确保useEffect仅运行一次。

从文档中注意:

如果使用此优化,请确保数组包含组件作用域中所有随时间变化且被效果使用的值(例如props和state)。否则,您的代码将引用先前渲染中的陈旧值。


3
如果您从CSS技巧上按字面意思进行复制/粘贴,那么您至少可以做的就是记下您的来源。 css-tricks.com/run-useeffect-only-once
牛气冲天
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.