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

useEffect React钩子将在每次更改时运行传入函数。可以对其进行优化,使其仅在所需属性更改时调用。

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

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

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

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

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

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


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

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

这告诉React,你的效果不依赖于道具或状态的任何值,所以它永远不需要重新运行。

这是一个片段,你可以运行,以显示它的工作:

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>

博士TL;

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

详细解释

useEffect默认在每一个渲染组件后运行(因此会产生影响)。

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

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

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

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

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将在挂载(初始渲染)和数组中的值发生更改后运行回调。因为我们传递了一个空数组,所以它只会在挂载之后运行。

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

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


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


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

   useEffect(() => {
doSomething()
}, [])

空依赖数组只在挂载时运行一次

useEffect(() => {
doSomething(value)
}, [value])

传递value作为依赖项。如果依赖关系自上次以来发生了变化,该效果将再次运行。

useEffect(() => {
doSomething(value)
})

没有依赖。这个在每次渲染后都会被调用。

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);

以下是我对亚辛回答的看法。

import {useEffect, useRef} from 'react';


const useOnceEffect = (effect: () => void) => {
const initialRef = useRef(true);


useEffect(() => {
if (!initialRef.current) {
return;
}
initialRef.current = false;
effect();
}, [effect]);
};


export default useOnceEffect;


用法:

useOnceEffect(
useCallback(() => {
nonHookFunc(deps1, deps2);
}, [deps1, deps2])
);

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

默认情况下,效果在每个渲染周期后运行,但也有选择退出这种行为的选项。若要选择退出,可以定义依赖项,这意味着仅在对其中一个依赖项进行更改时才执行效果。

如果显式地定义一个效果没有依赖关系,则该效果只在第一个渲染循环之后运行一次。

第一个解决方案(带有ESLint-complaint)

所以,你的例子的第一个解决方案是:

function MyComponent() {


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


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

但是React Hooks ESLint插件会抱怨这样的事情:

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

起初,这个警告似乎很烦人,但请不要忽视它。它可以帮助你更好地编写代码,并将你从“陈旧的闭包”中拯救出来。如果你不知道什么“陈腐的结束语”;,请阅读这个伟大的帖子

第二种解决方案(正确的方法,如果依赖关系不依赖于组件)

如果我们将loadDataOnlyOnce添加到依赖数组中,我们的效果将在每次渲染循环后运行,因为loadDataOnlyOnce的引用在每次渲染时都会改变,因为函数被销毁(垃圾回收)并创建一个新函数,但这正是我们不想要的。

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

所以只要把函数定义移到上面:

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


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

通过这个改变,你可以确保loadDataOnlyOnce的引用永远不会改变。因此,您还可以安全地将引用添加到依赖项数组中。

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

如果效果的依赖关系(loadDataOnlyOnce)依赖于组件(需要道具或状态),则React内置useCallback-Hook。

useCallback-Hook的基本含义是在呈现循环期间保持函数的引用相同。

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 (...);
}

如果你只是在渲染后调用useeffect中的函数,你添加一个空数组作为useeffect的第二个参数

useEffect=(()=>{
functionName(firstName,lastName);
},[firstName,lastName])

calling a custom hooks

adding a empty array for rendering

这并没有完全回答你的问题,但可能会产生只运行一次函数的预期效果,并且是在第一次呈现之后。非常类似于componentDidMount函数。使用useState而不是useEffect来避免依赖lint错误。您只需将一个自动执行的匿名函数作为第一个参数传递给useState。说句题外话,我不确定为什么React不简单地提供一个钩子来做这件事。

import React, { useState } from "react"


const Component = () => {


useState((() => {
console.log('componentDidMountHook...')
}))


return (
<div className='component'>Component</div>
)
}


export default Component

窗口。即使用户按下返回按钮导航到页面,Onpageshow也能工作,这与传递一个空数组作为use-effect钩子的第二个参数不同,后者在通过返回按钮返回页面时不会触发(因此不是在每一种形式的初始页面加载上)。

 useEffect(() => {
window.onpageshow = async function() {
setSomeState(false)
let results = await AsyncFunction()
console.log(results, 'Fires on on first load,
refresh, or coming to the page via the back button')
};
};

如果你正在使用带有[]依赖的useEffect,请确保你的组件没有被包装到<React.StrictMode />

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

import { useEffect, useRef } from "react";


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


useEffect(() => {
if(isRunned.current) return;
isRunned.current = true;
            

/* CODE THAT SHOULD RUN ONCE */


}, []);


return <div> content </div>;
}

在这里检查他们如何解释为什么useEffect被调用不止一次

我在网上花了一段时间后发现的。useEffect在组件挂载时触发一次,然后组件卸载并再次挂载,useEffect再次触发。你得多看看React文档,他们为什么这么做。

我使用了自定义钩子。卸载时,你必须改变你的useRef状态。在这种情况下,不要忘记返回语句:当组件卸载时,useEffect在返回后运行清理函数。

import React, { useEffect, useRef } from "react"


const useDidMountEffect = (
func: () => void,
deps: React.DependencyList | undefined
) => {
const didMount = useRef(false)


useEffect(() => {
if (didMount.current) {
func()
}
return () => {
didMount.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps)
}


export default useDidMountEffect

使用它像正常的useEffect:

  useDidMountEffect(() => {
// your action
}, [])

我发现使用lodash中的once函数可以简洁而优雅地解决这个问题。

import { once } from "lodash";
import { useEffect, useRef } from "react";


export const useEffectOnce = (cb: () => void) => {
const callBackOnce = useRef(once(cb)).current;
useEffect(() => callBackOnce(), [callBackOnce]);
};