玩转Reactjs第四篇-组件(生命周期)

一、前言

     前一篇主要介绍了Reactjs组件的创建模式,了解了state以及props的基本知识和用法。本篇将重点介绍下Reactjs的生命周期这对于我们认识Reactjs的实现原理,正确使用钩子函数至关重要。

   Reactjs在V16.3版本之前使用的是旧版本的生命周期,之后启用了新版的生命周期,V17版本将彻底废弃旧版本。我们将两个版本都介绍下。

二、旧版本

     Reactjs生命周期包括挂载时(Mounting),更新时(Updateing),卸载时(Unmounting)三种场景,每种场景在不同状态提供相关的钩子函数,我们可以使用这些钩子函数,实现自定义功能。

生命周期钩子函数时序的示意图如下:

1、挂载时(Mounting)

Reactjs组件首次加载时执行的流程,constructor->componentWillMount->render->componentDidMount。

  • constructor

调用时机:组件挂载前会调用构造函数。

钩子作用:前一篇我们也讲到,在构造方法里,主要做以下两件事:

(1)初始化state,这里是唯一一个可以使用this.state进行初始化的地方,其他的地方只能使用setState更新。

(2)组件内定义的方法绑定实例,前一篇也讲到如果不用箭头函数定义方法,必须在构造器中绑定this。

如果不需要使用以上两点,可以不定义组件的构造函数。

扫描二维码关注公众号,回复: 10672968 查看本文章

函数示例:示例如下:

constructor(props){
        // 1、调用父类构造方法
        super(props);
        //2、初始化state对象
        this.state = {hotItem:['口罩','手套','酒精',]};
        //绑定this
        this.changeHot = this.changeHot.bind(this);
}
  • componentWillMount(即将废弃)

在方法即将废弃,可以使用到React 17版本,但是会和新的组件生命周期钩子函数冲突,建议不要使用。

调用时机:在调用render()前调用。

钩子作用:在该方法里面调用setState不会触发额外的渲染,所以想要在该方法中请求外部数据,减少一次render,是一个常见的误区,总的来说该方法没有太大的作用。

  • render

调用时机:首次挂载以及更新时,都会调用该方法进行渲染。

钩子作用:render方法是class组件中唯一需要实现的方法。在该钩子方法里,将React JSX渲染成DOM节点,并挂载到DOM树中。

render函数应该是纯的,意味着不应该改变组件的状态,其每次调用都应返回相同的结果,同时它不会直接和浏览器交互。

函数示例:该方法的实现形式,前一篇我们也重点介绍了,如App.js中,实例如下:

render(){
    return (
    <div className="App">
      {/* 引入组件 */}
      <SearchBox changeSearchVal = {this.changeSearchVal}/>
      <SearchHot />
      <SearchList searchListVal={this.state.searchListVal}>
        <div>为您搜索到2条记录</div>
      </SearchList>
    </div>
    );
  }
  • componentDidMount

调用时机:执行完render函数(DOM节点已创建完成并挂载到DOM数中(节点插入DOM数)),立即执行。

钩子作用:在componentDidMount中可以进行以下的操作:

(1)请求网络数据,并调用setState进行更新,它将会触发一次额外的渲染,但是它将在浏览器刷新屏幕之前发生。这保证了在此情况下即使render()将会调用两次,用户也不会看到中间状态

(2)通过ref获取DOM的节点,获取DOM属性或者操作DOM。

函数示例:在App.js中,我们增加componentDidMount方法,在该钩子函数中获取默认的搜索列表,这样就能实现在首次加载完成后,页面展示默认列表。

componentDidMount(){
    console.log("App componentDidMount");
    fetch("http://localhost:3000/search.json",{
      method:'GET',
    }).then(response => response.json())
    .then((data)=>{
      this.setState({searchListVal:data.search});
    })
  }

2、更新时(Updateing)

挂载完成后,当传入父组件的props,以及组件内的state值发生变化,会触发更新时流程。componentWillReceiveProps->shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate

  • componentWillReceiveProps(nextProps)(即将废弃)

该钩子函数将在React 17版本废弃。

调用时机:在已挂载的组件接收新的 props 之前被调用。

钩子作用:当接收到新属性后,通过if比较新旧Props不同,此方法中使用 this.setState() 执行 state 转换。这个和新版本的getDerivedStateFromProps作用类似。

  • shouldComponentUpdate(nextProps,nextState)

调用时机:props或者state发送变化后,render调用前调用该钩子函数。父组件更新prop,或者调用setState都会触发。但是首次挂载以及forceUpdate不会触发该方法。

钩子作用:该函数可以决定是否继续渲染(是否执行render方法),入参为新的props和state,返回的是个布尔值,true表示继续渲染,false则表示阻止渲染,默认返回true。

     在Vue中,通过监听相关属性值的变化,通过机制自动判断是否需要渲染,无法"人为"干预。在Reactjs中,利用shouldComponentUpdate为我们提供了干预的契机,进行精确控制。

适用场景:在某些状态或属性变化时,通过this.props和nextProps以及this.state 和 nextState比较,来判断是否需要重新渲染该组件。

函数示例:我们来改写下SearchHot.js,当热搜的个数有变化时,返回false,不继续渲染。

shouldComponentUpdate(nextProps,nextState){
        console.log(nextState.hotItem.length);
        if(this.state.hotItem.length != nextState.hotItem.length){
            return false;
        }
        return true;
    }

由于state.hotItem新旧值的长度不一致,所以点击"下一批",页面不会有变化。

需要注意的是,state和props需要保持结构简单,层次不要太深,不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。

如果仅做浅层次比较,可以考虑使用React.PureComponent。

  • componentWillUpdate(nextProps, nextState)(即将废弃)

该钩子函数将在React 17版本废弃,

调用时机:当组件收到新的 props 或 state 时,会在渲染之前调用。

钩子作用:入参为新的props以及state,使用此作为在更新发生之前执行准备更新的机会,初始渲染不会调用此方法。

适用场景:可以读取DOM属性,为componentDidUpdate作准备(使用场景少)。不能在此方法中调用 this.setState()

  • componentDidUpdate(prevProps, prevState, snapshot)

调用时机:在组件已经重新渲染之后调用,首次渲染不会执行此方法。

钩子作用:入参为前props,state值,第三个参数与新版本getSnapshotBeforeUpdate有关,下一节介绍。其作用类似componentDidMount,此时已经挂载完成,可以做DOM进行操作,也可以进行网络请求。

使用场景:对DOM进行操作,比如滚动条指定到 位置,获取DOM尺寸等。在该方法里,可以调用setState,但是要包裹在一定的条件中,否则会 引起死循环。调用setSate会引起再一次的渲染,虽然用户看不见(此时浏览器还没有变化),但是会影响性能。

函数示例:   我们改写下 SearchList.js,每次更新完,滚动条指向列表的指定位置。(这里使用了ref获取dom对象,后面会专门介绍)

componentDidUpdate(prevProps, prevState, snapshot){
    console.log("componentDidUpdate");
     //列表的滚动条指向60的位置
     this.searchul.current.scrollTop=60;
  }
...
render(){
 ...
 <ul className="u" ref={this.searchul}>
...
}

3、卸载时(Unmounting)

这个阶段是指组件将被删除并从DOM中清除。

  • componentWillUnmount()

调用时机:在组件卸载及销毁之前直接调用。

适用场景:此方法中执行必要的清理操作,例如,清除 timer,取消网络请求。不要在此方法中调用setState,因为该组件永远不会渲染。

三、新版本

V16.3版本启用了新版本生命周期钩子函数,不过此版本还可以兼容老版本的钩子函数,与老版本一样,也分三种场景。

生命周期的钩子函数时序的示意图如下:

与老版本比较,钩子函数发送如下变化:

删除:componentWillMount,componentWillReceiveProps,componentWillUpdate

新增:getDerivedStateFromProps,getSnapshotBeforeUpdate。

其他的函数保留。我们重点介绍下新增的。

  • static getDerivedStateFromProps(nextProps,preState)

调用时机:props以及state发送更新后,会在调用 render 方法之前调用发。与旧的生命周期图比较可以看出,getDerivedStateFromProps处于原来的componentWillMountcomponentWillReceiveProps位置,setState以及forceUpdate方法的调用都会触发该钩子。

钩子作用:从钩子的函数名称可以看出,用接受的props值来改变state值,返回一个新的state。它的入参是新props,旧state,该函数为一个静态的,这就表示无法使用组件实例(无法调用this)。

适用场景:组件分为受控和非受控组件,受控是指数据由父组件的props传入的组件(可以由父组件控制),非受控是指数据保存在内部的state的组件(外部无法直接控制)。如果state可以由props派生出,那么就意味着该组件既是受控的,又是非受控的。其数据源就不唯一了,会导致一些意想不到的问题。通过props派生出state,是我们尽量需要避免的情况

官方文档中,也为我们罗列了以下两种常见场景的替代方案,在你可能不需要使用派生 state这篇博文中进行了详细的介绍。

(1)如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。
(2)如果你想在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用 key 使组件完全不受控 代替。

函数示例:SearchList组件中,搜索的结果列表都是由父组件App通过props传给该组件,下面我们修改下,在SearchList定义一个默认列表保存到state中,当父组件传入的prop的列表结果更新后,修改state的值,实现更新。

constructor(props){
    super(props);
    this.searchul = React.createRef();
    //定义默认搜索列表
    this.state={searchListVal:['电脑','电视','冰箱']};
  }
  static getDerivedStateFromProps(nextProps,preState){
    //当props的列表发送更新,且与state的列表不一致,且不为空
     if(nextProps.searchListVal!=preState.searchListVal&&nextProps.searchListVal.length > 0){
      //修改state的searchListVal值,返回新值
       return{
        searchListVal:nextProps.searchListVal
       }
     }
     //其他更新,state不变化
     return null;
  }
  • getSnapshotBeforeUpdate(prevProps, prevState)

调用时机:在最近一次渲染输出(提交到 DOM 节点)之前调用。

钩子作用:它让你的组件能在当前的值可能要改变前获得它们。这一生命周期返回的任何值将会 作为参数被传递给componentDidUpdate(),即snapshot入参。

适用场景:适用的场景也不常用,它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

函数示例:在SearchList组件中,我们通过componentDidUpdate,每次加载列表,将滚动条滚动到指定的位置。下面我们再次修改下,获取滚动条之前的位置,在此基础上再累加一个指定值,模拟懒加载的情况下,每次显示最新加载的值(列表底部)。

getSnapshotBeforeUpdate(prevProps, prevState){
    //获取当前滚动条位置
    return this.searchul.current.scrollTop;
  }
  componentDidUpdate(prevProps, prevState, snapshot){
    //在滚动条位置上,累加60个
    if(snapshot!=null){
      this.searchul.current.scrollTop=snapshot+60;
    } 
  }

四、与Vue的生命周期比较

这里我们将Reactjs的生命周期与Vue的比较下。

1、挂载时

2、更新时

3、销毁时

五、总结

本章节主要描述了Reactjs的新旧生命周期以及相关钩子函数的调用时机,功能,以及适用场景,并与vue的生命周期做了比较。

老版本的生命周期钩子函数的时序:

1、挂载时,constructor->componentWillMount->render->componentDidMount

2、更新时,componentWillReceiveProps->shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate

3、卸载时,componentWillUnmount

新版本的生命周期钩子函数的时序:

1、挂载时,constructor->getDerivedStateFromProps->render->componentDidMount

2、更新时,getDerivedStateFromProps->shouldComponentUpdate->render->componentDidUpdate

3、卸载时,componentWillUnmount

发布了33 篇原创文章 · 获赞 95 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/tcy83/article/details/104343082