如何使用 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>;
}
将空数组作为第二个参数传递给 useEffect
。这有效地告诉 React,引用 docs:
This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
这是一个片段,您可以运行证明它有效:
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;DR
useEffect(yourCallback, [])
- 只会在第一次渲染后触发回调。
详细解释
useEffect
运行 默认情况下,在组件的 每个 渲染之后(从而产生效果)。
在你的组件中放置 useEffect
时,你告诉 React 你想要 运行 回调作为效果。 React 将 运行 渲染后和执行 DOM 更新后的效果。
如果您只传递一个回调 - 回调将在每次渲染后 运行。
如果传递第二个参数(数组),React 将 运行 在第一次渲染后以及每次数组中的一个元素发生更改时回调。例如,当放置 useEffect(() => console.log('hello'), [someVar, someOtherVar])
时 - 回调将在第一次渲染后 运行 和任何渲染后 someVar
或 someOtherVar
中的一个发生更改。
通过向第二个参数传递一个空数组,React 将在每次渲染数组后进行比较,并且会发现没有任何变化,因此仅在第一次渲染后调用回调。
使用MountEffect挂钩
运行 组件挂载后仅执行一次函数是一种常见的模式,它证明自己的钩子隐藏了实现细节。
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()
}, [])
空依赖数组运行s Only Once, on Mount
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);
这是我对 Yasin 的回答的版本。
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 效果不同于旧式 class-life-cycle-methods.
默认效果 运行 在每个渲染周期后,但也有选择退出此行为的选项。要选择退出,您可以定义依赖关系,这意味着只有在对其中一个依赖关系进行更改时才会执行效果。
如果您明确定义效果没有依赖性,则效果 运行 仅在第一个渲染周期后出现一次。
第一个解决方案(使用 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
的引用在每次渲染时都会发生变化,因为函数是destroyed(garbarge-collected) 并创建了一个新函数,但这正是我们不想要的。
我们必须在渲染周期中保持 loadDataOnlyOnce
的相同引用。
所以把函数定义移到上面:
const loadDataOnlyOnce = () => {
console.log("loadDataOnlyOnce");
};
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire only on first render
}, []);
return (...);
}
第三种解决方案(正确的方法,如果依赖依赖于组件)
如果效果的依赖(loadDataOnlyOnce
)依赖于组件(需要 props 或 state),则有 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])
这并不能准确回答您的问题,但可能仅 运行 一次和第一次渲染后的函数具有相同的预期效果。与 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')
};
};
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>;
}
将空数组作为第二个参数传递给 useEffect
。这有效地告诉 React,引用 docs:
This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
这是一个片段,您可以运行证明它有效:
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;DR
useEffect(yourCallback, [])
- 只会在第一次渲染后触发回调。
详细解释
useEffect
运行 默认情况下,在组件的 每个 渲染之后(从而产生效果)。
在你的组件中放置 useEffect
时,你告诉 React 你想要 运行 回调作为效果。 React 将 运行 渲染后和执行 DOM 更新后的效果。
如果您只传递一个回调 - 回调将在每次渲染后 运行。
如果传递第二个参数(数组),React 将 运行 在第一次渲染后以及每次数组中的一个元素发生更改时回调。例如,当放置 useEffect(() => console.log('hello'), [someVar, someOtherVar])
时 - 回调将在第一次渲染后 运行 和任何渲染后 someVar
或 someOtherVar
中的一个发生更改。
通过向第二个参数传递一个空数组,React 将在每次渲染数组后进行比较,并且会发现没有任何变化,因此仅在第一次渲染后调用回调。
使用MountEffect挂钩
运行 组件挂载后仅执行一次函数是一种常见的模式,它证明自己的钩子隐藏了实现细节。
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()
}, [])
空依赖数组运行s Only Once, on Mount
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);
这是我对 Yasin 的回答的版本。
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 效果不同于旧式 class-life-cycle-methods.
默认效果 运行 在每个渲染周期后,但也有选择退出此行为的选项。要选择退出,您可以定义依赖关系,这意味着只有在对其中一个依赖关系进行更改时才会执行效果。
如果您明确定义效果没有依赖性,则效果 运行 仅在第一个渲染周期后出现一次。
第一个解决方案(使用 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
的引用在每次渲染时都会发生变化,因为函数是destroyed(garbarge-collected) 并创建了一个新函数,但这正是我们不想要的。
我们必须在渲染周期中保持 loadDataOnlyOnce
的相同引用。
所以把函数定义移到上面:
const loadDataOnlyOnce = () => {
console.log("loadDataOnlyOnce");
};
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire only on first render
}, []);
return (...);
}
第三种解决方案(正确的方法,如果依赖依赖于组件)
如果效果的依赖(loadDataOnlyOnce
)依赖于组件(需要 props 或 state),则有 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])
这并不能准确回答您的问题,但可能仅 运行 一次和第一次渲染后的函数具有相同的预期效果。与 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')
};
};