问题
一、父子组件生命周期执行顺序
import React from "react";
import ReactDOM from "react-dom";
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0,
};
console.log("子组件 constructor");
}
static getDerivedStateFromProps() {
console.log("子组件 getDerivedStateFromProps");
return null
}
// componentWillMount() {
// console.log("子组件 componentWillMount");
// }
componentDidMount() {
console.log("子组件 componentDidMount");
}
// componentWillReceiveProps() {
// console.log("子组件 componentWillReceiveProps");
// }
getDerivedStateFromProp() {
console.log("子组件 getDerivedStateFromProp");
}
shouldComponentUpdate() {
console.log("子组件 shouldComponentUpdate");
}
// componentWillUpdate() {
// console.log("子组件 componentWillUpdate");
// }
getSnapshotBeforeUpdate() {
console.log("子组件 getSnapshotBeforeUpdate");
}
componentDidUpdate() {
console.log("子组件 componentDidUpdate");
}
componentWillUnmount() {
console.log("子组件 componentWillUnmount");
}
render() {
console.log("子组件 render");
return <div>Child组件</div>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0,
};
console.log("父组件 constructor");
}
static getDerivedStateFromProps() {
console.log("父组件 getDerivedStateFromProps");
return null
}
// componentWillMount() {
// console.log("父组件 componentWillMount");
// }
componentDidMount() {
console.log("父组件 componentDidMount");
}
// componentWillReceiveProps() {
// console.log("父组件 componentWillReceiveProps");
// }
getDerivedStateFromProp() {
console.log("父组件 getDerivedStateFromProp");
return null
}
shouldComponentUpdate() {
console.log("父组件 shouldComponentUpdate");
}
// componentWillUpdate() {
// console.log("父组件 componentWillUpdate");
// }
getSnapshotBeforeUpdate() {
console.log("父组件 getSnapshotBeforeUpdate");
}
componentDidUpdate() {
console.log("父组件 componentDidUpdate");
}
componentWillUnmount() {
console.log("父组件 componentWillUnmount");
}
render() {
console.log("父组件 render");
return (
<div>
App组件
<Child />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
执行顺序原则如下: 父组件 render
阶段 -> 子组件 render
阶段 -> 父组件 commit
阶段 -> 子组件 commit
阶段
执行结果如下: 父组件 constructor
-> 父组件 getDerivedStateFromProps
-> 父组件 render
-> 子组件 constructor
-> 子组件 getDerivedStateFromProps
-> 子组件 render
-> 子组件 componentDidMount
-> 父组件 componentDidMount
二、React Fiber 架构的出现影响了哪些生命周期?
React Fiber
架构实现了 Fiber
将渲染模式从原来不可中断的同步任务改为可中断的异步任务, 任务单元之间的任意切换和任务之间的打断和恢复等等。Fiber
架构下的异步渲染, 导致了 componentWillMount
、componentWillReceiveProps
、 componentWillUpdate
三个生命周期在实际渲染之前可能会被调用多次,产生不可预料的调用结果。因此这三个生命周期函数不建议被使用,取而代之的是使用全新的两个生命周期函数 getDerivedStateFromProps
和 getSnapshotBeforeUpdate
详细原因:
Fiber
架构的重要特征就是可以被打断的异步渲染模式。但这个打断是有原则的,根据能否被打断这一标准,React 16
的生命周期被划分为了 render
和 commit
两个阶段,而 commit
阶段又被细分为了 pre-commit
和 commit
。看下三个阶段各自有哪些特征:
-
render
阶段: 纯净且没有副作用,可能会被React
暂停、终止或重新启动 -
pre-commit
阶段: 可以读取DOM
-
commit
阶段: 可以使用 DOM,运行副作用,安排更新
总的来说, render
阶段在执行过程中允许被打断,而 commit
阶段则总是同步执行的。
为什么这样设计呢?简单来说,由于 render
阶段的操作对用户来说其实是不可见的,所以就算打断再重启,对用户来说也是零感知。而 commit
阶段的操作则涉及真实 DOM
的渲染,所以这个过程必须用同步渲染来求稳。
在 Fiber
机制下,render
阶段是允许暂停、终止和重启的。当一个任务执行到一半被打断后,下一次渲染线程抢回主动权时,这个任务被重启的形式是重复执行一遍整个任务而非接着上次执行到的那行代码往下走。这就导致 render
阶段的生命周期都是有可能被重复执行的。componentWillMount
、componentWillUpdate
、componentWillReceiveProps
这三个生命周期都处于 render
阶段, 都可能重复被执行,而且由于这些 API
常年被滥用,它们在重复执行的过程中都存在着不可小觑的风险
总的来说,React 16
改造生命周期的主要动机是为了配合 Fiber
架构带来的异步渲染机制。在这个改造的过程中,React
团队精益求精,针对生命周期中长期被滥用的部分推行了具有强制性的最佳实践。这一系列的工作做下来,首先是确保了 Fiber
机制下数据和视图的安全性,同时也确保了生命周期方法的行为更加纯粹、可控、可预测。
三、从 React 16.3.0 开始如下三个生命周期钩子被标记为 UNSAFE
从 React16.3.0
开始如下三个生命周期钩子被标记为UNSAFE
-
componentWillMount
-
componentWillReceiveProps
-
componentWillUpdate
究其原因,有如下两点:
- 这三个钩子经常被错误使用,并且现在出现了更好的替代方案(这里指新增的
getDerivedStateFromProps
与getSnapshotBeforeUpdate
)
- getSnapshotBeforeUpdate: 在
commit
阶段内的before mutation
阶段调用的,由于commit
阶段是同步的,所以不会遇到多次调用的问题
React
从Legacy
模式迁移到Concurrent
模式后,这些钩子的表现会和之前不一致。究其原因,是因为Stack Reconciler
重构为Fiber Reconciler
后,render
阶段的任务可能中断/重新开始,对应的组件在render
阶段的生命周期钩子(即componentWillXXX
)可能触发多次