引言
在 React 函数式组件的开发进程里,useEffect 堪称极为关键的钩子,它赋予我们处理副作用操作的强大且灵活的手段。副作用操作于前端开发场景中随处可见,诸如数据获取、DOM 操作、事件监听等。接下来,本文将深度剖析 useEffect 的运行原理、使用场景以及常见的易错点,助力你全方位掌握这一核心要点。
什么是 useEffect?
useEffect 是 React 提供的 Hook 函数,其能让我们在函数式组件内执行副作用操作。这里所说的副作用,指的是与组件渲染本身无直接关联的操作,具体涵盖:
- 数据获取(AJAX 请求)
- DOM 操作(手动修改样式、添加事件监听等)
- 定时器或 interval
- 订阅事件
- 清理无效操作
基本语法
useEffect(() => {
// 副作用操作代码
return () => {
// 清理副作用的代码(可选)
};
}, [依赖项数组]); // 依赖项数组是可选的
它接收两个参数:
- 第一个参数为回调函数,其中包含要执行的副作用操作
- 第二个参数是可选的依赖项数组,用于把控副作用的执行时机
依赖项数组的三种情况
依赖项数组是 useEffect 的核心机制,它决定了副作用函数的执行时机。下面通过具体实例来理解三种不同情形。
1. 没有依赖项数组
当不设置依赖项数组时,副作用函数会在每次组件渲染(包括初始渲染和更新渲染)完成后执行。
import { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("副作用函数执行");
});
return (
<div>
<button onClick={() => setCount(count + 1)}>+{count}</button>
</div>
);
}
在此例中,每次点击按钮更新 count 的值,组件便会重新渲染,副作用函数也会随之执行,控制台将持续输出 "副作用函数执行"。
2. 依赖项数组为空
若依赖项数组为空,副作用函数仅会在组件初始挂载时执行一次,后续组件更新时不再执行。
import { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("副作用函数只在挂载时执行一次");
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>+{count}</button>
</div>
);
}
这种用法适用于仅需执行一次的操作,例如数据初始化。
3. 依赖项数组包含特定值
当依赖项数组中包含特定值时,副作用函数会在组件挂载时执行一次,并且在这些依赖项的值发生变化时再次执行。
import { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
useEffect(() => {
console.log("count 变化时执行");
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>+{count}</button>
<button onClick={() => setCount1(count1 + 1)}>+{count1}</button>
</div>
);
}
在此例中,仅当 count 的值发生变化时,副作用函数才会执行,count1 的变化不会触发该函数执行。
数据获取示例
下面通过一个实际的数据获取案例,来看看 useEffect 在实际开发中的具体应用。
import { useEffect, useState } from "react";
const URL = "http://geek.itheima.net/v1_0/channels";
function App() {
const [list, setList] = useState([]);
useEffect(() => {
// 获取频道列表
async function getList() {
await fetch(URL)
.then((res) => res.json())
.then((data) => {
setList(data.data.channels);
});
}
getList();
}, []);
return (
<div>
<h1>频道列表</h1>
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
在此例中,我们在组件挂载时发送 AJAX 请求获取频道列表数据。由于仅需获取一次数据,所以依赖项数组为空。
清理副作用
部分副作用操作会产生需要清理的资源,像定时器、订阅事件等。useEffect 支持我们通过返回一个清理函数来处理此类情况。
定时器清理示例
import { useEffect, useState } from "react";
function Son() {
// 渲染时开启定时器
useEffect(() => {
const timer = setInterval(() => {
console.log("定时器执行中");
}, 1000);
// 卸载时清除定时器
return () => {
clearInterval(timer);
};
}, []);
return <div>这是子组件</div>;
}
function App() {
const [show, setShow] = useState(true);
return (
<div>
{show && <Son />}
<button onClick={() => setShow(false)}>卸载子组件</button>
</div>
);
}
在此例中,Son 组件挂载时设置了定时器,每秒输出一次日志。为避免组件卸载后定时器持续运行造成内存泄漏,我们返回了一个清理函数,在组件卸载时清除定时器。
常见应用场景
1. 数据获取
这是最为常见的使用场景,通常在组件挂载时获取初始化数据 。
2. DOM 操作
我们可以在组件渲染完成后操作 DOM 元素。
import { useEffect, useRef } from "react";
function App() {
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载后聚焦到输入框
inputRef.current.focus();
}, []);
return (
<div>
<input ref={inputRef} type="text" />
</div>
);
}
3. 订阅事件
在组件挂载时订阅事件,卸载时取消订阅。
import { useEffect } from "react";
function App() {
useEffect(() => {
const handleResize = () => {
console.log("窗口大小改变");
};
// 订阅窗口大小改变事件
window.addEventListener("resize", handleResize);
// 卸载时取消订阅
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return <div>调整窗口大小查看效果</div>;
}
注意事项
- 依赖项数组的正确使用:务必确保依赖项数组中涵盖了所有需要监听变化的变量,否则可能致使副作用函数无法按预期执行。
- 避免无限循环:若副作用函数中修改了状态,且该状态又存在于依赖项数组中,可能会引发无限循环。
- 异步操作的处理:尽管可以在 useEffect 中使用异步操作,但要妥善处理异步逻辑,规避竞态条件的出现 。
- 清理函数的必要性:对于那些会产生需清理资源的副作用操作,一定要提供清理函数,防止内存泄漏。
总结
useEffect 是 React 中功能强大且灵活多变的钩子,它为我们在函数式组件中处理副作用操作提供了有效途径。通过合理运用依赖项数组和清理函数,我们能够精准控制副作用函数的执行时机,并确保资源得以正确释放。熟练掌握 useEffect 的使用,对开发高质量的 React 应用来说至关重要。