本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出
来我的
GitHub repo 浏览完全的专题文章来我的
个人博客 取得无与伦比的浏览体验
生命周期,望文生义,就是从生到死的历程。
而生命周期钩子,就是从生到死历程当中的症结节点。
普通人的终身有哪些生命周期钩子呢?
- 诞生
- 考上大学
- 第一份事情
- 买房
- 完婚
- 生子
- 孩子的生命周期钩子
- 退休
- 临终遗嘱
每到症结节点,我们总愿望有一些寻思时刻,因为这时刻做出的决议计划会转变人生的走向。
React组件也一样,它会给开发者一些寻思时刻,在这里,开发者可以转变组件的走向。
异步衬着下的生命周期
React花了两年时刻祭出Fiber衬着机制。
简朴来讲,React将diff的历程叫做Reconciliation。之前这一历程是一挥而就的,Fiber机制把它改成了异步。异步妙技将在接下来的版本中逐步解锁。
明显是一段同步代码,怎样就异步了呢?
道理是Fiber把任务切成很小的片,每实行一片就把控制权交还给主线程,待主线程忙完手头的活再来实行剩下的任务。固然假如某一片的实行时刻就很长(比方死循环),那就没主线程什么事了,该崩溃崩溃。
这会给生命周期带来什么影响呢?
影响就是挂载和更新之前的生命周期都变的不可靠了。
为何这么讲?因为Reconciliation这个历程有可以停息然后继续实行,所以挂载和更新之前的生命周期钩子就有可以不实行或许屡次实行,它的表现是不可预期的。
因而16今后的React生命周期迎来了一波大换血,以下生命周期钩子将被逐步烧毁:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
看出特点了么,都是带有will
的钩子。
如今React为这几个生命周期钩子供应了别号,分别是:
- UNSAFE_componentWillMount
- UNSAFE_componentWillReceiveProps
- UNSAFE_componentWillUpdate
React17将只供应别号,完全烧毁这三个大活宝。取这么个别号意义就是让你用着恶心。
constructor()
React借用class类的constructor
充任初始化钩子。
React险些没做什么四肢,然则因为我们只允许经由过程特定的门路给组件通报参数,所以constructor
的参数实际上是被React划定好的。
React划定constructor
有三个参数,分别是props
、context
和updater
。
-
props
是属性,它是不可变的。 -
context
是全局上下文。 -
updater
是包括一些更新要领的对象,this.setState
终究挪用的是this.updater.enqueueSetState
要领,this.forceUpdate
终究挪用的是this.updater.enqueueForceUpdate
要领,所以这些API更多是React内部运用,暴露出来是以备开发者不时之需。
在React中,因为一切class组件都要继续自Component
类或许PureComponent
类,因而和原生class写法一样,要在constructor
里起首挪用super
要领,才取得this
。
constructor
生命周期钩子的最好实践是在这里初始化this.state
。
固然,你也可以运用属性初始化器来替代,以下:
import React, { Component } from 'react';
class App extends Component {
state = {
name: 'biu',
};
}
export default App;
componentWillMount()
💀这是React不再引荐运用的API。
这是组件挂载到DOM之前的生命周期钩子。
很多人会有一个误区:这个钩子是要求数据然后将数据插进去元素一同挂载的最好时机。
实在componentWillMount
和挂载是同步实行的,意味着实行完这个钩子,马上挂载。而向服务器要求数据是异步实行的。所以不管要求怎样快,都要排在同步任务今后再处置惩罚,这是辈份题目。
也就是说,永久不可以在这里将数据插进去元素一同挂载。
并不是说不能在这里要求数据,而是达不到你臆想的结果。
它被烧毁的缘由重要有两点:
- 底本它就没什么用。预计当初是为了成双成对所以才制造了它吧。
- 假如它声清楚明了定时器或许定阅器,在服务端衬着中,
componentWillUnmount
生命周期钩子中的消灭代码不会见效。因为假如组件没有挂载胜利,componentWillUnmount
是不会实行的。姚明说的:没有挂载就没有卸载。 - 在异步衬着中,它的表现不稳定。
初始化this.state
应该在constructor
生命周期钩子中完成,要求数据应该在componentDidMount
生命周期钩子中完成,所以它不仅被烧毁了,连继任者都没有。
static getDerivedStateFromProps(props, state)
👽这是React v16.3.0宣布的API。
起首,这是一个静态要领生命周期钩子。
也就是说,定义的时刻得在要领前加一个static
症结字,或许直接挂载到class类上。
扼要辨别一下实例要领和静态要领:
- 实例要领,挂载在
this
上或许挂载在prototype
上,class类不能直接接见该要领,运用new
症结字实例化今后,实例可以接见该要领。 - 静态要领,直接挂载在class类上,或许运用新的症结字
static
,实例没法直接接见该要领。
题目是,为何getDerivedStateFromProps
生命周期钩子要设想成静态要领呢?
如许开发者就接见不到this
也就是实例了,也就不能在里面挪用实例要领或许setsState了。
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>React</div>
);
}
static getDerivedStateFromProps(props, state) {}
}
export default App;
这个生命周期钩子的任务是依据父组件传来的props按需更新本身的state,这类state叫做衍生state。返回的对象就是要增量更新的state。
它被设想成静态要领的目标是坚持该要领的地道,它就是用来定义衍生state的,除此以外不应该在里面实行任何操纵。
这个生命周期钩子也阅历了一些曲折,底本它是被设想成初始化
、父组件更新
和吸收到props
才会触发,如今只需衬着就会触发,也就是初始化
和更新阶段
都邑触发。
render()
作为一个组件,最中心的功用就是把元素挂载到DOM上,所以render
生命周期钩子是一定会用到的。
render
生命周期钩子怎样吸收模板呢?固然是你return给它。
然则不引荐在return之前写过量的逻辑,假如逻辑过量,可以封装成一个函数。
render() {
// 这里可以写一些逻辑
return (
<div>
<input type="text" />
<button>click</button>
</div>
);
}
注重,万万不要在render
生命周期钩子里挪用this.setState
,因为this.setState
会激发render,这下就没完没了了。主公,有内奸。
componentDidMount()
这是组件挂载到DOM今后的生命周期钩子。
这多是除了render
以外最重要的生命周期钩子,因为这时刻组件的各方面都准备就绪,天地任你闯。
这就是社会哥,人狠话不多。
componentWillReceiveProps(nextProps)
💀这是React不再引荐运用的API。
componentWillReceiveProps
生命周期钩子只要一个参数,更新后的props。
该声明周期函数可以在两种状况下被触发:
- 组件吸收到了新的属性。
- 组件没有收到新的属性,然则因为父组件从新衬着致使当前组件也被从新衬着。
初始化时并不会触发该生命周期钩子。
一样,因为Fiber机制的引入,这个生命周期钩子有可以会屡次触发。
shouldComponentUpdate(nextProps, nextState)
这个生命周期钩子是一个开关,推断是不是须要更新,重要用来优化机能。
有一个破例,假如开发者挪用this.forceUpdate
强迫更新,React组件会疏忽这个钩子。
shouldComponentUpdate
生命周期钩子默许返回true。也就是说,默许状况下,只需组件触发了更新,组件就一定会更新。React把推断的控制权给了开发者。
不过周密的React还供应了一个PureComponent
基类,它与Component
基类的区别是PureComponent
自动完成了一个shouldComponentUpdate
生命周期钩子。
关于组件来讲,只要状况发作转变,才须要从新衬着。所以shouldComponentUpdate
生命周期钩子暴露了两个参数,开发者可以经由过程比较this.props
和nextProps
、this.state
和nextState
来推断状况到底有没有发作转变,再响应的返回true或false。
什么状况下状况没转变,却依旧触发了更新呢?举个例子:
父组件给子组件传了一个值,当父组件状况变化,即使子组件吸收到的值没有变化,子组件也会被迫更新。这显然是异常不合理的,React对此无计可施,只能看开发者的个人造化了。
import React, { Component } from 'react';
import Child from './Child';
class App extends Component {
state = { name: 'React', star: 1 };
render() {
const { name, star } = this.state;
return (
<div>
<Child name={name} />
<div>{star}</div>
<button onClick={this.handle}>click</button>
</div>
);
}
handle = () => {
this.setState(prevState => ({ star: ++prevState.star }));
}
}
export default App;
import React, { Component } from 'react';
class Child extends Component {
render() {
return <h1>{this.props.name}</h1>;
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props === nextProps) {
return false;
} else {
return true;
}
}
}
export default Child;
同时要注重援用范例的坑。
下面这类状况,this.props
和nextProps
永久不可以相称。
import React, { Component } from 'react';
import Child from './Child';
class App extends Component {
state = { name: 'React', star: 1 };
render() {
return (
<div>
<Child name={{ friend: 'Vue' }} />
<div>{this.state.star}</div>
<button onClick={this.handle}>click</button>
</div>
);
}
handle = () => {
this.setState(prevState => ({ star: ++prevState.star }));
}
}
export default App;
import React, { Component } from 'react';
class Child extends Component {
render() {
return <h1>{this.props.friend}</h1>;
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props === nextProps) {
return false;
} else {
return true;
}
}
}
export default Child;
解决要领有两个:
- 比较
this.props.xxx
和nextProps.xxx
。 - 在父组件用一个变量将援用范例缓存起来。
所以this.state
和nextState
是只能用第一种要领比较了,因为React每次更新state都邑返回一个新对象,而不是修正原对象。
componentWillUpdate(nextProps, nextState)
💀这是React不再引荐运用的API。
shouldComponentUpdate
生命周期钩子返回true,或许挪用this.forceUpdate
今后,会马上实行该生命周期钩子。
要特别注重,componentWillUpdate
生命周期钩子每次更新前都邑实行,所以在这里挪用this.setState
异常风险,有可以会没完没了。
一样,因为Fiber机制的引入,这个生命周期钩子有可以会屡次挪用。
getSnapshotBeforeUpdate(prevProps, prevState)
👽这是React v16.3.0宣布的API。
望文生义,保留状况快照用的。
它会在组件行将挂载时挪用,注重,是行将挂载。它以至挪用的比render
还晚,因而可知render
并没有完成挂载操纵,而是举行构建笼统UI的事情。getSnapshotBeforeUpdate
实行完就会马上挪用componentDidUpdate
生命周期钩子。
它是做什么用的呢?有一些状况,比方网页转动位置,我不须要它耐久化,只须要在组件更新今后可以恢复本来的位置即可。
getSnapshotBeforeUpdate
生命周期钩子返回的值会被componentDidUpdate
的第三个参数吸收,我们可以运用这个通道保留一些不须要耐久化的状况,用完即可舍弃。
很显然,它是用来庖代componentWillUpdate
生命周期钩子的。
意义就是说呀,开发者平常用不到它。
componentDidUpdate(nextProps, nextState, snapshot)
这是组件更新今后触发的生命周期钩子。
搭配getSnapshotBeforeUpdate
生命周期钩子运用的时刻,第三个参数是getSnapshotBeforeUpdate
的返回值。
一样的,componentDidUpdate
生命周期钩子每次更新后都邑实行,所以在这里挪用this.setState
也异常风险,有可以会没完没了。
componentWillUnmount()
这是组件卸载之前的生命周期钩子。
为何组件将近卸载了还须要寻思时刻呢?
因为开发者要擦屁股吖。
React的最好实践是,组件中用到的事宜监听器、定阅器、定时器都要在这里烧毁。
固然我说的事宜监听器指的是这类:
componentDidMount() {
document.addEventListener('click', () => {});
}
因为下面这类React会自动烧毁,不劳烦开发者了。
render(
return (
<button onClick={this.handle}>click</button>
);
)
componentDidCatch(error, info)
👽这是React v16.3.0宣布的API。
它重要用来捕捉毛病并举行响应处置惩罚,所以它的用法也比较特别。
定制一个只要componentDidCatch
生命周期钩子的ErrorBoundary
组件,它只做一件事:假如捕捉到毛病,则显现毛病提醒,假如没有捕捉到毛病,则显现子组件。
将须要捕捉毛病的组件作为ErrorBoundary
的子组件衬着,一旦子组件抛出毛病,全部运用依旧不会崩溃,而是被ErrorBoundary
捕捉。
import React, { Component } from 'react';
class ErrorBoundary extends Component {
state = { hasError: false };
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
}
export default ErrorBoundary;
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import MyWidget from './MyWidget';
const App = () => {
return (
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
);
}
export default App;
生命周期
这么多生命周期钩子,实际上总结起来只要三个历程:
- 挂载
- 更新
- 卸载
挂载和卸载只会实行一次,更新会实行屡次。
一个完全的React组件生命周期会顺次挪用以下钩子:
old lifecycle
挂载
- constructor
- componentWillMount
- render
- componentDidMount
更新
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
卸载
- componentWillUnmount
new lifecycle
挂载
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
更新
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
卸载
- componentWillUnmount
组件树生命周期挪用栈
运用首次挂载时,我们以render
和componentDidMount
为例,React起首会挪用根组件的render
钩子,假如有子组件的话,顺次挪用子组件的render
钩子,挪用历程实在就是递归的递次。
等一切组件的render
钩子都递归实行终了,这时刻实行权在末了一个子组件手里,因而最先触发下一轮生命周期钩子,挪用末了一个子组件的componentDidMount
钩子,然后挪用栈顺次往上递归。
组件树的生命周期挪用栈走的是一个Z字形。
假如根组件没有定义A生命周期钩子而子组件定义了,那挪用栈就从这个子组件的A生命周期钩子最先。
别的,只需组件内定义了某个生命周期钩子,即使它没有任何行动,也会实行。
app.render();
child.render();
grandson.render();
// divide
grandson.componentDidMount();
child.componentDidMount();
app.componentDidMount();
// divide
app.render();
child.render();
grandson.render();
// divide
grandson.componentDidUpdate();
child.componentDidUpdate();
app.componentDidUpdate();
固然,componentWillMount、componentWillReceiveProps和componentWillUpdate生命周期钩子有可以被打断实行,也有可以被屡次挪用,表现是不稳定的。所以React决议逐步烧毁它们。
不过相识全部运用生命周期的一般挪用递次,照样有助于明白React的。
React专题一览