React笔记(四)生命周期函数、生命周期运行顺序

React笔记(四)

1.旧组件生命周期
  • 在类式组件上存在着一些生命周期函数,其贯穿了整个React的组件的生命周期。旧版的整体的组件生命周期如下图所示。旧版的组件生命周期分为两个阶段。挂载卸载阶段与更新阶段。render则是独立于这两个阶段作为渲染阶段存在。
    在这里插入图片描述

  • 在组件挂载卸载阶段中,存在四步操作

    • 最初,对于一个类式组件,要先将其实例化,只有实例化之后才可以通过React将JSobject的对象渲染成一块DOM。那么为了实例化,首先调用的就是构造器constructor。其接受参数props,并且存在着书写规范,如果使用了constructor,就必须要constructor内书写super,并将传给constructorprops,传入super内,否则会导致指向错误。可以在构造器里,完成状态的初始化赋值,事件的绑定等。

        constructor(props){
          super(props);
          //初始化state
          this.state = {xxx:xxx};
        }
      
    • 在实例化之后,会执行componentWillMount,这个生命周期钩子发生在组件已经被实例化之后,但却未挂载在页面上进行渲染时。可以理解为在调用render方法渲染组件前的最后一次修改状态的机会。

    • 当组件已经挂载在页面上,并完成第一次渲染后,这时会执行componentDidMount。此时组件渲染的DOM节点已经在页面上生成。这个生命周期函数在实际开发中是比较常用的。经常用于开启定时器,发送ajax请求,订阅消息等。

    • 当组件取消挂载时,这个组件会进行卸载与销毁,此时会执行componentWillUnmount ,在这个生命周期函数内完成组件的卸载与销毁。所有在componentDidMount添加的任务都需要在此生命周期函数内撤销,例如清除开启的定时器,移除事件监听器,或者撤销还未获取的请求信息对状态的影响。下面是应用示例。

      class Child extends Component {
        constructor(props) {
          super(props);
          this.state = {
            loading: true,
            content: '',
            count: 0
          };
          this.btn = React.createRef();
          this.isMount = true;
        }
      
        alertText(){
          alert('这是一个警告');
        }
          
        componentDidMount() {
          //未接收到返回值的异步请求
          setTimeout(() => {
            this.isMount && this.setState({ loading: false, content: '加载完成' })
          }, 10000);
          //绑定事件监听器
          this.btn.current.addEventListener('click', this,this.alertText, false);
          //设置定时器
          this.timer = setInterval(() => {
            let { count } = this.state;
            count++;
            this.setState({ count });
          })
        }
      
        //销毁组件内部开启的任务,不销毁的话,卸载组件后会出现错误
        componentWillUnmount(){
          //移除事件监听器
          this.btn.current.removeEventListener("click", this.alertText, false);
          //撤销还未获取的请求信息对状态的影响
          this.isMount = false;
          //清除开启的定时器
          clearInterval(this.timer);
        }
      
        render() {
          return (
            <div>
              {this.state.loading ? <p>加载中</p> : <p>{this.state.content}</p>}
              <button ref={this.btn}>点击我弹出alert</button>
            </div>
          )
        }
      }
      
      export default class Demo extends Component {
        state = {
          show: true
        };
        render() {
          return (
            <div>
              {this.state.show && <Child />}
              <button onClick={
                () => { this.setState({ show: false }) }
              }>点击我移除child组件</button>
            </div>
          )
        }
      }
      
    • 对于为什么不在页面刷新前的componentWillMount中进行异步请求等,有以下原因。如果是异步请求,即使在页面刷新前的componentWillMount中使用,也避免不了画面的二次渲染,即使使用,也不会减少开销。此外componentDidMount时,组件已经挂载到DOM树上了,此时根据请求的响应值来对DOM进行填充也更加妥当。所以相对于componentWillMountcomponentDidMount更适用于上述功能。最后componentWillMount作为旧版的生命周期函数,更应该减少使用。

  • 在组件更新阶段中,也存在四步操作

    • 首先是componentWillReceiveProps,当父组件的改变传给当前组件的props时,会运行此生命周期函数,其接收一个参数nextProps,即修改后的props。在此函数运行时。组件内部的props还未改变,因此可以通过比较this.propsnextProps来决定对状态的赋值,并渲染组件。但在组件挂载期间,此函数不会被调用。

    • 在接收了父组件更改后的props,或组件内部调用了setState设置了新的状态时,这是会触发一个门卫生命周期函数shouldComponentUpdate,其接收两个参数nextPropsnextState,并返回一个布尔型的值代表是否运行接下来的生命周期函数,以及调用render进行重新渲染。目的是为了提高性能,但大多数情况下不使用此方法优化性能。并且当使用forceUpdate时,会跳过这个门卫函数,直接进行页面的重新渲染。

      //只有当状态中value变更时才进行重新渲染
      shouldComponentUpdate: function(nextProps, nextState){
          return this.state.value === nextState.value;
      }
      
    • 在组件进行页面的重新渲染之前,会调用componentWillUpdate函数,与shouldComponentUpdate接收的参数相同。同样,初始渲染也不会调用此方法。应用场景比较局限,并且常常搭配其下一个生命周期函数componentDidUpdate实现一些功能,比如说在数据更新时修改页面上的DOM。下列代码实现了当数据不断更新时锁定当前的滚动条的位置。

      export default class Demoe extends Component {
        constructor(props) {
          super(props);
          this.state = {
            content: [],
            scrollY: 0
          };
          this.listDiv = React.createRef();
        }
        //倒序增加展示内容
        componentDidMount() {
          this.timer = setInterval(() => {
            var content = [`新闻${this.state.content.length + 1}`, ...this.state.content];
            this.setState({ content });
          }, 1000);
        }
      
        componentWillUnmount() {
          clearInterval(this.timer);
        }
      
        //获取数据变更后,但画面渲染前时滚动条的偏移量并保存。
        //这里的偏移量不要放在state中保存,放在实例对象上保存,如果在这个函数中使用setState,会导致无限循环渲染。
        componentWillUpdate(nextProps, nextState) {
          if (this.state.content.length < nextState.content.length) {
            this.previousScrollOffset =
              this.listDiv.current.scrollHeight - this.listDiv.current.scrollTop;
          }
        }
      
        //当画面渲染出新的数据后,将保存的滚动条偏移量赋值给DOM元素,令其保持原滚动条的位置
        componentDidUpdate(prevProps, prevState) {
          if (this.previousScrollOffset !== null) {
            this.listDiv.current.scrollTop =
              this.listDiv.current.scrollHeight -
              this.previousScrollOffset;
            this.previousScrollOffset = null;
          }
        }
      
        render() {
          return (
            <div style={{ height: 100, width: 150, backgroundColor: 'blue', color: 'white', overflowY: 'scroll' }} ref={this.listDiv}>
              <ul>
                {this.state.content.map(
                  (e) => {
                    return <li>{e}</li>
                  })
                }
              </ul>
            </div>
          )
        }
      }
      
    • 当组件更新渲染完毕后,会运行componentDidUpdate,这个方法与componentWillUpdate类似,但不同的是,其会接收组件在更新props或state之前的props与state。从上述的实例也可以看出,其一般用于访问与修改DOM。

  • 在渲染阶段,只有一个函数render。其在class组件内是唯一一个必须要实现的生命周期函数。其会检查组件的propsstate的变化,并返回将要渲染成的虚拟DOM等。当shouldComponentUpdate不返回false时,props的变化,使用setStateforceUpdate都会导致此函数被调用。

  • 除了这些React自动运行的生命周期函数,在React上还存在着两个需要手动运行的生命周期函数setStateforceUpdate

    • 首先setState的书写方式为setState(updater:(preState,props)=>state,[callback]),并需要注意以下几点。
      1.updater为返回stateChange对象的函数。
      2.updater可以接收到更改前,即当前的stateprops
      3.callback是可选的回调函数,它在状态更新完毕,页面也更新后(render调用后)才被调用。
      平时使用的直接向setState传递新状态的方式属于这种方式的语法糖。此外,这个函数在使用时,不会立即更新组件,而是会以一个异步的,批量的,推迟的方式去更新组件,所以在使用setState后,不能直接获取组件更新后状态。而且由于其是异步更新,所以当更新后的state依赖于更新前的state时,建议向setState的第一个参数传递函数。

      state = {
          value:'123'
        };
      
        //打印123,获取到的是更新前的state
        componentDidMount(){
          this.setState({value:'456'});
          console.log(this.state.value);
        }
      
        //打印456,获取到的是更新后的state
        componentDidMount(){
          this.setState({value:'456'},()=>{
            console.log(this.state.value);
          }); 
        }
      
        //打印456,获取到的是更新后的state
        componentDidMount(){
          this.setState({value:'456'});
        }
        componentDidUpdate(){
          console.log(this.state.value);
        }
      
    • setState是为了更新组件内状态以更新页面渲染,forceUpdate则是为了强制页面渲染,这个函数可以无视shouldComponentUpdate,直接调用render函数进行渲染。常常用于页面的内容依赖于除stateprops以外的其他数据时。另外如果在父组件中使用此函数,父组件的shouldComponentUpdate会跳过,但子组件的shouldComponentUpdate依旧会执行。

      export default class Demoe extends Component {
        constructor(props) {
          super(props);
          this.value = '123';
        }
      
        //即使整个组件内没有状态,点击按钮后,依旧会触发render函数,进行页面的重新渲染
        btnOnClick = ()=>{
          this.value = '456';
          this.forceUpdate();
        }
      
        render() {
          return (
            <div >
              {this.value}
              <button onClick={this.btnOnClick}>点我更新</button>
            </div>
          )
        }
      }
      
2.新组件生命周期
  • 相对于旧版的生命周期,新版的生命周期删除了一部分函数,同时作为替代也提供了一些新的生命周期函数。如下图所示
    在这里插入图片描述

  • 新添加了getDerivedStateFromProps替换旧版生命周期中的componentWillReceiveProps,与componentWillReceiveProps获取的参数相同,但不同的是,其会返回一个state对象以替换原state。相比于旧版生命周期,其函数内部无需再调用setState方法,减少了组件的重绘次数。此外,与旧函数不同的是,此函数再每次渲染前都会触发,而不是仅在父组件重新渲染时触发。

    //需要加静态修饰符,组件内部禁止访问this相关的内容
    //并且由于state派生于props,导致代码冗余,使得组件难以维护,因此不建议使用。
    static getDerivedStateFromProps(props, state) { 
      console.log("getDerivedStateFromProps", props, state);
      return null; 
    }
    
  • 另外一个生命周期函数为getSnapshotBeforeUpdate,它替换旧版生命周期中componentWillUpdate。两者之间的区别是,由于异步渲染,旧版生命周期中的componentWillUpdate读取到的DOM元素状态不一定是和最终的渲染画面内的DOM元素状态相同,但新版的getSnapshotBeforeUpdate会与最终的渲染画面内的DOM元素状态(componentDidUpdate中的)一致的。并且其返回值会作为componentDidUpdate的第三个参数。相应的,上面的示例可以更改为

    export default class Demoe extends Component {
      constructor(props) {
        super(props);
        this.state = {
          content: [],
          scrollY: 0
        };
        this.listDiv = React.createRef();
      }
      //倒序增加展示内容
      componentDidMount() {
        this.timer = setInterval(() => {
          var content = [`新闻${this.state.content.length + 1}`, ...this.state.content];
          this.setState({ content });
        }, 1000);
      }
    
      componentWillUnmount() {
        clearInterval(this.timer);
      }
    
      //获取数据变更后,但画面DOM更新前滚动条的偏移量并保存。
      //传入的不再是修改后的state与props,而是修改前的state与props,此时组件的state与props已经修改了
      getSnapshotBeforeUpdate(preProps, preState) {
        if (preState.content.length < this.state.content.length) {
          return this.listDiv.current.scrollHeight - this.listDiv.current.scrollTop;
        }
        else{
          return null;
        }
      }
    
      //当画面渲染出新的数据后,将从getSnapshotBeforeUpdate处接收到的滚动条偏移量赋值给DOM元素,令其保持原滚动条的位置
      componentDidUpdate(prevProps, prevState, snapshot) {
        if (snapshot !== null) {
          this.listDiv.current.scrollTop =
            this.listDiv.current.scrollHeight -
            snapshot;
        }
      }
    
      render() {
        return (
          <div style={{ height: 100, width: 150, backgroundColor: 'blue', color: 'white', overflowY: 'scroll' }} ref={this.listDiv}>
            <ul>
              {this.state.content.map(
                (e) => {
                  return <li>{e}</li>
                })
              }
            </ul>
          </div>
        )
      }
    
3.错误边界
  • 在组件内部,存在着贯穿着整个生命周期的错误边界函数,可以在其子组件树中的任何位置捕获错误,并渲染出备用页面。但其只能捕获组件树以下组件中的错误,而无法捕获其本身的错误。并且在开发环境中,错误会冒泡到window上,并中断这些被捕获的错误,但在生产环境上却不会出现冒泡。

    //getDerivedStateFromError配合componentDidCatch
    //生命周期函数,一旦后台组件报错,就会触发
    static getDerivedStateFromError(error){
        console.log(error);
        //在render之前触发
        //返回一个对象以更新state
        return {
            hasError: true,
        };
    }
    
    //会在画面DOM完成重新渲染后被调用,因此,常用于记录错误
    componentDidCatch(error,info){
        console.log(error,info);
    }
    

参考文章

React:组件的生命周期 - SegmentFault 思否

React的生命周期 - 简书 (jianshu.com)

React.Component – React (reactjs.org)

【react框架】学习记录6-组件的新旧生命周期_庞囧的博客-CSDN博客

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值