提问人:Dávid Molnár 提问时间:11/2/2018 最后编辑:YilmazDávid Molnár 更新时间:11/18/2023 访问量:613832
如何使用 React useEffect 仅调用一次加载函数
How to call loading function with React useEffect only once
问:
useEffect React 钩子将在每次更改时运行传入的函数。可以对此进行优化,使其仅在所需属性更改时调用。
如果我想从中调用初始化函数,而不是在更改时再次调用它,该怎么办?假设我想加载一个实体,但加载函数不需要来自组件的任何数据。我们怎样才能使用钩子来做到这一点?componentDidMount
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>;
}
评论
将空数组作为第二个参数传递给 。这有效地告诉 React,引用文档:useEffect
这告诉 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/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
TL;博士
useEffect(yourCallback, [])
- 仅在第一次渲染后才会触发回调。
详细说明
useEffect
默认情况下,在组件的每次渲染后运行(从而产生效果)。
当放入你的组件时,你告诉 React 你想将回调作为效果运行。React 将在渲染和执行 DOM 更新后运行效果。useEffect
如果仅传递回调 - 回调将在每次渲染后运行。
如果传递第二个参数(数组),React 将在第一次渲染后以及每次数组中的一个元素发生变化时运行回调。例如,放置时 - 回调将在第一次渲染后运行,并在其中一个渲染后运行。useEffect(() => console.log('hello'), [someVar, someOtherVar])
someVar
someOtherVar
通过向第二个参数传递一个空数组,React 将在每次渲染数组后进行比较,并且会发现没有任何变化,因此仅在第一次渲染后调用回调。
useMountEffect 钩子
在组件挂载后只运行一次函数是一种非常常见的模式,它证明了它自己的钩子是合理的,它隐藏了实现细节。
const useMountEffect = (fun) => useEffect(fun, [])
在任何功能组件中使用它。
function MyComponent() {
useMountEffect(function) // function will run only once after it has mounted.
return <div>...</div>;
}
关于 useMountEffect 钩子
当与第二个数组参数一起使用时,React 将在挂载(初始渲染)和数组中的值更改后运行回调。由于我们传递了一个空数组,因此它只会在挂载后运行。useEffect
评论
useMount
useEffect(()=>console.log(props.val),[])
useMount(()=>console.log(props.val))
"react-hooks/exhaustive-deps"
const useMountEffect = (fun) => useEffect(fun, [])
"react-hooks/exhaustive-deps"
fun
我喜欢定义一个函数,它以同样的方式欺骗 EsLint,我发现它更不言自明。mount
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)
})
无依赖关系。这在每次渲染后都会被调用。
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])
);
我们必须停止在组件生命周期方法(即)。我们必须开始思考效果。React 效果不同于旧式的类生命周期方法。componentDidMount
默认情况下,效果在每个渲染周期后运行,但有一些选项可以选择退出此行为。要选择退出,您可以定义依赖关系,这意味着只有在对其中一个依赖关系进行更改时才会执行效果。
如果显式定义效果没有依赖性,则该效果在第一个渲染周期之后仅运行一次。
第一个解决方案(使用 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
.
乍一看,这个警告似乎很烦人,但请不要忽视它。它可以帮助您更好地编码,并使您免于“过时的闭包”。如果您不知道什么是“陈旧闭包”,请阅读这篇很棒的文章。
第二种解决方案(正确的方法,如果依赖关系不依赖于组件)
如果我们添加到依赖数组中,我们的效果将在每个渲染周期后运行,因为每次渲染时的引用都会发生变化,因为函数被销毁(garbarge-collected)并创建了一个新函数,但这正是我们不想要的。loadDataOnlyOnce
loadDataOnlyOnce
我们必须在渲染周期中保持相同的引用。loadDataOnlyOnce
因此,只需移动上面的函数定义:
const loadDataOnlyOnce = () => {
console.log("loadDataOnlyOnce");
};
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire only on first render
}, [loadDataOnlyOnce]);
return (...);
}
通过此更改,您可以确保 的引用永远不会更改。因此,您还可以安全地添加对依赖项数组的引用。loadDataOnlyOnce
第三种解决方案(正确的方法,如果依赖关系依赖于组件)
如果效果 () 的依赖关系依赖于组件(需要 props 或 state),则有 React 内置的 -Hook。loadDataOnlyOnce
useCallback
-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 (...);
}
评论
如果你只是在渲染后在useeffect中调用该函数,你可以添加一个空数组作为useeffect的第二个参数
useEffect=(()=>{
functionName(firstName,lastName);
},[firstName,lastName])
这并不能准确回答您的问题,但可能会产生相同的预期效果,即仅在第一次渲染后运行一次函数。与 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
即使用户按下后退按钮导航到页面,window.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')
};
};
我在使用 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>;
}
在这里查看他们如何解释为什么 useEffect 被多次调用。
评论
我在互联网上花了一段时间后才发现。useEffect 在组件挂载时触发一次,然后 componennt 卸载并再次挂载,useEffect 再次触发。你必须在 React 文档上查看更多内容,了解他们为什么要这样做。
所以,我为此使用了自定义钩子。卸载时,您必须更改 useRef 状态。在这种情况下,不要忘记一个 return 语句:当组件卸载时,useEffect 会在返回后运行 cleanup 函数。
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
}, [])
我发现,用这个功能从问题可以简洁而优雅地解决。once
lodash
import { once } from "lodash";
import { useEffect, useRef } from "react";
export const useEffectOnce = (cb: () => void) => {
const callBackOnce = useRef(once(cb)).current;
useEffect(() => callBackOnce(), [callBackOnce]);
};
将来我们会有,看这篇官方的react文章:useEffectEvent
使用一个名为 useEffectEvent 的特殊 Hook 从 Effect 中提取此非反应式逻辑:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
在这里,onConnected 称为 Effect Event。它是效果逻辑的一部分,但它的行为更像一个事件处理程序。它里面的逻辑不是被动的,它总是“看到”你的道具和状态的最新值。
在他们的示例中,即使更改了(或问题情况下的回调),效果事件也不会再次运行(即 没有任何依赖项)。theme
useEffectEvent
因此,要回答这个问题(假设依赖于某些 props/state 或作用域中的其他变量,它们会一次又一次地执行它;否则只需将其放在组件主体之外;针对流控制做出反应建议):loadDataOnlyOnce
useCallback
function MyComponent() {
const loadDataOnlyOnceEvent = useEffectEvent(loadDataOnlyOnce)
useEffect(() => {
loadDataOnlyOnceEvent();
}, []);
return (...);
}
你在useEffect中传递空白数组,所以它运行一次
import React, { useEffect } from "react";
function MyComponent() {
useEffect(() => {
// This code will run only once when the component mounts
}, []); // The empty dependency array means the effect runs only once
return <div>{/* Your component JSX */}</div>;
}
您应该执行以下第一种方式
useEffect 钩子有 3 种使用方式。
在组件将呈现时运行一次:
useEffect(()=>{ // your code },[])
每次运行这些 x1,x2,...依赖项已被查格:
useEffect(()=>{ // your code },[x1,x2,...])
在更新任何内容以及渲染组件时运行:
useEffect(()=>{ // your code })
评论