React学习(8)-React中组件的生命周期

前言

为了进一步的了解React的工作过程,已经晓得了怎么编写React组件,知道了React的数据流,那么是时候学习React组件的生命周期了,每个组件都包含生命周期方法,生命周期如同四季更替,一个人的生,老,病,死.在每个特殊的年龄阶段,做这不同的事情

在React编写组件中,同样,每个组件在网页中都有被创建,更新,删除这么一过程,就像有机的生命体一样

理解生命周期函数对于编写React组件代码是非常重要的

如果你不清楚生命周期,以及生命周期的应用场景,那么本篇就是你想要知道的

生命周期(钩子)函数

定义: 在特定的阶段,能够自动执行的函数(方法)

在前面的JSX学习中,一个React元素渲染到页面当中,本质上是通过底层的React.CreateElement的一个方法实现的,它是一个javascript对象,将虚拟DOM转化为真实的DOM,最后通过ReactDOM.render()方法将真实的DOM渲染挂载到对应的页面位置上

一个组件的渲染,经历了以下几个过程:可以对照这个生命周期图谱的


完整生命周期图.png
  • 组件的装载(Mount):把组件第一次在DOM树中渲染的过程
    • componentWillMount:组件即将被挂载,在Render方法之前调用:
    • 应用场景: 常用于组件的启动工作,例如:Ajax数据的获取,定时器的启动,类似Render函数的前哨,调用setState修改状态也不会引起重新绘制,这个时候没有任何渲染,需要注意的是,它可以在服务器端被调用,也可以在浏览器端调用
  • componentDidMount:组件被加载完之后调用,也就是render函数执行之后调用,相当于render函数的后卫,当这个生命周期执行时,render函数会引发渲染,组件重新挂载到DOM树上,注意它只能在浏览器端调用,在服务器端使用React的时候不会调用,装载是将组件渲染,并且构造DOM元素,然后塞入页面的过程,这个状态是不可能在服务器端完成的,服务器端不可能产生DOM树的
    应用场景:我们往往在这个生命周期内进行Ajax的获取,填充组件的内容,因为在componentDidMount被调用时,组件已经挂载到DOM树上了,而往往若需要结合第三方库的使用,例如:JQ等,也是放到这个生命周期函数中进行处理的
  • getSnapshotBeforeUpdate(prevProps, prevState):
    使用场景:该函数在最终render结果提交到DOM之前被调用,记录DOM刷新前的特性,如:滚动位置
    注意:该函数的返回值会作为参数传递给ComponentDidUpdate
  • componentWillUnmount: 当组件对应的 DOM 元素从页面中删除之前调用
  • 组件的更新(update): 当组件被重新渲染的过程(state与props发生改变都会引起渲染)
    • componentWillReceiveProps:
    • shouldComponentUpdate
    • componentWillUpdate
    • componentDidUpdate
  • 组件的卸载(unmount): 组件从DOM中删除的过程
    • componentWillUnmount: 组件从页面销毁时,会触发该函数,当需要对数据进行清理时,例如定时器的清理,放到该函数里面去做

三种不同的过程,React库会依次调用组件的一些成员函数(生命周期函数)

装载过程

当组件第一次被渲染的时候,会依次的调用如下生命周期函数

  • constructor:构造器函数
  • getDerivedStateFromProps(props,state):
    使用场景:当组件内部的state变化依赖于props时,调用该生命周期函数
    注意:不要过度使用该函数,如果你的操作依赖于props的更改并有副作用,最好放到componentDidUpdate中
  • componentWillMount:组件挂载开始之前调用,也就是render函数之前被自动调用,在React16.3版本之后不应该使用,由于该函数在Render函数之前调用,因此使用同步的setState方法不会触发额外的render处理

它也只会在初始化的时候调用一次,所以this坏境的绑定放在这里面也是可以的,但是最好是放在constructor构造器函数里面,如果是处理异步操作或者有副作用的订阅事件处理,例如:Ajax数据获取,则放到componentDidMount中

  • render:组件的渲染,插入到DOM元素中,
  • componentDidMount:组件挂载完之后调用,也就是在render函数之后调用,DOM已经插入到页面中了的,可以在这里使用refs

constructor:构造器函数

constructor(ptops) {
    super(props);  // 一定要调用super,并且接收props参数,否则该组件的实例方法无法获取到外部的props值
}

至于constructor在上节当中已经提及过,并不是每个组件都需要定义constructor构造器函数,函数式(无状态)组件就不需要定义构造函数

一般使用constructor构造函数有如下两种情况

  • 组件内部初始化state,因为生命周期内的任何函数都可能要访问state,取它的值,进行相应的逻辑处理,它是该组件一个私有的对象变量
  • 在对JSX元素上绑定事件监听处理函数时,也就是组件内部成员函数(方法)this坏境的绑定,因为在Es6中类的成员方法在执行时this并不会和类的实例化本身自动的绑定,你需要手动bind的方式进行绑定

为了方便调用,在构造函数中,this就是当前组件的实例,往往在构造函数中将组件实例下的成员方法绑定this为当前的实例对象

constructor(props){
  super(props);
}

this.handleBtnClick = this.handleBtnCLick.bind(this);
this.handleInputChange = this.handleInputChange.bind(this)

在执行了constructor构造器函数后,执行componentWillMount方法,然后在执行render方法,执行完render方法后,在执行componentDidMount方法,整个装载过程就结束了的

当然这其中的一个componentWillUnmount方法是在组件销毁前进行触发,也就是删除DOM元素之前调用,这个常用于当组件从页面删除销毁时,做一些数据清理的时候能用得上,例如定时器的清理,取消网络请求,在该生命周期函数内,不应该调用setState函数,因为该组件销毁后,将不会被重新渲染

具体的实例代码如下所示:

import React, { Fragment, Component } from 'react';      
import ReactDOM from 'react-dom';

class LifeCycle extends Component {
  constructor(props){
    super(props);
    console.log("1-constructor函数被调用执行");
    this.state = {
      isShow: true
    }

    this.handleBtnClick = this.handleBtnClick.bind(this);
    
  }

  componentWillMount(){
    console.log("2-componentWillMount函数已执行,组件挂载之前,在render方法之前调用", this.state.isShow);
  }

  componentDidMount() {
    console.log("4-componentDidMount函数已执行,组件挂载完之后,DOM元素已经插入到页面后调用");
  }

  render(){
    console.log("3-render函数执行");
    return (
      <Fragment>
          <div>
               { this.state.isShow? <Text />:""}
               <button onClick={ this.handleBtnClick }>更改</button>
          </div>
      </Fragment>
    );
  }

  handleBtnClick(){
    this.setState({
      isShow:!this.state.isShow 
    })
  }

  

}

class Text extends Component {
  componentWillUnmount(){
    console.log("componentWillUnmount函数已执行,组件从页面中移除之前调用,Text组件移除");
  }
  render(){
    console.log("Text组件被渲染");
    return (
       <h1>itclanCoder</h1>
    );
  }
} 

const container = document.getElementById('root');

ReactDOM.render(<LifeCycle   />, container);

效果如下所示:


组件的装载过程.gif

大家可以自行将这些生命周期函数放到组件内部当中,进行测试的,看每个生命周期执行的顺序就一目了然了的

说完了组件的装载,那么接下来就是组件的更新了

组件的更新

当props或者state发生改变的时候,就会引起render函数的渲染,也就是会引发组件的更新,它与组件的装载一样,会触发一些生命周期函数

更新组件时:生命周期函数执行的顺序

  • componentWillReceiveProps(nextProps,nextState):只要父组件的render函数被调用,在render函数里面被渲染的子组件就会经历更新的过程,无论父组件传给子组件的props有没有改变,都会触发子组件的componentWillReceiveProps函数

你可以理解为,第一次渲染时,父组件的componentWillReceiveProps函数不会被执行,如果是第二次渲染时,已经存在于父组件中,则该componentWillReceiveProps才会执行

注意:在挂载过程中,React不会针对初始props调用此方法,通过触发setState方法更新过程不会调用这个函数,这是因为这个函数适合根据新的props值(也就是nextProps)来计算出是不是要更新内部状态state
应用场景:当你希望只有在接收新的props时做一些逻辑时,props改变需要相应改变内部state状态时,则使用componentWillReceiveProps,比如:根据父组件传入的数据初始化或重置组件内部的某些state状态

  • shouldComponentUpdate:它决定一个组件什么时候不需要被渲染,在组件更新过程中,Render函数之前调用执行,它同Render函数一样,要求有返回结果的函数

返回一个boolean值,告诉React库这个组件在这次更新过程是否要继续,如果该函数返回true,那么继续更新,调用render函数,反之,若函数返回false,那么立刻停止更新过程,便不会执行render函数了的

这个函数是提高React的性能的,如果发现没必要的渲染,那就干脆不用渲染了的,这个shouldComponentUpdate就可以做到
注意: forceUpdate不会触发该函数,也可以使用PureComponent替代该函数,该函数做了内部的优化

// nextProps表示的是接下来我的props值会样,nextState表示的是我的state会变成什么样
shouldComponentUpdate(nextProps, nextState)
  if(nextProps.props属性 !== this.props.props属性 || nextState.state属性 !== this.state.state属性)
  return true;    
}else{
  return false
}
  • componentWillUpdate: 组件即将更新时调用,在Render函数之前调用
    注意: 不要在该函数中通过this.setState再次改变state,如果需要,则在componentWillReceiveProps函数中改变
  • render:决定该组件UI渲染结果,返回的结果用于构造DOM对象
    注意:不能在render函数中调用setState,如果在shouldComponentUpdate返回false,则render函数不会被调用
  • componentDidUpdate:组件更新完之后执行,有两个参数prevProps和prevState,无论是父组件props的修改还是状态的更改都会触发该方法
    应用场景:如果希望无论props更改还是组件内的状态更改都能触发一些逻辑,则可以使用componentDidUpdate,进行业务处理,发送网络请求
    注意:在处理业务或发送网络请求时,一定要做好条件比较,否则容易造成死循环

组件的卸载

React组件从页面中移除时,在卸载的过程中,只涉及一个生命周期函数componentWillUnmount,由于该函数在组件删除之前会被调用,所以该函数适合做一些清理性的工作
应用场景: 清理无效的timer,取消未完成的网络请求,清理已注册的订阅
注意:在这里使用setState时无效的

当然对于React的生命周期,不同的版本,官方对它做了一些优化和改动,这里介绍的是React Version 16.2.0版本的,生命周期过程图如下所示


React Version16.2.png

如果是最新的,在React17.0版本中,生命周期函数如下所示


React17.0生命周期过程图.png

总结

本文主要讲解了React的生命周期,只要理解了生命周期的图谱,生命周期也就差不多了的,在constructor构造器中初始化工作,componentWillMount在组件即将挂载之前执行调用,常用于组件的启动工作,例如:Ajax数据的获取,定时器的启动

当然数据的请求最好放在componentDidMount函数中,而当props或者state发生改变时,会引起组件的渲染,也就是组件的更新,只要父组件的render函数被渲染了,就会触发子组件的componentWillReceiveProps,而当shouldComponentUpdate的函数返回true时,则render函数会渲染,要是返回false时,则render函数不会渲染

当组件从页面中移除时,在卸载之前会触发componentWillUnmount函数,该函数常常用于组件销毁时调用,清理无效的定时器timer,取消未完成的网络(Ajax)请求,清理已注册的订阅

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容