useEffect #
一、useEffect基础 #
1.1 什么是副作用 #
副作用是指函数执行过程中对外部世界的操作:
| 副作用类型 | 示例 |
|---|---|
| 数据获取 | API 请求 |
| 订阅 | 事件监听、WebSocket |
| DOM操作 | 直接修改 DOM |
| 定时器 | setTimeout、setInterval |
| 日志 | console.log |
1.2 基本语法 #
javascript
useEffect(() => {
// 副作用逻辑
return () => {
// 清理函数(可选)
};
}, [dependencies]);
1.3 基本用法 #
javascript
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `点击了 ${count} 次`;
}, [count]);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
二、依赖数组 #
2.1 三种依赖形式 #
javascript
function DependencyDemo() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// 1. 无依赖数组:每次渲染后都执行
useEffect(() => {
console.log('每次渲染后执行');
});
// 2. 空数组:仅挂载时执行一次
useEffect(() => {
console.log('组件挂载时执行');
}, []);
// 3. 有依赖:依赖变化时执行
useEffect(() => {
console.log('count变化:', count);
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
}
2.2 依赖数组规则 #
javascript
// ✅ 正确:包含所有依赖
useEffect(() => {
fetchData(userId);
}, [userId]);
// ❌ 错误:缺少依赖
useEffect(() => {
fetchData(userId);
}, []); // userId变化时不会重新执行
// ✅ 使用函数式更新避免依赖
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1); // 不需要依赖count
}, 1000);
return () => clearInterval(timer);
}, []);
2.3 ESLint规则 #
javascript
// 推荐启用 ESLint 规则
// "react-hooks/exhaustive-deps": "warn"
// ESLint 会警告缺失的依赖
useEffect(() => {
console.log(count);
}, []); // ⚠️ React Hook useEffect has a missing dependency: 'count'
三、清理函数 #
3.1 为什么需要清理 #
javascript
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// 清理函数:组件卸载时执行
return () => {
clearInterval(timer);
};
}, []);
return <div>{seconds}秒</div>;
}
3.2 清理时机 #
javascript
useEffect(() => {
console.log('副作用执行');
return () => {
console.log('清理函数执行');
};
}, [count]);
// 执行顺序:
// 1. 组件挂载 → 副作用执行
// 2. count变化 → 清理函数执行 → 副作用执行
// 3. 组件卸载 → 清理函数执行
3.3 常见清理场景 #
javascript
function EventListener() {
useEffect(() => {
const handleResize = () => {
console.log('窗口大小变化');
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>Window Resize Demo</div>;
}
function WebSocketDemo() {
useEffect(() => {
const ws = new WebSocket('ws://example.com');
ws.onmessage = (event) => {
console.log('收到消息:', event.data);
};
return () => {
ws.close();
};
}, []);
return <div>WebSocket Demo</div>;
}
四、常见使用场景 #
4.1 数据获取 #
javascript
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('请求失败');
}
const data = await response.json();
if (isMounted) {
setUser(data);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
return () => {
isMounted = false;
};
}, [userId]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
4.2 事件监听 #
javascript
function MousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return (
<div>
鼠标位置: {position.x}, {position.y}
</div>
);
}
4.3 定时器 #
javascript
function Countdown({ initialSeconds, onComplete }) {
const [seconds, setSeconds] = useState(initialSeconds);
useEffect(() => {
if (seconds <= 0) {
onComplete?.();
return;
}
const timer = setTimeout(() => {
setSeconds(prev => prev - 1);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [seconds, onComplete]);
return <div>{seconds}秒</div>;
}
4.4 本地存储 #
javascript
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
function App() {
const [name, setName] = useLocalStorage('name', '');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="输入名字"
/>
);
}
五、性能优化 #
5.1 避免不必要的执行 #
javascript
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
// 防抖处理
const timer = setTimeout(() => {
search(query).then(setResults);
}, 300);
return () => clearTimeout(timer);
}, [query]);
return <Results data={results} />;
}
5.2 使用useMemo减少依赖 #
javascript
function Chart({ data, options }) {
// 缓存计算结果
const processedData = useMemo(() => {
return data.map(item => transform(item));
}, [data]);
useEffect(() => {
drawChart(processedData, options);
}, [processedData, options]);
return <canvas />;
}
5.3 分离关注点 #
javascript
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
// 分离不同的副作用
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
useEffect(() => {
fetchPosts(userId).then(setPosts);
}, [userId]);
return (
<div>
<UserCard user={user} />
<PostList posts={posts} />
</div>
);
}
六、常见问题 #
6.1 无限循环 #
javascript
// ❌ 无限循环
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // 每次渲染都更新状态
}, [count]);
return <div>{count}</div>;
}
// ✅ 添加条件判断
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count < 10) {
setCount(count + 1);
}
}, [count]);
return <div>{count}</div>;
}
6.2 闭包陷阱 #
javascript
// ❌ 闭包陷阱
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 永远是初始值0
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖导致闭包
return <div>{count}</div>;
}
// ✅ 使用ref解决
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const timer = setInterval(() => {
console.log(countRef.current); // 获取最新值
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>{count}</div>;
}
// ✅ 或者添加依赖
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(timer);
}, [count]);
return <div>{count}</div>;
}
6.3 竞态条件 #
javascript
// ❌ 竞态条件
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
fetchResults(query).then(setResults);
// 如果query快速变化,可能显示旧结果
}, [query]);
return <Results data={results} />;
}
// ✅ 使用清理函数
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
let isCurrent = true;
fetchResults(query).then(data => {
if (isCurrent) {
setResults(data);
}
});
return () => {
isCurrent = false;
};
}, [query]);
return <Results data={results} />;
}
// ✅ 使用AbortController
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
const controller = new AbortController();
fetchResults(query, { signal: controller.signal })
.then(setResults)
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
return () => {
controller.abort();
};
}, [query]);
return <Results data={results} />;
}
七、useEffect模式 #
7.1 挂载时执行 #
javascript
useEffect(() => {
console.log('组件挂载');
return () => {
console.log('组件卸载');
};
}, []);
7.2 更新时执行 #
javascript
useEffect(() => {
console.log('count更新:', count);
}, [count]);
7.3 挂载和更新都执行 #
javascript
useEffect(() => {
console.log('组件渲染');
});
7.4 监听多个依赖 #
javascript
useEffect(() => {
console.log('a或b变化:', a, b);
}, [a, b]);
八、最佳实践 #
8.1 依赖数组完整性 #
javascript
// ✅ 使用 ESLint 规则确保依赖完整
// eslint-disable-next-line react-hooks/exhaustive-deps
8.2 分离副作用 #
javascript
// ✅ 不同职责的副作用分离
useEffect(() => {
// 数据获取
}, [userId]);
useEffect(() => {
// 事件订阅
}, []);
useEffect(() => {
// 文档标题
}, [title]);
8.3 自定义Hook封装 #
javascript
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
fetch(url)
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
setLoading(false);
}
})
.catch(err => {
if (isMounted) {
setError(err);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, [url]);
return { data, loading, error };
}
九、总结 #
| 要点 | 说明 |
|---|---|
| 依赖数组 | 正确设置所有依赖 |
| 清理函数 | 清理订阅、定时器等 |
| 竞态处理 | 使用标志位或AbortController |
| 分离关注点 | 不同副作用分开处理 |
核心原则:
- 确保依赖数组完整
- 及时清理副作用
- 处理好竞态条件
- 避免闭包陷阱
最后更新:2026-03-26