在 React 的函数组件中,useEffect
Hook 是一个强大且不可或缺的工具。它允许我们处理副作用 (side effects)——那些在组件渲染之外发生的操作。但是,什么时候才是使用 useEffect
的正确时机呢?让我们深入探讨一下!
在 React 的世界里,副作用是指任何在组件渲染周期之外与外部世界交互的操作。常见的副作用包括:
setTimeout
或 setInterval
。useEffect
的目的就是让你在函数组件中也能够执行这些副作用操作。
useEffect
的基本语法JavaScript
import React, { useState, useEffect } from 'react';
function MyComponent({ someProp }) {
const [data, setData] = useState(null);
useEffect(() => {
// 副作用代码在这里执行
console.log('Component mounted or someProp updated!');
// 比如,基于 someProp 获取数据
fetch(`https://api.example.com/data?id=${someProp}`)
.then(response => response.json())
.then(result => setData(result));
// 清理函数 (可选)
return () => {
console.log('Component unmounted or before next effect runs');
// 在这里进行清理,比如取消订阅、清除计时器等
};
}, [someProp]); // 依赖项数组
return {data ? JSON.stringify(data) : 'Loading...'};
}
useEffect
接收两个参数:
useEffect
?主要场景详解如果你希望在组件首次渲染到 DOM 后执行某些操作(例如,获取初始数据、设置事件监听器),可以将依赖项数组设置为空数组 []
。
JavaScript
useEffect(() => {
// 仅在组件挂载后运行一次
console.log('Component has mounted!');
fetchInitialData();
return () => {
// 在组件卸载时运行
console.log('Component will unmount!');
cleanupSubscriptions();
};
}, []); // 空依赖项数组
当你希望在某个特定的 prop
或 state
发生变化时重新运行副作用,你需要将这些 prop
或 state
添加到依赖项数组中。
JavaScript
const [userId, setUserId] = useState(1);
useEffect(() => {
// 当 userId 变化时,重新获取用户数据
console.log(`Fetching data for user: ${userId}`);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setData);
// 如果需要,这里也可以返回一个清理函数
}, [userId]); // 依赖项:userId
重要提示:如果你在 useEffect
内部使用了外部作用域的变量 (props, state, 或组件内部定义的函数),并且希望在这些变量改变时重新运行 effect,那么务必将它们包含在依赖项数组中。否则,你的 effect 可能会捕获到过时的(stale)值,导致难以调试的 bug。
useEffect
返回的函数(我们称之为清理函数 (cleanup function))会在以下两种情况下执行:
JavaScript
useEffect(() => {
const timerId = setInterval(() => {
console.log('Tick');
}, 1000);
// 清理函数:在组件卸载或依赖项变化导致 effect 重新运行前执行
return () => {
clearInterval(timerId);
console.log('Timer cleared');
};
}, []); // 假设这个 timer 只在挂载时设置
如果省略依赖项数组,useEffect
中的副作用函数会在每次组件渲染之后执行。这通常不是你想要的行为,因为它很容易导致性能问题或无限循环(例如,在 effect 内部更新了某个 state,而这个 state 的更新又触发了重新渲染)。
JavaScript
useEffect(() => {
// 每次渲染后都会执行,除非绝对必要,否则请避免
console.log('Component re-rendered');
}); // 没有依赖项数组
经验法则:总是尝试提供依赖项数组。如果你确实需要在每次渲染后运行,请仔细检查逻辑以避免无限循环。ESLint 的 eslint-plugin-react-hooks
插件中的 exhaustive-deps
规则会帮助你检查依赖项数组是否完整。
useEffect
?props
或 state
直接计算出来,那么不需要 useEffect
。直接在组件渲染逻辑中计算即可。 JavaScript // 不好 ❌
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
// 好 ✅
const fullName = `${firstName} ${lastName}`;
useEffect
。useEffect
更适合响应 props
或 state
的 变化 而不是直接的用户交互。
useEffect
关键点:
[]
:仅在挂载和卸载时运行。[dep1, dep2]
:在挂载时以及任何依赖项发生变化时运行。