【React深切】setState的实行机制

一.几个开辟中经常会碰到的题目

以下几个题目是我们在现实开辟中经常会碰到的场景,下面用几个简朴的示例代码来复原一下。

1.setState是同步照样异步的,为何有的时刻不能马上拿到更新结果而有的时刻能够?

1.1 钩子函数和React合成事宜中的setState

如今有两个组件

  componentDidMount() {
    console.log('parent componentDidMount');
  }

  render() {
    return (
      <div>
        <SetState2></SetState2>
        <SetState></SetState>
      </div>
    );
  }

组件内部放入一样的代码,并在Setstate1中的componentDidMount中放入一段同步延时代码,打印延时时候:

  componentWillUpdate() {
    console.log('componentWillUpdate');
  }

  componentDidUpdate() {
    console.log('componentDidUpdate');
  }

  componentDidMount() {
    console.log('SetState挪用setState');
    this.setState({
      index: this.state.index + 1
    })
    console.log('state', this.state.index);
    
    console.log('SetState挪用setState');
    this.setState({
      index: this.state.index + 1
    })
    console.log('state', this.state.index);
  }

下面是实行结果:

《【React深切】setState的实行机制》

申明:

  • 1.挪用setState不会马上更新
  • 2.一切组件运用的是统一套更新机制,当一切组件didmount后,父组件didmount,然后实行更新
  • 3.更新时会把每一个组件的更新兼并,每一个组件只会触发一次更新的生命周期。

1.2 异步函数和原生事宜中的setstate

setTimeout中挪用setState(例子和在浏览器原生事宜以及接口回调中实行结果雷同)

  componentDidMount() {
    setTimeout(() => {
      console.log('挪用setState');
      this.setState({
        index: this.state.index + 1
      })
      console.log('state', this.state.index);
      console.log('挪用setState');
      this.setState({
        index: this.state.index + 1
      })
      console.log('state', this.state.index);
    }, 0);
  }

实行结果:

《【React深切】setState的实行机制》

申明:

  • 1.在父组件didmount后实行
  • 2.挪用setState同步更新

2.为何偶然一连两次setState只要一次见效?

离别实行以下代码:

  componentDidMount() {
    this.setState({ index: this.state.index + 1 }, () => {
      console.log(this.state.index);
    })
    this.setState({ index: this.state.index + 1 }, () => {
      console.log(this.state.index);
    })
  }
  componentDidMount() {
    this.setState((preState) => ({ index: preState.index + 1 }), () => {
      console.log(this.state.index);
    })
    this.setState(preState => ({ index: preState.index + 1 }), () => {
      console.log(this.state.index);
    })
  }

实行结果:

1
1
2
2

申明:

  • 1.直接通报对象的setstate会被兼并成一次
  • 2.运用函数通报state不会被兼并

二.setState实行历程

由于源码比较复杂,就不贴在这里了,有兴致的能够去githubclone一份然后根据下面的流程图去走一遍。

1.流程图

《【React深切】setState的实行机制》

图不清楚能够点击检察原图

  • partialStatesetState传入的第一个参数,对象或函数
  • _pendingStateQueue:当前组件守候实行更新的state行列
  • isBatchingUpdates:react用于标识当前是不是处于批量更新状况,一切组件公用
  • dirtyComponent:当前一切处于待更新状况的组件行列
  • transcation:react的事件机制,在被事件挪用的要领外包装n个waper对象,并一次实行:waper.init、被挪用要领、waper.close
  • FLUSH_BATCHED_UPDATES:用于实行更新的waper,只要一个close要领

2.实行历程

对比上面流程图的文字申明,也许可分为以下几步:

  • 1.将setState传入的partialState参数存储在当前组件实例的state暂存行列中。
  • 2.推断当前React是不是处于批量更新状况,假如是,将当前组件到场待更新的组件行列中。
  • 3.假如未处于批量更新状况,将批量更新状况标识设置为true,用事件再次挪用前一步要领,保证当前组件到场到了待更新组件行列中。
  • 4.挪用事件的waper要领,遍历待更新组件行列顺次实行更新。
  • 5.实行生命周期componentWillReceiveProps
  • 6.将组件的state暂存行列中的state举行兼并,取得终究要更新的state对象,并将行列置为空。
  • 7.实行生命周期componentShouldUpdate,依据返回值推断是不是要继续更新。
  • 8.实行生命周期componentWillUpdate
  • 9.实行真正的更新,render
  • 10.实行生命周期componentDidUpdate

三.总结

1.钩子函数和合成事宜中:

react的生命周期和合成事宜中,react依然处于他的更新机制中,这时候isBranchUpdate为true。

根据上述历程,这时候不管挪用多少次setState,都邑不会实行更新,而是将要更新的state存入_pendingStateQueue,将要更新的组件存入dirtyComponent

当上一次更新机制实行终了,以生命周期为例,一切组件,即最顶层组件didmount后会将isBranchUpdate设置为false。这时候将实行之前积累的setState

2.异步函数和原生事宜中

由实行机制看,setState自身并非异步的,而是假如在挪用setState时,假如react正处于更新历程,当前更新会被暂存,等上一次更新实行后在实行,这个历程给人一种异步的假象。

在生命周期,依据JS的异步机制,会将异步函数先暂存,等一切同步代码实行终了后在实行,这时候上一次更新历程已实行终了,isBranchUpdate被设置为false,依据上面的流程,这时候再挪用setState即可马上实行更新,拿到更新结果。

3.partialState兼并机制

我们看下流程中_processPendingState的代码,这个函数是用来兼并state暂存行列的,末了返回一个兼并后的state


  _processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    }

    return nextState;
  },

我们只须要关注下面这段代码:

 _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);

假如传入的是对象,很明显会被兼并成一次:

Object.assign(
  nextState,
  {index: state.index+ 1},
  {index: state.index+ 1}
)

假如传入的是函数,函数的参数preState是前一次兼并后的结果,所以盘算结果是准确的。

4.componentDidMount挪用setstate

在componentDidMount()中,你 能够马上挪用setState()。它将会触发一次分外的衬着,然则它将在浏览器革新屏幕之前发作。这保证了在此状况下纵然render()将会挪用两次,用户也不会看到中间状况。郑重运用这一形式,由于它常致使机能题目。在大多数状况下,你能够 在constructor()中运用赋值初始状况来替代。但是,有些状况下必需如许,比如像模态框和东西提示框。这时候,你须要先丈量这些DOM节点,才衬着依靠尺寸或许位置的某些东西。

以上是官方文档的申明,不引荐直接在componentDidMount直接挪用setState,由上面的剖析:componentDidMount自身处于一次更新中,我们又挪用了一次setState,就会在将来再举行一次render,形成不必要的机能糟蹋,大多数状况能够设置初始值来搞定。

当然在componentDidMount我们能够挪用接口,再回调中去修正state,这是准确的做法。

当state初始值依靠dom属性时,在componentDidMountsetState是没法防止的。

5.componentWillUpdate componentDidUpdate

这两个生命周期中不能挪用setState

由上面的流程图很轻易发明,在它们内里挪用setState会形成死循环,致使顺序崩溃。

6.引荐运用体式格局

在挪用setState时运用函数通报state值,在回调函数中猎取最新更新后的state

    原文作者:ConardLi
    原文地址: https://segmentfault.com/a/1190000018260218
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞