[英]How to call loading function with React useEffect only once
useEffect React hook 将在每次更改时运行传入的 function。 这可以优化为仅在所需属性更改时才调用。
如果我想从componentDidMount
调用初始化 function 并且不在更改时再次调用它怎么办? 假设我想加载一个实体,但加载 function 不需要来自组件的任何数据。 我们如何使用useEffect
钩子来做到这一点?
class MyComponent extends React.PureComponent {
componentDidMount() {
loadDataOnlyOnce();
}
render() { ... }
}
使用钩子,这可能看起来像这样:
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire on every change :(
}, [...???]);
return (...);
}
如果你只想在初始渲染后运行给useEffect
的函数,你可以给它一个空数组作为第二个参数。
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce();
}, []);
return <div> {/* ... */} </div>;
}
TL;博士
useEffect(yourCallback, [])
- 只会在第一次渲染后触发回调。
详细解释
useEffect
默认在每次渲染组件后运行(从而产生效果)。
在你的组件中放置useEffect
时,你告诉 React 你想将回调作为一个效果运行。 React 将在渲染后和执行 DOM 更新后运行效果。
如果您只传递一个回调 - 回调将在每次渲染后运行。
如果传递第二个参数(数组),React 将在第一次渲染之后以及每次更改数组中的一个元素时运行回调。 例如,当放置useEffect(() => console.log('hello'), [someVar, someOtherVar])
时 - 回调将在第一次渲染之后运行,并且在someVar
或someOtherVar
中的一个被更改的任何渲染之后运行。
通过将第二个参数传递给一个空数组,React 将在每次渲染后比较该数组,并且会发现没有任何变化,因此仅在第一次渲染后调用回调。
在组件挂载后只运行一次函数是一种常见的模式,它证明了它自己的一个隐藏实现细节的钩子是合理的。
const useMountEffect = (fun) => useEffect(fun, [])
在任何功能组件中使用它。
function MyComponent() {
useMountEffect(function) // function will run only once after it has mounted.
return <div>...</div>;
}
关于 useMountEffect 挂钩
当使用带有第二个数组参数的useEffect
时,React 将在挂载(初始渲染)和数组中的值更改后运行回调。 由于我们传递了一个空数组,它只会在挂载后运行。
我们必须停止思考组件生命周期方法(即componentDidMount
)。 我们必须开始思考效果。 React 效果不同于旧式的类生命周期方法。
默认情况下,效果会在每个渲染周期后运行,但可以选择退出此行为。 要选择退出,您可以定义依赖关系,这意味着仅在对依赖关系之一进行更改时才会执行效果。
如果您明确定义效果没有依赖关系,则效果仅在第一个渲染周期之后运行一次。
第一种解决方案(带有 ESLint 投诉)
因此,您的示例的第一个解决方案如下:
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
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
}, []);
return (...);
}
第三种解决方案(正确的方法,如果依赖项依赖于组件)
如果效果的依赖( 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
。 这有效地告诉 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>
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);
我喜欢定义一个mount
函数,它以与 useMount 相同的方式useMount
,我发现它更不言自明。
const mount = () => {
console.log('mounted')
// ...
const unmount = () => {
console.log('unmounted')
// ...
}
return unmount
}
useEffect(mount, [])
将依赖项数组留空。 希望这将帮助您更好地理解。
useEffect(() => {
doSomething()
}, [])
空依赖数组仅在挂载上运行一次
useEffect(() => {
doSomething(value)
}, [value])
将value
作为依赖项传递。 如果自上次以来依赖项发生了变化,则效果将再次运行。
useEffect(() => {
doSomething(value)
})
没有依赖。 每次渲染后都会调用它。
useEffect React挂钩将在每次更改时运行传入的函数。 可以对其进行优化,使其仅在所需属性更改时才调用。
如果我想从componentDidMount
调用初始化函数而不在更改时再次调用该怎么办? 假设我要加载一个实体,但是加载功能不需要组件中的任何数据。 我们如何使用useEffect
挂钩做到这一点?
class MyComponent extends React.PureComponent {
componentDidMount() {
loadDataOnlyOnce();
}
render() { ... }
}
使用钩子可能看起来像这样:
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire on every change :(
}, [...???]);
return (...);
}
这是我对亚辛答案的看法。
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 函数。 这使用 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
如果您将useEffect
与[]
依赖项一起使用,请确保您的组件没有被包装到<React.StrictMode />
中
即使用户按下后退按钮导航到页面,window.onpageshow 也可以工作,这与传递一个空数组作为使用效果挂钩的第二个参数不同,后者在通过后退按钮返回页面时不会触发(因此并非在每个初始页面加载的形式)。
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')
};
};
我在使用 React 18 时遇到了这个问题。我是这样处理的:
import { useEffect, useRef } from "react";
export default function Component() {
const isRunned = useRef(false);
useEffect(() => {
!isRunned.current &&
{
/* CODE THAT SHOULD RUN ONCE */
};
return () => {
isRunned.current = true;
};
}, []);
return <div> content </div>;
}
我在网上花了一段时间后才发现。 useEffect 在组件挂载时触发一次,然后组件卸载并再次挂载,useEffect 再次触发。 您必须查看更多有关 React 文档的信息,了解他们这样做的原因。
所以,我为此使用了自定义钩子。 卸载时,您必须更改您的 useRef state。 在这种情况下不要忘记返回语句:当组件卸载时,useEffect 在返回后运行清理 function。
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
}, [])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.