带你初学React之Class组件生命周期函数(二)

1,223 阅读4分钟

前言

上篇文章已经带你学习了react的引进方式与项目的启动,以及react的元素、组件、父组件向子组件传参props(只能读不能写),以及state(内部数据)的使用;今天主要讲解react的生命周期函数;

生命周期函数(lifecycle)

我们使用js创建并渲染元素的过程如下:

let div=document.createElement('div')
// 这是div的create/construct过程
div.textContent = 'hi'
// 这是初始化state
document.body.appendChild(div);
// 这是div的mount过程
div.textContent = 'h2'
// 这是div的update过程
div.remove()
// 这是div的unmount过程

同样的react组件也是有这些过程,我们称之为生命周期,而生命周期函数就是每个生命阶段所调用的函数;

constructor函数

用途:

  • 初始化props
  • 初始化state,但此时不能使用setState()
    如果不使用state,则可以不写constructor此函数,如果要使用state,则必须要使用constructor此函数
constructor(){
    super();
    this.state = {
      n:0
    }
  }

shouldComponentUpdate函数

用途:

  • 返回true,则不阻止UI更新
  • 返回false,则阻止UI更新
  • 它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新;

我们使用之前的代码做示范

import React from 'react';
class SonChild extends React.Component {
  constructor(){
    super();
    // 初始化
    this.state = {
      n:0
    }
  }
  add = ()=>{
    this.setState(state=>({n:state.n+1}))
    this.setState(state=>({n:state.n-1}))
  }
  render(){
    console.log('render了')
    return(
      <div className='sonChildBag'>我是{this.props.name},我的初始值是{this.state.n}<button onClick={this.add}>+1</button></div>
    )
  }

}
export default  SonChild;

上面的代码,我们点击按钮,n最后的值还是0,所以页面都是0,但是控制台每点击按钮都会打印一次'render了'' 220.gif

这是因为每次setState()里面传入的对象都是一个新对象,对于react来说换了对象就相当于换了数据,就会重新render,render以后发现两次的dom没有区别,最后没有去重新修改页面;但是render的确是多执行了,我们就可以使用shouldComponentUpdate方法来阻止render的执行;

import React from 'react';
class SonChild extends React.Component {
  constructor(){
    super();
    // 初始化
    this.state = {
      n:0
    }
  }
  add = ()=>{
    this.setState(state=>({n:state.n+1}))
    this.setState(state=>({n:state.n-1}))
  }
  shouldComponentUpdate(nextProps, nextState, nextContext) {
    return !(nextState.n === this.state.n)
  }

  render(){
    console.log('render了')
    return(
      <div className='sonChildBag'>我是{this.props.name},我的初始值是{this.state.n}<button onClick={this.add}>+1</button></div>
    )
  }

}
export default  SonChild;

以上代码就阻止了render

阻断过程如下:

React根据我们上面的思想,也做了优化,使用React.PureComponent代替React.Component;PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。 如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。

上面的代码和我们之前使用的shouldComponentUpdate函数效果一样;

render函数

  • 作用:展示视图,用来告诉浏览器组件的样子
render(){
    return (<div></div>)
}
// <div></div>这部分代码不是dom对象,是表示dom的对象,是虚拟dom

我们可以打印出虚拟dom

render(){
    const x = (
      <div className='sonChildBag'>我是{this.props.name},我的初始值是{this.state.n}<button onClick={this.add}>+1</button></div>
    )
    console.log('render了', x)
    return x 
  }

如上图,React把DOM变成了经过处理的对象;

  • 只能有一个根元素
render(){
    const x = (
      <div className='sonChildBag'>我是{this.props.name},我的初始值是{this.state.n}<button onClick={this.add}>+1</button></div>
      <div>我是第二个根元素</div>
    )
    return x
  }

上面代码有两个根元素,运行后会报错;

错误的译文为:JSX元素必须有闭合标签包住,你是想使用fragment<></>吗? 其实根据上面的提示我们可以把代码写成下面形式:

render(){
    const x = (
      <React.Fragment>
        <div className='sonChildBag'>我是{this.props.name},我的初始值是{this.state.n}<button onClick={this.add}>+1</button></div>
        <div>我是第二个根元素</div>
      </React.Fragment>
    )
    return x
  }

上面代码运行正常,使用<React.Fragment>与使用

的区别是,渲染页面的dom结构不一样;

上面的图片是使用<React.Fragment>标签

上面的图片是使用div标签

从上面两个渲染结果来看,使用div来作为最外层标签比React.Fragment,渲染的时候会多个div标签;
React会认为React.Fragment写法也是太麻烦了,于是做了简络,使用<></>与使用<React.Fragment></React.Fragment>效果一样;

render(){
    const x = (
      <>
        <div className='sonChildBag'>我是{this.props.name},我的初始值是{this.state.n}<button onClick={this.add}>+1</button></div>
        <div>我是第二个根元素</div>
      </>
    )
    return x
  }

componentDidMount 页面加载完成后触发

作用:主要用来操作页面上的dom

  • ajax请求可以写在此钩子
  • 首次渲染会执行此钩子函数

componentDidUpdate 视图更新后执行代码

  • 此处也可以加载ajax请求,用来更新数据
  • 首次渲染不会执行此钩子函数

componentWillUnmount 组件将要移除页面并且被销毁时触发(组件从内存销毁时触发);unMount过的组件不会再mount

使用场景: - 如果在componentDidMount里面设置了scroll监听事件,就需要在componentWillUnmount里面取消此监听事件

  • 如果在componentDidMount里面创建了Timer,就需要在componentWillUnmount里面删除Timer
  • 如果在componentDidMount里面创建了ajax请求,就需要在componentWillUnmount里面删除请求
    原则谁污染谁治理,否则一些不用的监听事件、timer,ajax请求就会占用户的内存;