React 的类组件生命周期模型存在三个核心问题:
useEffect 的诞生带来了革命性的改变:
// 类组件
class Example extends React.Component {
componentDidMount() { /* 初始化 */ }
componentDidUpdate() { /* 更新 */ }
componentWillUnmount() { /* 清理 */ }
}
// 函数组件
function Example() {
useEffect(() => {
// 初始化 + 更新逻辑
return () => { /* 清理逻辑 */ };
}, [dependencies]);
}
阶段 | useEffect 执行时机 | 类组件等价方法 |
---|---|---|
挂载阶段 | DOM 提交后异步执行 | componentDidMount |
更新阶段 | DOM 更新后异步执行 | componentDidUpdate |
卸载阶段 | 清理函数同步执行 | componentWillUnmount |
渲染中断 | 清理函数立即执行 | 无对应机制 |
useEffect(() => {
// 副作用逻辑
const subscription = props.source.subscribe();
// 清理函数
return () => {
subscription.unsubscribe();
};
}, [props.source]); // 依赖数组
依赖形态 | 行为特征 | 适用场景 |
---|---|---|
空数组 [] | 仅挂载时执行 | 初始化操作 |
无依赖 | 每次渲染后执行 | 开发调试(不建议) |
精确依赖 | 依赖变化时执行 | 90% 使用场景 |
动态依赖 | 依赖项在 effect 内计算 | 需要谨慎使用 |
// 动态依赖示例(危险模式)
useEffect(() => {
const timer = setInterval(() => {
console.log(props.count); // 闭包陷阱
}, 1000);
return () => clearInterval(timer);
}, [props.count % 2 === 0]); // 依赖项是动态计算的布尔值
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
const data = await fetch('/api/data');
if (isMounted) {
setData(data);
}
} catch (error) {
if (isMounted) {
setError(error);
}
}
};
fetchData();
return () => {
isMounted = false;
};
}, []);
// 危险!清理函数无法取消 Promise
useEffect(async () => {
const data = await fetch('/api/data');
setData(data);
}, []);
技巧 | 实现方式 | 效果 |
---|---|---|
依赖项扁平化 | 使用基本类型代替对象 | 减少无效触发 |
函数记忆化 | 使用 useCallback 包裹函数 | 保持引用稳定 |
复杂对象处理 | 使用 JSON.stringify 简化比较 | 谨慎使用 |
const fetchData = useCallback(async (params) => {
// 数据获取逻辑
}, [apiEndpoint, authToken]);
useEffect(() => {
fetchData(searchParams);
}, [fetchData, JSON.stringify(searchParams)]);
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
const result = await fetch(`/items/${itemId}`);
if (!didCancel) {
setItem(result);
}
};
fetchData();
return () => {
didCancel = true;
};
}, [itemId]);
// 危险代码:对象字面量导致无限循环
useEffect(() => {
setUser({ ...user, timestamp: Date.now() });
}, [user]); // user 对象每次都是新的引用
// 解决方案:扁平化依赖
useEffect(() => {
setUser(prev => ({ ...prev, timestamp: Date.now() }));
}, [user.id]); // 只依赖必要属性
// .eslintrc.json
{
"rules": {
"react-hooks/exhaustive-deps": "warn"
}
}
泄漏类型 | 典型案例 | 解决方案 |
---|---|---|
未清理订阅 | WebSocket 连接 | 返回清理函数 |
未取消请求 | fetch/axios 请求 | AbortController |
定时器未清除 | setInterval | clearInterval |
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', {
signal: controller.signal
}).then(/* ... */);
return () => {
controller.abort();
};
}, []);
function useAsyncEffect(asyncFn, dependencies) {
useEffect(() => {
let isMounted = true;
let cleanupFn;
const execute = async () => {
try {
cleanupFn = await asyncFn(isMounted);
} catch (error) {
if (isMounted) {
handleError(error);
}
}
};
execute();
return () => {
isMounted = false;
cleanupFn?.();
};
}, dependencies);
}
// 使用示例
useAsyncEffect(
async (isMounted) => {
const data = await fetchData();
if (isMounted()) {
setData(data);
}
return () => { /* 自定义清理逻辑 */ };
},
[id]
);
useEffect(() => {
const startTime = performance.now();
// 业务逻辑...
return () => {
const duration = performance.now() - startTime;
if (duration > 100) {
reportSlowEffect('ComponentName', duration);
}
};
}, [dependencies]);
// 提案中的解决方案
const onEvent = useEvent(() => {
// 总是能访问最新 props/state
});
useEffect(() => {
const timer = setInterval(onEvent, 1000);
return () => clearInterval(timer);
}, []); // 依赖项保持稳定
// 外部状态集成
const state = useSyncExternalStore(
store.subscribe,
store.getState
);
useEffect(() => {
// 响应状态变化
}, [state]);
useEffect 的最佳实践原则:
进阶学习路径: