react 生命周期(新)

本文深入解析React组件的生命周期,包括装载、更新和卸载三个阶段,详细介绍了各阶段的钩子函数及其应用场景,如constructor、getDerivedStateFromProps、render、componentDidMount、componentWillUpdate、componentDidUpdate和componentWillUnmount等。

生命周期就是组件从创建->挂载->销毁的过程,其中在这个过程中提供一些钩子函数,以便能够在适时便于操作,

在react生命周期更新后大致的流程就如图所示:

生命周期分为三个过程:装载期间(Mounting),更新期间(Updating)和卸载期间(Unmounting)

装载期间:

组件实例化并挂载到dom树这一过程称为装载,在装载期间调用的生命周期函数以此为:

constructor()

getDerivedStateFromProps()

render()

componentDidMount()

 

constructor(props)

构造函数,用于初始化这个组件的一些状态和操作,如果通过继承React.Component子类来创建React的组件的,你应该首先调用super(props)初始化父类。

在contructor函数中,你可以初始化state,比如this.state = {},不要在构造函数中使用setState()函数,强行使用的话React会报错,其次你可以在构造函数中进行函数bind(),this.handleChange = this.handleChange.bind(this);

一个示例contructor实现如下:

constructor(props) {
  super(props);
  this.state = {
    color: '#fff'
  };

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

如果你不需要初始化状态也不需要绑定handle函数的this,那么你可以不实现constructor函数,由默认实现代替。

当然上面绑定函数的方式也可以用箭头函数的方式代替

此时不用在构造函数中生命直接:

handleChange = ()=>{}

这是因为箭头函数的this取决于生命时外层的this

 

getDerivedStateFromProps()

这个函数会在render函数被调用之前调用,包括第一次的初始化组件以及后续的更新过程中,每次接受新的props之后都会返回一个对象作为新的state,返回null则说明不需要更新state。

该方法主要用来替代componentWillReceiveProps方法,willReceiveProps经常被误用,导致了一些问题,因此在新版本中被标记为unsafe。以掘金上的?为例,componentWillReceiveProps的常见用法如下,根据传进来的属性值判断是否要load新的数据

class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
  };

  componentWillReceiveProps(nextProps) {
    if (this.props.currentRow !== nextProps.currentRow) {
      // 检测到变化后更新状态、并请求数据
      this.setState({
        isScrollingDown: nextProps.currentRow > this.props.currentRow,
      });
      this.loadAsyncData()
    }
  }

  loadAsyncData() {/* ... */}
}

但这个方法的一个问题是外部组件多次频繁更新传入多次不同的 props,而该组件将这些更新 batch 后仅仅触发单次自己的更新,这种写法会导致不必要的异步请求,相比下来getDerivedStateFromProps配合componentDidUpdate的写法如下:

class ExampleComponent extends React.Component {
  state = {
    isScrollingDown: false,
    lastRow: null,
  };

  static getDerivedStateFromProps(nextProps, prevState) {
    // 不再提供 prevProps 的获取方式
    if (nextProps.currentRow !== prevState.lastRow) {
      return {
        isScrollingDown: nextProps.currentRow > prevState.lastRow,
        lastRow: nextProps.currentRow,
      };
    }

    // 默认不改动 state
    return null;
  }
  
  componentDidUpdate() {
    // 仅在更新触发后请求数据
    this.loadAsyncData()
  }

  loadAsyncData() {/* ... */}
}

这种方式只在更新触发后请求数据,相比下来更节省资源。

注意getDerivedStateFromProps是一个static方法,意味着拿不到实例的this

render()

该方法在一个React组件中是必须实现的,你可以看成是一个java interface的接口

这是React组件的核心方法,用于根据状态state和属性props渲染一个React组件。我们应该保持该方法的纯洁性,这会让我们的组件更易于理解,只要state和props不变,每次调用render返回的结果应当相同,所以请不要在render方法中改变组件状态,也不要在在这个方法中和浏览器直接交互

componentDidMount()

componentDidMount方法会在render方法之后立即被调用,该方法在整个React生命周期中只会被调用一次。

React组件树是一个树形结构,此时这个组件及其他下面的所有子组件都已经渲染完了,所以在这个方法中可以调用和真是DOM相关的操作了。

有些组件的启动工作是依赖DOM的,例如动画的启动,而componentWillMount的时候组件还没挂载完成,所以没法进行这些启动工作,这时候就可以把这些操作放在componentDidMount当中。在这个函数中发送异步请求,在回调函数中调用setState()设置state,等数据到达后出发重新渲染,但注意尽量不要在这个函数中直接调用setState()设置状态,这会出发一次额外的重新渲染,可能造成性能问题。

 

下面的代码演示了如何在componentDidMount加载数据并设置状态:

  componentDidMount() {
    console.log('componentDidMount');
    fetch("https://api.github.com/search/repositories?q=language:java&sort=stars")
      .then(res => res.json())
      .then((result) => {
          this.setState({ // 触发render
            items: result.items
          });
        })
      .catch((error) => { console.log(error)});
    // this.setState({color: xxx}) // 不要这样做
  }

 

更新期间

当组件的状态或这属性变化时会触发更新,更新过程中会依次调用以下方法:

getDerivedStateFromProps()

componentWillUpdate()

render()

getSnapshotBeforeUpdate()

componentDidUpdate()

 

shouldComponentUpdate(nextProps,nextState)

你可以用这个方法来告诉React是否要进行下一次render(), 默认这个函数返回true,即每次更新状态和属性的时候都进行组件更新。注意这个函数如果返回false宾不会导致子组件也不更新。

 

这个钩子函数一般不需要实现,如果你的组件性能比较差或者渲染比较耗时,这个函数通常是优化性能的紧急出口,因此不要轻易使用。

getSnapshotBeforeUpdate()

该方法的触发时间为update发生的时候,在render之后dom渲染之前返回一个值,作为componentDidUpdate的第三个参数。该函数与componentDidUpdate 一起使用可以取代componentWillUpdate的所有功能。

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the scroll position so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }
}

 

componentDidUpdate(prevProps,prevState,snapshot)

该方法会在更新完成后立即调用,你可以在这个方法中进行dom操作,或者做一些异步调用。这个和首次装载过程后调用componentDidMount是类似的,不一样的是你可能需要判断下属性是否发生了变化再发起io请求

componentDidUpdate(prevProps) { // 来自网络
  if(prevProps.myProps !== this.props.myProp) {
    // this.props.myProp has a different value
    // we can perform any operations that would 
    // need the new value and/or cause side-effects 
    // like AJAX calls with the new value - this.props.myProp
  }
}

卸载期间

卸载期间是指组件被从DOM树中移除时,调用相关方法为:

componentWillUnmount()

componentWillUnmount()

该方法会在组件被卸载之前被调用,你可以在这个函数中进行相关清理工作,比如删除定时器

componentWillUnmount() {
    console.log('componentWillUnmount');

    // 清除timer
    clearInterval(this.timerID1);
    clearTimeout(this.timerID2);
    
    // 关闭socket
    this.myWebsocket.close();

    // 取消消息订阅...
  }

 

错误捕获

React16中新增了一个生命周期函数:

componentDidCatch(error, info)

在react组件中如果产生的错误没有被被捕获会被抛给上层组件,如果上层也不处理的话就会抛到顶层导致浏览器白屏错误,在React16中我们可以实现这个方法来捕获子组件产生的错误,然后在父组件中妥善处理,比如搞个弹层通知用户网页崩溃等。

在这个函数中请只进行错误恢复相关的处理,不要做其他流程控制方面的操作。比如:

componentDidCatch(error, info) { // from react.org
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

 

 

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值