React Hooks函数中useState及useEffect出场率算是很高了,今天聊一下useEffect使用的最佳实践。
使用方法及调用规则
- 每一次渲染后都执行的副作用:传入回调函数,不传依赖数组。
useEffect(callBack)
- 仅在挂载阶段执行一次的副作用:传入回调函数,且这个函数的返回值不是一个函数,同时传入一个空数组。
useEffect(()=>{
// 这里是业务逻辑
}, [])
- 仅在挂载阶段和卸载阶段执行的副作用:传入回调函数,且这个函数的返回值是一个函数,同时传入一个空数组。假如回调函数本身记为 A, 返回的函数记为 B,那么将在挂载阶段执行 A,卸载阶段执行 B。
useEffect(()=>{
// 这里是 A 的业务逻辑
// 返回一个函数记为 B
return ()=>{
}
}, [])
这里需要注意,这种调用方式之所以会在卸载阶段去触发 B 函数的逻辑,是由 useEffect 的执行规则决定的:useEffect 回调中返回的函数被称为“清除函数”,当 React 识别到清除函数时,会在调用新的 effect 逻辑之前执行清除函数内部的逻辑。这个规律不会受第二个参数或者其他因素的影响,只要你在 useEffect 回调中返回了一个函数,它就会被作为清除函数来处理。
- 每一次渲染都触发,且卸载阶段也会被触发的副作用:传入回调函数,且这个函数的返回值是一个函数,同时不传第二个参数.
useEffect(()=>{
// 这里是 A 的业务逻辑
// 返回一个函数记为 B
return ()=>{
}
})
- 根据一定的依赖条件来触发的副作用:传入回调函数,同时传入一个非空的数组,
useEffect(()=>{
// 这是回调函数的业务逻辑
// 若 xxx 是一个函数,则 xxx 会在组件每次因 num1、num2、num3 的改变而重新渲染时被触发
return xxx
}, [num1, num2, num3])
若数组不为空,那么 React 就会在新的一次渲染后去对比前后两次的渲染,查看数组内是否有变量发生了更新(只要有一个数组元素变了,就会被认为更新发生了),并在有更新的前提下去触发 useEffect 中定义的副作用逻辑。
最佳实践
1. 指定依赖项
使用 useEffect 时,指定依赖项非常重要。这样可以确保只有在这些依赖项发生变化时才运行 effect。如果不指定依赖项,effect 将在每次渲染时都运行,这可能会导致性能问题。
以下是指定依赖项的示例:
import { useEffect, useState } from 'react';
function MyComponent(props) {
const [count, setCount] = useState(0);
useEffect(() => {
// 这个 effect 只会在 `count` 发生变化时运行
console.log(`Count changed to ${count}`);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
在这个示例中,effect 只会在 count 状态发生变化时运行。
2. 清理 effect
如果你的 effect 创建了任何资源,例如事件监听器或定时器,那么在组件卸载时清理它们非常重要。这可以防止内存泄漏和其他问题。
以下是清理 effect 的示例:
import { useEffect } from 'react';
function MyComponent(props) {
useEffect(() => {
const intervalId = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
return <div>...</div>;
}
在这个示例中,effect 创建了一个每秒记录 “Tick” 的间隔计时器。effect 还返回了一个清理函数,该函数在组件卸载时清除了间隔计时器。
3. 避免在渲染中执行副作用
当使用 useEffect 时,应该避免在渲染中执行副作用。这是因为渲染阶段应该专注于渲染 UI,而执行副作用可能会导致不必要的重新渲染和其他问题。相反,应该在 useEffect 钩子中执行副作用。这样可以确保它们只在其依赖项发生更改时运行,而不是在每次渲染时都运行。
以下是在 useEffect 中执行副作用的示例:
import { useEffect } from 'react';
function MyComponent(props) {
useEffect(() => {
// This effect will only run when the component mounts
console.log('Component mounted');
// This effect will also run when `props.someProp` changes
console.log(`Some prop changed to ${props.someProp}`);
// This effect will also run when `props.anotherProp` changes
console.log(`Another prop changed to ${props.anotherProp}`);
// This effect will only run when the component unmounts
return () => {
console.log('Component unmounted');
};
}, [props.someProp, props.anotherProp]);
return <div>...</div>;
}
在这个示例中,useEffect 钩子只在组件挂载时运行一次,并在组件卸载时清理。它还在 props.someProp 和 props.anotherProp 更改时运行。这确保了副作用只在其依赖项发生更改时运行,而不是在每次渲染时都运行。
4.将 effect 拆分成多个
如果你的 effect 包含多个不同的逻辑,可以将它们拆分成多个 effect。这样可以更好地组织代码,并使每个 effect 更加专注于单个任务。
以下是将 effect 拆分成多个的示例:
import { useEffect, useState } from 'react';
function MyComponent(props) {
const [count, setCount] = useState(0);
useEffect(() => {
// This effect will only run when `count` changes
console.log(`Count changed to ${count}`);
}, [count]);
useEffect(() => {
// This effect will only run when `props.someProp` changes
console.log(`Some prop changed to ${props.someProp}`);
}, [props.someProp]);
useEffect(() => {
// This effect will only run when `props.anotherProp` changes
console.log(`Another prop changed to ${props.anotherProp}`);
}, [props.anotherProp]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
5. 使用 useCallback 和 useMemo 优化性能
如果你的 effect 依赖于回调函数或计算结果,可以使用 useCallback 和 useMemo 优化性能。这些钩子可以缓存回调函数和计算结果,以避免不必要的重新计算和重新渲染。
以下是使用 useCallback 和 useMemo 的示例:
import { useEffect, useState, useCallback, useMemo } from 'react';
function MyComponent(props) {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
const expensiveValue = useMemo(() => {
// Some expensive computation
}, [dependency1, dependency2]);
}
在上面的示例代码中,我们使用了 useCallback 和 useMemo 来优化性能。useCallback 可以缓存回调函数,避免不必要的重新渲染,而 useMemo 可以缓存计算结果,避免不必要的重新计算。
在 handleClick 回调函数中,我们使用了 useCallback 来缓存回调函数,以避免在每次重新渲染时都创建一个新的回调函数。这样可以提高性能,因为 React 可以比较新旧回调函数是否相等,从而避免不必要的重新渲染。
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
在 expensiveValue 中,我们使用了 useMemo 来缓存计算结果,以避免在每次重新渲染时都重新计算。这样可以提高性能,因为计算结果只需要在依赖项发生变化时才重新计算。
const expensiveValue = useMemo(() => {
// Some expensive computation
}, [dependency1, dependency2]);
需要注意的是,useCallback 和 useMemo 都需要传入一个依赖项数组,用于指定哪些变量的变化会导致回调函数或计算结果的重新计算。如果依赖项数组中的任何一个变量发生变化,那么回调函数或计算结果都会被重新计算。如果依赖项数组为空,那么回调函数或计算结果只会在组件挂载和卸载时被计算一次。
原文链接:https://juejin.cn/post/7218392277918711864 作者:wayne214