装载阶段
装载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
constructor
用处
组件的构造函数,第一个被执行。
若没有显式会有一个默认的构造函数,若显式定义必须在构造函数中执行 super(props),否则无法在构造函数中拿到 this。原因见constructor里面为什么必须写super(props)
getDerivedStateFromProps
static getDerivedStateFromProps(props, state)
用处
装载时,接收到新的 props 或者调用了 setState 和 forceUpdate 时被调用。静态方法,所以不能在这个函数里使用 this。
返回
返回一个对象用来更新当前的 state 对象,不需要更新可以返回 null。
参数
有两个参数,分别指接收到的新参数和当前组件的 state 对象。
使用中经常会碰到一个问题,例如:
class Children extends React.Component {
constructor(props) {
super(props)
this.state = {
num: 0
}
}
handleClick = () => {
this.setState({
num: this.state.num+1 });
};
static getDerivedStateFromProps(props, state) {
if (props.num !== state.num) {
return {
num: props.num
}
}
return null
}
render() {
return (
<div>
<input type='button' value='点击+1' onClick={
this.handleClick} />
<span>{
this.state.num}</span>
</div>
)
}
}
class Fa extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Fragment>
<Children num={
1} />
</Fragment>
);
}
}
无论怎么点击button,num始终为1,不会增加。
原因
在16.4^ 的版本中 setState 和 forceUpdate 也会触发getDerivedStateFromProps,所以当组件内部 state 变化后,就会重新走这个方法,最终一直把 state 值赋值为 props 的值,导致handleClick方法无效。
解决方案
多加一个字段来记录之前的 props 值。
class Children extends React.Component {
constructor(props) {
super(props)
this.state = {
num: 0,
// 增加一个 preNum 记录之前的 props 传来的值
preNum: 0,
}
}
handleClick = () => {
this.setState({
num: this.state.num+1 });
};
static getDerivedStateFromProps(props, state) {
// 跟 state.preNum 进行比较
if (props.num !== state.preNum) {
return {
num: props.num,
preNum: props.Num
}
}
return null
}
render() {
return (
<div>
<input type='button' value='点击+1' onClick={
this.handleClick} />
<span>{
this.state.num}</span>
</div>
)
}
}
class Fa extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Fragment>
<Children num={
1} />
</Fragment>
);
}
}
render
用处
这个函数只做一件事,就是根据状态 state 和属性 props 渲染返回需要渲染的内容;不要在这个函数内做其他业务逻辑。
返回
- React 元素:原生 DOM / React 组件;
- 数组和 Fragment(片段):可以返回多个元素;
- 字符串和数字:被渲染成 DOM 中的 text 节点;
- 布尔值或 null:不会渲染任何东西。
没有调用 setState,props 值也没有变化,是不是组件就不会重新渲染?
如果是父组件重新渲染时,不管传入的 props 有没有变化,都会引起子组件的重新渲染。
在讲这个生命周期函数之前,我们先来探讨两个问题:
setState 函数在任何情况下都会导致组件重新渲染吗?
会。
this.setState({
number: this.state.number})
解决以上两个场景下的重渲染-提升性能
shouldComponentUpdate(nextProps, nextState) ,在重新渲染组件开始前触发的,默认返回 true,我们可以比较 this.props 和 nextProps ,this.state 和 nextState 值是否变化。这个生命周期函数是用来提升速度的。
componentDidMount
用处
组件装载完成调用,可以获取/操作 DOM 节点,比如对 canvas,svg 的操作以及服务器请求等。
注意
在 componentDidMount 中调用 setState 会触发一次额外的渲染,多调一次 render 函数,在开发中避免这样使用带来一定的性能问题,尽量在 constructor 中初始化state 对象。
更新阶段
当组件的 props 改变、组件内部调用 setState/forceUpdate,会触发更新重新渲染,这个过程可能会发生多次。这个阶段会依次调用下面这些方法:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
getDerivedStateFromProps
在更新阶段,无论接收到新的 props,调用了 setState 或者 forceUpdate,这个方法都会被触发。(装载阶段已讲过)
shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)
用处
在重新渲染组件开始前触发的,用来提升速度的,性能优化。
返回
布尔值
render
(装载阶段已介绍)
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
用处
render 之后,componentDidUpdate 之前调用。这个函数必须要和 componentDidUpdate 一起使用。
参数
prevProps 和 prevState,表示更新之前的 props 和 state。
返回
必须要有一个返回值,默认是 null,这个返回值作为第三个参数传给 componentDidUpdate。
处理滚动条示例
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 增加了新的一条聊天消息
// 获取滚动条位置,以便我们之后调整
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// snapshot 是上个生命周期的返回值,当有新消息加入时,调整滚动条位置,使新消息及时显示出来
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={
this.listRef}>{
/* ...contents... */}</div>
);
}
}
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
用处
在 getSnapshotBeforeUpdate 方法之后被调用。在这个方法中我们可以操作 DOM,发起 http 请求,也可以 setState 更新状态,但注意这里使用 setState 要有条件,不然就会陷入死循环。
参数
有三个参数,分别表示更新之前的 props 和 state,以及getSnapshotBeforeUpdate的返回值。
卸载阶段
执行一些清理工作,如清除定时器/事件监听,取消未完成的网络请求等。
- componentWillUnmount
componentWillUnmount
用处
在一个组件被卸载和销毁之前被调用,不能再这个方法中使用 setState,组件一旦被卸载,就不会重新渲染。
错误处理
componentDidCatch
如果 render() 函数抛出错误,则会触发该函数,打印错误日志,并且显示回退的用户界面。
解决了早期的 React 开发中,一个小的组件抛出错误将会破坏整个应用程序的情况。