React学习之setState的实现机制

2019-01-03  本文已影响0人  青艹止

        在react中,通过管理状态来实现对组件的管理,通过this.state()来访问state,通过this.setState方法来更新state,当this.setState方法被调用时,react会重新调用render来重新渲染UI。

一、setState的全部实现过程:

        1、enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component

        2、如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。

         3、batchedUpdates发起一次transaction.perform()事务

         4、开始执行事务初始化,运行,结束三个阶段

            初始化:事务初始化阶段没有注册方法,故无方法要执行

            运行:执行setSate时传入的callback方法,一般不会传callback参数

            结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法

          5、FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。

二、setState异步更新

源码:

//将新的state合并到状态更新队列中

var nextState = this._processPendingState(nextProps,nextContext);

//根据更新队列和 shouldComponentUpdate的状态来判断是否需要更新组件

var shouldUpdate = this._pendingForceUpdate || ! inst. shouldComponentUpdate || inst. shouldComponentUpdate(nextProps, nextState, nextContext)

        注意:如果不通过setState而直接修改this.state的值,而是诸如这样: this.state.value = 1,那么该state将不会被放入状态队列中,下次调用this.setState并对状态队列进行合并时,将会忽略之前直接别修改的state,因此我们应该用setState更新state的值。

三、setState的循环调用

        React在setState之后,会经对state进行diff,判断是否有改变,然后去diff dom决定是否要更新UI。如果这一系列过程立刻发生在每一个setState之后,就可能会有性能问题。

        当调用setState时,实际上会执行enqueueSetState方法,并对partialState以及_pendingStateQueue更新队列进行合并,最终通过enqueueUpdate执行state更新。

        而performUpdateIfNecessary方法获取_pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用 reciveComponent 和updateComponent方法进行组件更新。

        在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。

        注意:setState不能在shouldComponentUpdate或componentWillUpdate中调用setState,则会造成循环调用,将浏览器的内存占满后崩溃。

四、事务 transaction

transaction的运行过程

        · transaction的使用场景:

        1、在一次 DOM reconciliation(调和,即 state 改变导致 Virtual DOM 改变,计算真实 DOM 该如何改变的过程)的前后,保证 input 中选中的文字范围(range)不发生变化。

        2、当 DOM 节点发生重新排列时禁用事件,以确保不会触发多余的 blur/focus 事件。同时可以确保 DOM 重拍完成后事件系统恢复启用状态。

        3、当 worker thread 的 DOM reconciliation 计算完成后,由 main thread 来更新整个 UI

在渲染完新的内容后调用所有 componentDidUpdate 的回调

        · 事务通过wrapper进行封装。

        1、一个wrapper包含一对initialize和close方法。比如RESET_BATCHED_UPDATES

var RESET_BATCHED_UPDATES = {

  // 初始化调用

  initialize: emptyFunction,

  // 事务执行完成,close时调用

  close: function () {

    ReactDefaultBatchingStrategy.isBatchingUpdates = false;

  }

};

        2、transation 被包装在wrapper中,比如

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

        transaction是通过transaction.perform(callback, args…)方法进入的,它会先调用注册好的wrapper中的initialize方法,然后执行perform方法中的callback,最后再执行close方法。

五、总结

        在react中,根绝setState调用栈的不同,我们可以将它划分为两类,一类是在componentDidMount中,一类是在setTimeOut中。


这是一个例子:

class Example extends React.Component{

  constructor() {

    super();

    this.state = {

      val: 0    };

  }

    componentDidMount() {

        this.setState({val: this.state.val + 1});

        console.log(this.state.val);        // 第 1 次 打印  0

        this.setState({val: this.state.val + 1});

        console.log(this.state.val);        // 第 2 次 打印  0   

         setTimeout(() => {

              this.setState({val: this.state.val + 1});

              console.log(this.state.val);      // 第 3 次 打印  2      

              this.setState({val: this.state.val + 1});

              console.log(this.state.val);      // 第 4 次 打印  3   

     }, 0);

  }

  render() {

    return null;

  }

};


代码运行过程:

this.setState(newState) ——newState存入_pendingStateQueue —— 是否处于batch update中

处于batch update中: component保存在dirtyComponents中  

不处于batch update中:遍历dirtyComponents、调用updateComponent、更新state


        在componentDidMount中调用setState时,batchingStrategy的isBatchingUpdates已经被设置为true了,所以setState的结果并没有立即生效,而是被放进了dirtyComponents中。所以前两次打印this.state.val都是0,因为新的state还没被应用到组件中。

         setTimeOut中的两次setState,因为没有前置的batchedUpdate调用,所以batchingStrategy的isBatchingUpdates标志位是false,也就导致了新的state马上生效,没有走到dirtyComponents分支。也就是说,setTimeOut中的第一次执行,setState时,this.state.val为1,而setState完成后打印时this.state.val变成了2。第二次的setState同理,打印结果为3。

上一篇 下一篇

猜你喜欢

热点阅读