共计 14871 个字符,预计需要花费 38 分钟才能阅读完成。
在 React 中,对于每一次由状态扭转导致页面视图的扭转,都会经验两个阶段:render 阶段
、commit 阶段
。
只有 class 组件才有生命周期,因为 class 组件会创立对应的实例,而函数组件不会。组件实例从被创立到被销毁的过程称为 组件的生命周期。
由 class 组件创立的实例具备生命周期,它的 render 函数在 render 阶段执行,并在此阶段进行 DOM 节点的 diff(diff 算法就是在此阶段进行的),找出须要扭转的 DOM 操作。而后在 commit 阶段将对应的 DOM 操作提交至视图中。
而 class 组件实例的所有生命周期函数,都会在 render 阶段和 commit 阶段执行。
注:红色为 React 17 曾经废除的生命周期钩子,绿色为新增的生命周期钩子
在首次渲染页面时,会调用 Mount 相干生命周期钩子;在之后的页面渲染中,会调用 Update 相干生命周期钩子。所以与 Mount 相干的生命周期钩子只会被调用一次。
render 阶段
render 阶段会执行泛滥生命周期钩子,例如:在首次渲染时执行 constructor、getDerivedStateFromProps、componentWillMount、render,在更新时执行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render,在渲染阶段捕捉到了后辈组件中的谬误时会执行 getDerivedStateFromError。
接下来,看看这些生命周期钩子的调用机会,以及它们的作用。
constructor
该办法只会执行一次,调用该办法会返回一个组件实例。
在初始化阶段执行,可间接对 this.state
赋值。其余生命周期函数中只能通过 this.setState
批改 state,不能间接为 this.state
赋值。
应用场景:
个别在 constructor 中做一些组件的初始化工作,例如:初始化组件的 state。
componentWillReceiveProps
在已挂载组件接管到新的 props 之前调用。你能够在这个函数中比拟新旧 props,并依据新旧 props 更改 state。然而它会 毁坏 props 数据的繁多数据源。
在首次渲染组件时,不会调用此生命周期钩子;应用 this.setState
触发组件更新时,也不会调用此生命周期钩子。
不过要 留神:如果是父组件渲染导致了组件的从新渲染,即便传给该组件的 props 没变,该组件中的这个生命周期函数也会被调用。
咱们个别不应用此生命周期函数,因为它通常会毁坏数据源的单一性。
getDerivedStateFromProps
它是一个 静态方法,接管 props 和 state 两个参数。它会在调用 render 办法之前被调用,不论是在初始挂载时还是在后续组件更新时都会被调用。
它的调用机会和 componentWillMount、componentWillUpdate、componentWillReceiveProps 一样都是在 render 办法被调用之前,它能够作为 componentWillMount、componentWillUpdate 和 componentWillReceiveProps 的代替计划。
当然,它的作用不止如此,它能够返回一个对象,用来更新 state,就像它的名字一样,从 props 中获取衍生的 state。如果不须要更新 state 则能够返回 null。
须要 留神 的是:这个生命周期函数是类的 静态方法,并不是原型中的办法,所以在其外部应用 this 拜访到的不是组件实例。
此生命周期钩子不罕用,如果能够的话,咱们也尽可能不会应用它。
对于为什么要废除 componentWillMount、componentWillUpdate、componentWillReceiveProps 三个生命周期钩子而新增 getDerivedStateFromProps 生命周期钩子,前面会阐明起因。参考 React 实战视频解说:进入学习
shouldComponentUpdate
在组件筹备更新之前调用,然而首次渲染或者应用 forceUpdate 函数时不会被调用。跟它的名字一样,它用来判断一个组件是否应该更新。
默认状况下,当组件的 props 或者 state 变动时,都会导致组件更新。它在 render 办法之前执行,如果它的返回值为 false,则不会更新组件,也不会执行前面的 render 办法。
它接管两个参数,nextProps 和 nextState,即下一次更新的 props 和下一次更新的 state。咱们能够将 this.props
和 nextProps 比拟,以及将 this.state
与 nextState 比拟,并返回 false,让组件跳过更新。不过 留神:它并不会阻止子组件因为 state 扭转而导致的更新。
应用场景:
这个生命周期办法通常用来做性能优化。
componentWillMount(UNSAFE)
在组件挂载至 DOM 之前调用,并且只会调用一次。它在 render 办法之前调用,因而在 componentWillMount 中调用 this.setState
不会触发额定的渲染。
这个生命周期钩子应用频率较小,因为咱们个别在 constructor 中 初始化 state,在 componentDidMount 中 引入副作用 或者 订阅内容。
componentWillUpdate(UNSAFE)
在组件行将更新之前执行,如果 shouldComponentUpdate 函数返回 false,则不会调用 componentWillUpdate 办法。
这个生命周期钩子和 componentWillMount 相似,执行的机会是雷同的,只不过 componentWillMount 在组件首次渲染时执行,而 componentWillUpdate 在组件后续更新时执行。这两个生命周期函数都不常常应用。
render
render 办法是类组件中惟一必须实现的办法,它的返回值将作为页面渲染的视图。render 函数应该为纯函数,也就是对于雷同的 state 和 props,它总是返回雷同的渲染后果。
render 函数被调用时,会返回以下四种类型之一:
- React 元素:通常为 JSX 语法。例如:
<div />
、<MyComponent>
等等。 - 数组或者 fragments:render 办法能够通过数组返回多个元素。
- Portals:渲染子节点至不同的子树中。
- 字符串或者数值:会作为文本节点被渲染。
- boolean 类型或者 null:什么都不渲染。
须要 留神 的是:如果 shouldComponentUpdate 生命周期钩子返回 false,则 render 办法(render 阶段后续生命周期钩子)不会执行。
commit 阶段
commit 阶段在首次渲染时会执行 componentDidMount,在组件更新时会执行 getSnapshotBeforeUpdate 和 componentDidUpdate。
componentDidMount
该生命周期办法会在组件挂载之后执行,也只会执行一次,也就是将组件对应的 DOM 插入 DOM 树中之后调用。它会在浏览器更新视图之前调用,如果在 componentDidMount 中 间接调用 this.setState
,它会触发额定的渲染,会再一次调用 render 函数,然而浏览器中视图的更新只会执行一次。
应用场景:
依赖于 DOM 的初始化 操作应该放在这里,此外,咱们个别在这个生命周期办法中 发送网络申请 、 增加订阅 等。
getSnapshotBeforeUpdate
此生命周期函数在最近一次渲染提交至 DOM 树之前执行,此时 DOM 树还未扭转,咱们能够在这里获取 DOM 扭转前的信息,例如:更新前 DOM 的滚动地位。
它接管两个参数,别离是:prevProps、prevState,上一个状态的 props 和上一个状态的 state。它的返回值将会传递给 componentDidUpdate 生命周期钩子的第三个参数。
应用场景:
须要 获取更新前 DOM 的信息 时。例如:须要以非凡形式解决滚动地位的聊天线程等。
componentDidUpdate
在组件更新后立刻调用,首次渲染不会调用该办法。它的执行机会和 componentDidMount 统一,只是 componentDidMount 在首次渲染时调用,而 componentDidUpdate 在后续的组件更新时调用。能够在这个生命周期中间接调用 this.setState
,然而必须包裹在一个条件语句中,否则会导致死循环。
componentDidUpdate 接管三个参数,别离是 prevProps、prevState、snapshot,即:前一个状态的 props,前一个状态的 state、getSnapshotBeforeUpdate 的返回值。
如果组件实现了 getSnapshotBeforeUpdate 生命周期函数,则 getSnapshotBeforeUpdate 的返回值将作为 componentDidUpdate 的第三个参数。
应用场景:
在这个生命周期办法中,能够 对 DOM 进行操作 或者进行 网络申请。
componentWillUnmount
这个生命周期函数会在组件卸载以及销毁之前调用。
应用场景:
通常用来执行组件的 清理操作,例如:革除 timer、勾销网络申请、革除订阅等。
为什么废除三个生命周期函数
React 在 16.3 版本中:
- 将 componentWillMount、componentWillReceiveProps、componentWillUpdate 三个生命周期钩子加上了 UNSAFE 前缀,变为 UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。
- 并引入了一个新的生命周期钩子:getDerivedStateFromProps。
并在 17.0 以及之后的版本中:
- 删除了 componentWillMount、componentWillReceiveProps、componentWillUpdate 这三个生命周期钩子。
- 不过 UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate 还是能够用的。
为什么 要废除这三个生命周期钩子?它们有哪些问题呢?React 又是 如何解决 的呢?
咱们晓得 React 的更新流程分为:render 阶段和 commit 阶段。componentWillMount、componentWillReceiveProps、componentWillUpdate 这三个生命周期钩子都是在 render 阶段执行的。
在 fiber 架构被利用之前,render 阶段是不能被打断的。当页面逐步简单之后,就有可能会阻塞页面的渲染,于是 React 推出了 fiber 架构。在利用 fiber 架构之后,低优先级工作的 render 阶段能够被高优先级工作打断。
而这导致的 问题 就是:在 render 阶段执行的生命周期函数可能被执行屡次。像 componentWillMount、componentWillReceiveProps、componentWillUpdate 这三个生命周期钩子,如果咱们在其中执行一些具备副作用的操作,例如发送网络申请,就有可能导致一个同样的网络申请被执行屡次,这显然不是咱们想看到的。
而 React 又没法强制开发者不去这样做,因为怎么样应用 React 是开发者的自在,所以 React 就新增了一个动态的生命周期 getDerivedStateFromProps,来解决这个问题。
用一个 动态函数 getDerivedStateFromProps 来取代被废除的几个生命周期函数,这样开发者就无奈通过 this 获取到组件的实例,也不能 发送网络申请 以及 调用 this.setState
。它就是强制开发者在 render 之前只做无副作用的操作,间接强制咱们无奈进行这些不合理不标准的操作,从而防止对生命周期的滥用。
父子组件生命周期函数调用程序
接下来咱们来探索一下 React 中父子组件生命周期函数的执行程序。由不同的起因导致的组件渲染,React 会执行不同的生命周期函数。例如:在首次渲染的时候,会执行与 mount 相干的生命周期函数;触发子组件的 this.setState
只会调用子组件中与 update 相干的生命周期函数;触发父组件的 this.setState
则会调用父子组件中与 update 相干的生命周期函数等。
为了探索父子组件以及不同子组件之间生命周期函数的执行程序,我初始化了三个组件别离是父组件 App、子组件 Child1、子组件 Child2。它们的构造如下:
首次渲染
在首次渲染中,咱们探索图中 Mount 阶段生命周期钩子的执行程序。
注:红色为 React 17 曾经废除的生命周期钩子,绿色为新增的生命周期钩子
因为 被废除 的生命周期钩子和 新增 的生命周期钩子不能同时呈现在代码中,所以咱们分状况进行:
旧的生命周期函数调用程序
import React from 'react';
class App extends React.Component {constructor(props) {super(props);
console.log('App constructor');
}
UNSAFE_componentWillMount() {console.log('App componentWillMount');
}
componentDidMount() {console.log('App componentDidMount');
}
render() {console.log('App render');
return (
<div>
<Child order={1} />
<Child order={2} />
</div>
)
}
}
class Child extends React.Component {constructor(props) {super(props);
console.log(`Child${this.props.order} constructor`);
}
UNSAFE_componentWillMount() {console.log(`Child${this.props.order} componentWillMount`);
}
componentDidMount() {console.log(`Child${this.props.order} componentDidMount`);
}
render() {console.log(`Child${this.props.order} render`);
return (
<div>
Child{this.props.order} </div>
)
}
}
export default App;
其执行后果如下:
其中 constructor、componentWillMount、render 为 render
阶段执行的生命周期函数,componentDidMount 为 commit
阶段执行的生命周期函数。
- 首先 顺次 执行父组件 render 阶段的生命周期函数;
- 而后 顺次 执行子组件 render 阶段的生命周期函数;
- 最初 穿插 执行子组件和父组件 commit 阶段的生命周期函数。
React Fiber 树的构建、更新相似于树的先序遍历(深度优先搜寻)。在“递归”时,执行 render 阶段的生命周期函数;在“回溯”时,执行 commit 阶段的生命周期函数。
对于 render 阶段的生命周期函数,其程序是 父组件
-> 子组件
;而对于 commit 阶段的生命周期函数,其程序是 子组件
-> 父组件
。
须要 留神 的是:这里的执行程序并不是真正的树的先序遍历。在“回溯”时,是穿插执行各子组件和父组件 commit 阶段的生命周期函数。
新的生命周期函数调用程序
import React from 'react';
class App extends React.Component {constructor(props) {super(props);
this.state = {count: 0,}
console.log('App constructor');
}
static getDerivedStateFromProps() {console.log('App static getDerivedStateFromProps');
return null;
}
componentDidMount() {console.log('App componentDidMount');
}
render() {console.log('App render');
return (
<div>
<Child order={1} />
<Child order={2} />
</div>
)
}
}
class Child extends React.Component {constructor(props) {super(props);
this.state = {count: 0,};
console.log(`Child${this.props.order} constructor`);
}
static getDerivedStateFromProps(props) {console.log(`Child${props.order} static getDerivedStateFromProps`);
return null;
}
componentDidMount() {console.log(`Child${this.props.order} componentDidMount`);
}
render() {console.log(`Child${this.props.order} render`);
return (
<div>
Child{this.props.order} </div>
)
}
}
export default App;
其执行后果如下:
能够看到,在首次渲染时,getDerivedStateFromProps 的执行程序基本上代替了 componentWillMount 的执行程序。
然而须要 留神 :getDerivedStateFromProps 是一个 静态方法,不能通过 this 获取到组件实例,如果咱们要拿到组件的 props 和 state,必须要通过参数能力拿到。而在 componentWillMount 中,则是通过 this.props 拿到 props。
子组件状态扭转
接下来,咱们别离为父组件和子组件加上 onClick 事件,当点击子组件对应的文字时,让子组件更新,调用其 this.setState
办法,再来看看各生命周期的执行程序。
旧的生命周期函数调用程序
import React from 'react';
class App extends React.Component {constructor(props) {super(props);
this.state = {count: 0,}
console.log('App constructor');
}
UNSAFE_componentWillReceiveProps() {console.log('App componentWillReceiveProps');
}
UNSAFE_componentWillUpdate() {console.log('App componentWillUpdate');
}
shouldComponentUpdate(nextProps) {console.log('App shouldComponentUpdate');
return true;
}
componentDidUpdate() {console.log('App componentDidUpdate');
}
render() {console.log('App render');
return (
<div>
<div onClick={() => this.setState((count) => ({count: count + 1}))}>App</div>
<Child order={1} />
<Child order={2} />
</div>
)
}
}
class Child extends React.Component {constructor(props) {super(props);
this.state = {count: 0,};
console.log(`Child${this.props.order} constructor`);
}
UNSAFE_componentWillReceiveProps(nextProps) {console.log(`Child${nextProps.order} componentWillReceiveProps`);
}
UNSAFE_componentWillUpdate() {console.log(`Child${this.props.order} componentWillUpdate`);
}
componentDidMount() {console.log(`Child${this.props.order} componentDidMount`);
}
shouldComponentUpdate(nextProps) {console.log(`Child${nextProps.order} shouldComponentUpdate`);
return true;
}
componentDidUpdate() {console.log(`Child${this.props.order} componentDidUpdate`);
}
render() {console.log(`Child${this.props.order} render`);
return (<div onClick={() => this.setState((count) => ({count: count + 1}))}> Child{this.props.order} </div>
)
}
}
export default App;
当点击文字 Child1 时,其执行后果如下:
当点击文字 Child2 时,其执行后果如下:
下面的后果中,并没有 执行 componentWillReceiveProps 生命周期函数,因为应用 this.setState
触发组件更新时,并不会调用此生命周期钩子,只有 props 扭转 或者 父组件更新导致子组件从新渲染 时,才会执行这个生命周期钩子,看它的名字也晓得它仅和 props 无关。
- 因为组件的 state 扭转导致组件更新 不会执行
componentWillReceiveProps
; - 执行
shouldComponent
判断组件是否须要更新,须要则执行后续生命周期函数,否则不执行后续生命周期函数; - 在渲染之前,执行
componentWillUpdate
; - 执行渲染办法
render
; - 将更改提交至 DOM 树之后,执行
componentDidUpdate
;
留神:这里没有应用 getSnapshotBeforeUpdate 这个新增的生命周期函数,因为新增的生命周期函数与被废除的生命周期函数同时写入代码中,React 会报错。
新的生命周期函数调用程序
import React from 'react';
class App extends React.Component {constructor(props) {super(props);
this.state = {count: 0,}
console.log('App constructor');
}
static getDerivedStateFromProps() {console.log('App static getDerivedStateFromProps');
return null;
}
shouldComponentUpdate(nextProps) {console.log('App shouldComponentUpdate');
return true;
}
getSnapshotBeforeUpdate(prevProps) {console.log('App getSnapshotBeforeUpdate');
return null;
}
componentDidUpdate() {console.log('App componentDidUpdate');
}
render() {console.log('App render');
return (
<div>
<div onClick={() => this.setState((count) => ({count: count + 1}))}>App</div>
<Child order={1} />
<Child order={2} />
</div>
)
}
}
class Child extends React.Component {constructor(props) {super(props);
this.state = {count: 0,};
console.log(`Child${this.props.order} constructor`);
}
static getDerivedStateFromProps(props) {console.log(`Child${props.order} static getDerivedStateFromProps`);
return null;
}
shouldComponentUpdate(nextProps) {console.log(`Child${nextProps.order} shouldComponentUpdate`);
return true;
}
getSnapshotBeforeUpdate(prevProps) {console.log(`Child${prevProps.order} getSnapshotBeforeUpdate`);
return null;
}
componentDidUpdate() {console.log(`Child${this.props.order} componentDidUpdate`);
}
render() {console.log(`Child${this.props.order} render`);
return (<div onClick={() => this.setState((count) => ({count: count + 1}))}> Child{this.props.order} </div>
)
}
}
export default App;
当点击文字 Child1 时,其执行后果如下:
当点击文字 Child2 时,其执行后果如下:
能够看到,子组件的状态产生扭转,只会执行该子组件对应的生命周期函数,而不会执行其父组件或其兄弟组件的生命周期函数。
- 首先执行
getDerivedStateFromProps
,在这里能够依据 props 更新 state; - 而后判断该组件是否须要更新,即执行
shouldComponentUpdate
; - 须要更新则执行
render
函数以及后续生命周期函数,否则跳过前面生命周期函数的执行; - 在将更改提交至 DOM 树之前执行
getSnapshotBeforeUpdate
,在这里能够获取 DOM 被更改前的最初一次快照; - 最初在将更改提交至 DOM 树之后执行
componentDidUpdate
。
父组件状态扭转
晓得了子组件更新时,生命周期函数的执行程序。咱们点击父组件中对应文字,让其调用父组件的 this.setState
办法,触发父组件和子组件的从新渲染,看看父子组件生命周期函数的执行程序。
旧的生命周期函数调用程序
import React from 'react';
class App extends React.Component {constructor(props) {super(props);
this.state = {count: 0,}
console.log('App constructor');
}
UNSAFE_componentWillReceiveProps() {console.log('App componentWillReceiveProps');
}
UNSAFE_componentWillUpdate() {console.log('App componentWillUpdate');
}
shouldComponentUpdate(nextProps) {console.log('App shouldComponentUpdate');
return true;
}
componentDidUpdate() {console.log('App componentDidUpdate');
}
render() {console.log('App render');
return (
<div>
<div onClick={() => this.setState((count) => ({count: count + 1}))}>App</div>
<Child order={1} />
<Child order={2} />
</div>
)
}
}
class Child extends React.Component {constructor(props) {super(props);
this.state = {count: 0,};
console.log(`Child${this.props.order} constructor`);
}
UNSAFE_componentWillReceiveProps(nextProps) {console.log(`Child${nextProps.order} componentWillReceiveProps`);
}
UNSAFE_componentWillUpdate() {console.log(`Child${this.props.order} componentWillUpdate`);
}
shouldComponentUpdate(nextProps) {console.log(`Child${nextProps.order} shouldComponentUpdate`);
return true;
}
componentDidUpdate() {console.log(`Child${this.props.order} componentDidUpdate`);
}
render() {console.log(`Child${this.props.order} render`);
return (<div onClick={() => this.setState((count) => ({count: count + 1}))}> Child{this.props.order} </div>
)
}
}
export default App;
父子组件生命周期函数调用程序为:
咱们晓得 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 这四个生命周期函数是在 render 阶段 调用的,而 componentDidUpdate 生命周期函数是在 commit 阶段 调用的。
它们的执行程序和 首次渲染 中失去的论断一样,还是满足如下特点:
- 首先 顺次 执行父组件 render 阶段的生命周期函数;
- 而后 顺次 执行子组件 render 阶段的生命周期函数;
- 最初 穿插 执行子组件和父组件 commit 阶段的生命周期函数。
因为是在父组件中调用 this.setState
办法触发的更新,并 不会执行 它的 componentWillReceiveProps 生命周期函数,而因为父组件更新导致的子组件更新,是 会执行 子组件的 componentWillReceiveProps 生命周期函数的,这点也在 子组件状态扭转 中提到了。
新的生命周期函数调用程序
import React from 'react';
class App extends React.Component {constructor(props) {super(props);
this.state = {count: 0,}
console.log('App constructor');
}
static getDerivedStateFromProps() {console.log('App static getDerivedStateFromProps');
return null;
}
shouldComponentUpdate(nextProps) {console.log('App shouldComponentUpdate');
return true;
}
getSnapshotBeforeUpdate(prevProps) {console.log('App getSnapshotBeforeUpdate');
return null;
}
componentDidUpdate() {console.log('App componentDidUpdate');
}
render() {console.log('App render');
return (
<div>
<div onClick={() => this.setState((count) => ({count: count + 1}))}>App</div>
<Child order={1} />
<Child order={2} />
</div>
)
}
}
class Child extends React.Component {constructor(props) {super(props);
this.state = {count: 0,};
console.log(`Child${this.props.order} constructor`);
}
static getDerivedStateFromProps(props) {console.log(`Child${props.order} static getDerivedStateFromProps`);
return null;
}
shouldComponentUpdate(nextProps) {console.log(`Child${nextProps.order} shouldComponentUpdate`);
return true;
}
getSnapshotBeforeUpdate(prevProps) {console.log(`Child${prevProps.order} getSnapshotBeforeUpdate`);
return null;
}
componentDidUpdate() {console.log(`Child${this.props.order} componentDidUpdate`);
}
render() {console.log(`Child${this.props.order} render`);
return (<div onClick={() => this.setState((count) => ({count: count + 1}))}> Child{this.props.order} </div>
)
}
}
export default App;
父子组件生命周期函数调用程序为:
能够看到,换成 getDerivedStateFromProps 后,不论是不是通过调用 this.setState
导致的组件更新,都会执行 getDerivedStateFromProps 生命周期函数。
这里还是合乎后面说的法则:
- 首先 顺次 执行父组件 render 阶段的生命周期函数;
- 而后 顺次 执行子组件 render 阶段的生命周期函数;
- 最初 穿插 执行子组件和父组件 commit 阶段的生命周期函数。
对于其执行程序起因的了解
为什么在 commit 阶段要先执行父组件的 getSnapshotBeforeUpdate,再执行子组件的 componentDidUpdate?而在 render 阶段却是先执行父组件的 render,再执行子组件的 constructor 呢?
因为 getSnapshotBeforeUpdate 是为了获取 DOM 更新前的一次快照,而 componentDidUpdate 是在 DOM 更新之后执行的。天然要在 DOM 更新之前能力获取每一个组件的 DOM 快照,在 DOM 更新之后能力调用 componentDidUpdate。
如果曾经调用了 componentDidUpdate,阐明 DOM 曾经更新完了,此时再调用 getSnapshotBeforeUpdate 还能获取 DOM 更新前的快照吗?显然不行!
至于先执行父组件的 render 再执行子组件的 constructor 是因为:先执行父组件的 render 函数之后,才晓得父组件有哪些子组件,接着能力调用对应子组件的 constructor 去结构子组件。这一切都是如此的正当!