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

Dáv*_*nár 133 javascript reactjs react-hooks

useEffect阵营钩将运行在功能上传递的每一个变化.这可以进行优化,只有在所需的属性发生变化时才能调用它.

如果我想调用初始化函数componentDidMount而不是在更改时再调用它会怎么样?假设我想加载一个实体,但加载函数不需要组件中的任何数据.我们怎么能用useEffect钩子做这个?

class MyComponent extends React.PureComponent {
    componentDidMount() {
        loadDataOnlyOnce();
    }
    render() { ... }
}
Run Code Online (Sandbox Code Playgroud)

使用钩子,这可能是这样的:

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

Tho*_*lle 255

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

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

  return <div> {/* ... */} </div>;
}
Run Code Online (Sandbox Code Playgroud)

  • 注意:从 React 18 开始,这将不再有效。这段代码将运行两次。 (13认同)
  • 注意:useEffect 在 React 18 中挂载时运行两次,但仅在开发模式下运行。他们说这是为了帮助调试(但实际上有点令人困惑)。请参阅此处:https://beta.reactjs.org/reference/react/useEffect#usage (11认同)
  • 或者,如果有用于获取数据的参数(例如用户ID),则可以在该数组中传递用户ID,如果更改,则组件将重新获取数据.许多用例都是这样的. (9认同)
  • 这似乎是最简单的答案,但 ESLint 抱怨...请参阅此线程上的其他答案 /sf/answers/3973751841/ (5认同)
  • 是的...有关跳过的更多信息,请参见此处:https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects (4认同)
  • [] 内有变量怎么样?它总是至少运行 2 次。 (2认同)

wua*_*min 92

我们必须停止思考组件生命周期方法(即componentDidMount)。我们必须开始考虑效果。React 效果与旧式的类生命周期方法不同。

默认情况下,效果在每个渲染周期后运行,但可以选择退出此行为。要选择退出,您可以定义依赖关系,这意味着仅当对依赖关系之一进行更改时才会产生效果。

如果您明确定义某个效果没有依赖性,则该效果仅在第一个渲染周期之后运行一次。

第一个解决方案(符合 ESLint)

因此,您的示例的第一个解决方案如下:

function MyComponent() {

    const loadDataOnlyOnce = () => {
      console.log("loadDataOnlyOnce");
    };

    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, []);
    return (...);
}
Run Code Online (Sandbox Code Playgroud)

But then the React Hooks ESLint plugin will complain with something like that:

React Hook useEffect has missing dependency: loadDataOnlyOnce. Either include it or remove the dependency array.

At first this warning seems annoying, but please don't ignore it. It helps you code better and saves you from "stale closures". If you don't know what "stale closures" are, please read this great post.

2nd solution (the right way, if dependency is not dependent on component)

If we add loadDataOnlyOnce to the dependency array, our effect will run after every render-cycle, because the reference of loadDataOnlyOnce changes on every render, because the function is destroyed(garbarge-collected) and a new function is created, but that's exactly what we don't want.

我们必须loadDataOnlyOnce在渲染周期中保持相同的引用。

所以只需移动上面的函数定义即可:

const loadDataOnlyOnce = () => {
  console.log("loadDataOnlyOnce");
};

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, [loadDataOnlyOnce]);
    return (...);
}
Run Code Online (Sandbox Code Playgroud)

通过此更改,您可以确保引用loadDataOnlyOnce will never change. Therefore you can also safely add the reference to the dependency array.

第三种解决方案(正确的方法,如果依赖项依赖于组件)

如果效果(loadDataOnlyOnce)的依赖依赖于组件(需要 props 或 state),则有 React 的内置useCallback-Hook。

-Hook 的基本意义useCallback是在渲染周期期间保持函数的引用相同。

function MyComponent() {
    const [state, setState] = useState("state");

    const loadDataOnlyOnce = useCallback(() => {
      console.log(`I need ${state}!!`);
    }, [state]);

    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only when loadDataOnlyOnce-reference changes
    }, [loadDataOnlyOnce]);
    return (...);
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案值得初学者 React 开发人员更多关注。 (17认同)
  • @JusticeBringer 我想说的不仅仅是初学者。这不是一个简单的概念。 (6认同)
  • 第四个选项。将函数内容直接放入useEffect中。这也消除了“eslint”错误。感谢您顺便解释了这个概念。 (5认同)
  • 可能值得指出的是,对于在开发模式下使用 create-react-app 的任何人来说,即使上面应该运行一次的情况实际上也会运行两次,因为 create-react-app 和严格模型故意双重调用某些函数:https:// stackoverflow.com/a/61091205/334402 (2认同)

Eda*_*rit 52

TL; DR

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

详细说明

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

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

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

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

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


Ben*_*arp 26

useMountEffect挂钩

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

const useMountEffect = (fun) => useEffect(fun, [])
Run Code Online (Sandbox Code Playgroud)

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

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

关于useMountEffect挂钩

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

  • 我不太明白...... `"react-hooks/exhaustive-deps"` 仍然抱怨 `const useMountEffect = (fun) =&gt; useEffect(fun, [])` 中的空数组 (10认同)
  • 这不会“绕过”ESLint 规则 b/c 它仍然会指出 useEffect 有一个依赖项:`fun`。 (9认同)
  • 我非常喜欢您的回答,因为ESLint规则“ react-hooks / exhaustive-deps”将始终在空的依赖项列表上失败。例如,著名的create-react-app模板将执行该规则。 (6认同)
  • 谢谢!尽管我认为这指出了“react-hooks/exhaustive-deps”中的一个缺陷,特别是因为这是在挂载上运行事物的规范方式。这个“解决方案”在功能上将问题从组件转移到其他地方,而不是从根本上用空的部门解决问题。 (4认同)
  • 现在,当效果函数需要 props 中的某些内容但永远不需要再次运行时,您可以使用 useMount,即使该值在没有 linter 警告的情况下发生变化:useEffect(()=&gt;console.log(props.val),[])`将缺少依赖项警告,但 useMount(()=&gt;console.log(props.val)) 不会导致警告,但“确实有效”。我不确定并发模式是否会出现问题。 (3认同)
  • @BenCarp我不是在讨论该函数的意图是什么,我是说实际使用而不将 `fun` 列为依赖项仍然会导致抛出 ESLinting 错误。当然,它可以被忽略或关闭,但我不确定在这个自定义挂钩中这样做与在您传入的函数中这样做有何价值。 (2认同)

Yan*_*Tay 15

传递一个空数组作为第二个参数useEffect.这有效地告诉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'));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


Gno*_*por 15

我在使用 React 18 时遇到了这个问题。我是这样处理的:

import { useEffect, useRef } from "react";

export default function Component() {
    const wasCalled = useRef(false);

    useEffect(() => {
        if(wasCalled.current) return;
        wasCalled.current = true;
            
         /* CODE THAT SHOULD RUN ONCE */

    }, []);

    return <div> content </div>;
}
Run Code Online (Sandbox Code Playgroud)

在这里查看他们如何解释为什么 useEffect 被多次调用。


eco*_*gic 10

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

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

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

Run Code Online (Sandbox Code Playgroud)


Yas*_*glu 10

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

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

并使用它。

useOnceCall(() => {
  console.log('called');
})
Run Code Online (Sandbox Code Playgroud)

或者

useOnceCall(()=>{
  console.log('Fetched Data');
}, isFetched);
Run Code Online (Sandbox Code Playgroud)


Shu*_*man 6

将依赖项数组留空。希望这能帮助您更好地理解。

   useEffect(() => {
      doSomething()
    }, []) 
Run Code Online (Sandbox Code Playgroud)

空依赖项数组仅在 Mount 上运行一次

useEffect(() => {
  doSomething(value)
}, [value])  
Run Code Online (Sandbox Code Playgroud)

value作为依赖传递。如果自上次以来依赖项已更改,则效果将再次运行。

useEffect(() => {
  doSomething(value)
})  
Run Code Online (Sandbox Code Playgroud)

没有依赖性。每次渲染后都会调用此函数。