引言
随着 React 16.8 版本引入 Hooks,函数组件逐渐成为主流。本文将详细介绍如何将类组件的生命周期方法迁移到函数组件的 Hooks,帮助开发者更好地理解和使用现代 React 开发模式。
生命周期对应关系总览
类组件生命周期 | 函数组件 Hooks | 执行时机 |
---|---|---|
componentDidMount |
useEffect(() => {}, []) |
组件挂载后执行一次 |
componentWillUnmount |
useEffect(() => { return () => {} }, []) |
组件卸载前执行清理 |
componentDidUpdate |
useEffect(() => {}, [deps]) |
依赖变化时执行 |
componentDidCatch |
暂无直接对应 | 错误边界处理 |
详细迁移指南
1. componentDidMount → useEffect (空依赖)
类组件写法:
jsx
class MyComponent extends React.Component {
componentDidMount() {
console.log('组件挂载完成');
this.fetchData();
}
fetchData() {
// 获取数据逻辑
}
}
函数组件 Hooks 写法:
jsx
const MyComponent = () => {
useEffect(() => {
console.log('组件挂载完成');
fetchData();
}, []); // 空依赖数组,只执行一次
const fetchData = () => {
// 获取数据逻辑
};
return <div>组件内容</div>;
};
关键点:
- 空依赖数组
[]
确保只在组件挂载时执行一次 - 相当于
componentDidMount
的替代方案
2. componentWillUnmount → useEffect 清理函数
类组件写法:
jsx
class MyComponent extends React.Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
console.log('组件即将卸载,清理定时器');
}
}
函数组件 Hooks 写法:
jsx
const MyComponent = () => {
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
// 返回清理函数,相当于 componentWillUnmount
return () => {
clearInterval(timer);
console.log('组件即将卸载,清理定时器');
};
}, []);
return <div>组件内容</div>;
};
关键点:
useEffect
返回的函数会在组件卸载前执行- 用于清理定时器、事件监听器、取消请求等
- 确保资源不会泄漏
3. componentDidUpdate → useEffect (带依赖)
类组件写法:
jsx
class MyComponent extends React.Component {
componentDidUpdate(prevProps, prevState) {
if (prevProps.userId !== this.props.userId) {
console.log('用户ID变化,重新获取数据');
this.fetchUserData(this.props.userId);
}
if (prevState.count !== this.state.count) {
console.log('计数变化');
}
}
}
函数组件 Hooks 写法:
jsx
const MyComponent = ({ userId }) => {
const [count, setCount] = useState(0);
// 监听 userId 变化
useEffect(() => {
console.log('用户ID变化,重新获取数据');
fetchUserData(userId);
}, [userId]);
// 监听 count 变化
useEffect(() => {
console.log('计数变化');
}, [count]);
return <div>组件内容</div>;
};
关键点:
- 依赖数组中的值变化时会触发执行
- 可以针对不同依赖分别使用
useEffect
- 比类组件的
componentDidUpdate
更精确
实际应用场景
场景 1:数据获取和清理
jsx
const UserProfile = ({ userId }) => {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
let isCancelled = false;
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!isCancelled) {
setUserData(data);
}
} catch (error) {
if (!isCancelled) {
console.error('获取用户数据失败:', error);
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchUser();
// 清理函数:取消未完成的请求
return () => {
isCancelled = true;
};
}, [userId]);
if (loading) return <div>加载中...</div>;
if (!userData) return <div>暂无数据</div>;
return <div>{userData.name}</div>;
};
场景 2:事件监听器管理
jsx
const WindowResizeHandler = () => {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
// 清理函数:移除事件监听器
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<div>
窗口尺寸: {windowSize.width} x {windowSize.height}
</div>
);
};
场景 3:定时器管理
jsx
const CountdownTimer = ({ initialTime }) => {
const [time, setTime] = useState(initialTime);
useEffect(() => {
if (time <= 0) return;
const timer = setInterval(() => {
setTime(prev => prev - 1);
}, 1000);
// 清理函数:清除定时器
return () => {
clearInterval(timer);
};
}, [time]);
return <div>倒计时: {time}秒</div>;
};
最佳实践
1. 依赖数组的合理使用
jsx
// ✅ 正确:明确指定依赖
useEffect(() => {
fetchData(userId, page);
}, [userId, page]);
// ❌ 错误:缺少依赖,可能导致闭包问题
useEffect(() => {
fetchData(userId, page);
}, []);
// ❌ 错误:依赖过多,可能导致不必要的执行
useEffect(() => {
fetchData(userId, page);
}, [userId, page, fetchData]); // fetchData 应该用 useCallback 包装
2. 使用 useCallback 优化函数依赖
jsx
const MyComponent = ({ userId }) => {
const fetchData = useCallback(async () => {
// 获取数据逻辑
}, [userId]);
useEffect(() => {
fetchData();
}, [fetchData]); // fetchData 变化时重新执行
return <div>组件内容</div>;
};
3. 使用 useMemo 优化计算
jsx
const MyComponent = ({ items }) => {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
useEffect(() => {
console.log('计算结果:', expensiveValue);
}, [expensiveValue]);
return <div>{expensiveValue}</div>;
};
常见陷阱和解决方案
陷阱 1:无限循环
jsx
// ❌ 错误:会导致无限循环
useEffect(() => {
setCount(count + 1);
}, [count]);
// ✅ 正确:使用函数式更新
useEffect(() => {
setCount(prev => prev + 1);
}, []); // 只在挂载时执行一次
陷阱 2:闭包问题
jsx
// ❌ 错误:闭包捕获旧值
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 总是打印 0
}, 1000);
return () => clearInterval(timer);
}, []);
// ✅ 正确:使用 useRef 或函数式更新
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const timer = setInterval(() => {
console.log(countRef.current); // 打印最新值
}, 1000);
return () => clearInterval(timer);
}, []);
总结
通过使用 useEffect
Hook,我们可以优雅地替代类组件的生命周期方法:
componentDidMount
→useEffect(() => {}, [])
componentWillUnmount
→useEffect(() => { return () => {} }, [])
componentDidUpdate
→useEffect(() => {}, [deps])
这种迁移不仅让代码更简洁,还提供了更精确的控制和更好的性能。掌握这些模式,你将能够更有效地使用现代 React 开发模式,构建更高质量的应用程序。
记住,Hooks 的核心思想是"关注点分离"------每个 useEffect
专注于一个特定的副作用,这样代码更容易理解和维护。