react生命周期-各阶段详解

装载阶段

装载阶段组件被创建,然后组件实例插入到 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 渲染返回需要渲染的内容;不要在这个函数内做其他业务逻辑。

返回

  1. React 元素:原生 DOM / React 组件;
  2. 数组和 Fragment(片段):可以返回多个元素;
  3. 字符串和数字:被渲染成 DOM 中的 text 节点;
  4. 布尔值或 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 开发中,一个小的组件抛出错误将会破坏整个应用程序的情况。

猜你喜欢

转载自blog.csdn.net/m0_38073011/article/details/115232928