跳到主要内容

问题

一、父子组件生命周期执行顺序


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 架构下的异步渲染, 导致了 componentWillMountcomponentWillReceivePropscomponentWillUpdate 三个生命周期在实际渲染之前可能会被调用多次,产生不可预料的调用结果。因此这三个生命周期函数不建议被使用,取而代之的是使用全新的两个生命周期函数 getDerivedStateFromPropsgetSnapshotBeforeUpdate

详细原因:

Fiber 架构的重要特征就是可以被打断的异步渲染模式。但这个打断是有原则的,根据能否被打断这一标准,React 16 的生命周期被划分为了 rendercommit 两个阶段,而 commit 阶段又被细分为了 pre-commitcommit。看下三个阶段各自有哪些特征:

  1. render 阶段: 纯净且没有副作用,可能会被 React 暂停、终止或重新启动

  2. pre-commit 阶段: 可以读取 DOM

  3. commit 阶段: 可以使用 DOM,运行副作用,安排更新

总的来说, render 阶段在执行过程中允许被打断,而 commit 阶段则总是同步执行的。

为什么这样设计呢?简单来说,由于 render 阶段的操作对用户来说其实是不可见的,所以就算打断再重启,对用户来说也是零感知。而 commit 阶段的操作则涉及真实 DOM 的渲染,所以这个过程必须用同步渲染来求稳。

Fiber 机制下,render 阶段是允许暂停、终止和重启的。当一个任务执行到一半被打断后,下一次渲染线程抢回主动权时,这个任务被重启的形式是重复执行一遍整个任务而非接着上次执行到的那行代码往下走。这就导致 render 阶段的生命周期都是有可能被重复执行的。componentWillMountcomponentWillUpdatecomponentWillReceiveProps 这三个生命周期都处于 render 阶段, 都可能重复被执行,而且由于这些 API 常年被滥用,它们在重复执行的过程中都存在着不可小觑的风险

总的来说,React 16 改造生命周期的主要动机是为了配合 Fiber 架构带来的异步渲染机制。在这个改造的过程中,React 团队精益求精,针对生命周期中长期被滥用的部分推行了具有强制性的最佳实践。这一系列的工作做下来,首先是确保了 Fiber 机制下数据和视图的安全性,同时也确保了生命周期方法的行为更加纯粹、可控、可预测。

三、从 React 16.3.0 开始如下三个生命周期钩子被标记为 UNSAFE


React16.3.0 开始如下三个生命周期钩子被标记为UNSAFE

  • componentWillMount

  • componentWillReceiveProps

  • componentWillUpdate

究其原因,有如下两点:

  1. 这三个钩子经常被错误使用,并且现在出现了更好的替代方案(这里指新增的getDerivedStateFromPropsgetSnapshotBeforeUpdate
  • getSnapshotBeforeUpdate: 在commit阶段内的before mutation阶段调用的,由于commit阶段是同步的,所以不会遇到多次调用的问题
  1. ReactLegacy模式迁移到Concurrent模式后,这些钩子的表现会和之前不一致。究其原因,是因为Stack Reconciler重构为Fiber Reconciler后,render阶段的任务可能中断/重新开始,对应的组件在render阶段的生命周期钩子(即componentWillXXX)可能触发多次