React 废弃三个生命周期钩子的原因
解释 React 为何废弃 componentWillMount 等钩子,以及如何用 getDerivedStateFromProps 替代
问题
React 为什么要废弃 componentWillMount、componentWillReceiveProps、componentWillUpdate 这三个生命周期钩子?
解答
废弃时间线
React 16.3 版本:
- 为这三个钩子添加
UNSAFE_前缀 - 引入新的生命周期钩子
getDerivedStateFromProps
React 17.0 及之后:
- 完全删除未加前缀的版本
- 保留
UNSAFE_前缀的版本
废弃原因
React 的更新流程分为 render 阶段和 commit 阶段。这三个被废弃的生命周期钩子都在 render 阶段执行。
在 fiber 架构引入之前,render 阶段不可中断。页面复杂时会阻塞渲染。fiber 架构允许低优先级任务的 render 阶段被高优先级任务打断,这带来了新问题:render 阶段的生命周期函数可能被执行多次。
如果在这些钩子中执行副作用操作(如发送网络请求),就会导致同一请求被执行多次:
class MyComponent extends React.Component {
// ❌ 不推荐:可能被执行多次
componentWillMount() {
fetch('/api/data').then(/* ... */);
}
// ❌ 不推荐:可能被执行多次
componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
fetch(`/api/data/${nextProps.id}`).then(/* ... */);
}
}
}
替代方案
React 引入静态方法 getDerivedStateFromProps 来替代:
class MyComponent extends React.Component {
// ✅ 推荐:静态方法,无法访问 this
static getDerivedStateFromProps(props, state) {
if (props.id !== state.prevId) {
return {
prevId: props.id,
data: null
};
}
return null;
}
// 副作用操作放在 componentDidMount 和 componentDidUpdate
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.fetchData();
}
}
fetchData() {
fetch(`/api/data/${this.props.id}`).then(/* ... */);
}
}
getDerivedStateFromProps 是静态方法,无法通过 this 访问组件实例,也就无法发送请求或调用 this.setState。这强制开发者在 render 前只做无副作用的操作,避免生命周期滥用。
关键点
- fiber 架构允许 render 阶段被打断,导致 render 阶段的生命周期可能执行多次
- 在可能多次执行的钩子中进行副作用操作(如网络请求)会导致重复执行
getDerivedStateFromProps是静态方法,无法访问实例,强制开发者避免副作用- 副作用操作应放在
componentDidMount和componentDidUpdate中执行
目录