美文网首页让前端飞Web前端之路
React进阶篇(四)事务

React进阶篇(四)事务

作者: 娜姐聊前端 | 来源:发表于2020-04-24 23:04 被阅读0次

    1. 何为事务?

    根据维基百科的解释: 提供独立可靠的恢复机制,保证出错时数据的一致性,不同事务之间互相独立。

    事务一般在 数据库 中使用的比较多,能保证出错的时候进行rollbakc恢复。但是,React中的事务并不提供类似数据库的回滚能力。

    React中的事务-Transaction就是一个包装函数,函数被包装为一个个wrapper,其中每个wrapper都有两个方法:initializeclose。当执行方法时,需要执行事务的perform方法。perform方法会首先一次执行wrapper的initialize,然后执行函数本身,最后执行wrapper的close方法。

    image

    2. 从setState看事务的应用

    React does not guarantee that the state changes are applied immediately

    setState不一定是同步的

    class Demo extends Component {
        state = {
            count: 1
        }
    
        onClickHandler = ()  => {
            this.setState({count: this.state.count + 1});
            console.log(this.state.count); // console.log 结果 1
            this.setState({count: this.state.count + 1});
            console.log(this.state.count); // console.log 结果 1
    
            setTimeout(() => {
                this.setState({count: this.state.count + 1});
                console.log(this.state.count); // console.log 结果 2
                this.setState({count: this.state.count + 1});
                console.log(this.state.count); // console.log 结果 3
            }, 0);
        }
    
        render() {
            const { count } = this.state;
            return (
                <button onClick={this.onClickHandler}>{count}</button>
            );
        }
    }
    

    上面的例子中,直接调用setState后,state并未立刻更新,但是,如果通过setTimeout异步调用后,state会立刻更新,为什么呢?

    setState的实现

    1)setState是React.Component的方法

    function ReactComponent(props, context, updater) {
      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
    }
    
    ReactComponent.prototype.setState = function (partialState, callback) {
      this.updater.enqueueSetState(this, partialState); // state入队列
      if (callback) {
        this.updater.enqueueCallback(this, callback); // callback入队列
      }
    };
    

    2)判断当前组件对象的state更新队列是否存在,如果存在则将partialState也就是新的state值加入队列;如果不存在,则创建该对象的更新队列。

    var ReactUpdatedQueue = {
      enqueueSetState: function (publicInstance, partialState) {
        var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
    
        if (!internalInstance) {
          return;
        }
    
        var queue = internalInstance._pendingStateQueue 
                    || (internalInstance._pendingStateQueue = []);
        queue.push(partialState); // 入队列
    
        enqueueUpdate(internalInstance); // 队列更新
      },
    }
    

    3)当batchingStrategy.isBatchingUpdatesfalse时,将执行batchedUpdates更新队列,若为true时,则将组件放入dirtyComponent中。

    var ReactUpdates = {
        enqueueUpdate: function enqueueUpdate(component) {
            ensureInjected();
            // batchingStrategy是一种批量更新策略
            if (!batchingStrategy.isBatchingUpdates) {
                batchingStrategy.batchedUpdates(enqueueUpdate, component);
                return;
            }
            
            dirtyComponents.push(component);
        }
    }
    

    截止到此,setState流程如下:

    image

    4)batchingStrategy是干什么的?看一下该对象类型ReactDefaultBatchingStrategy的定义。

    var ReactDefaultBatchingStrategy = {
      isBatchingUpdates: false,
      // 执行批量更新的方法
      batchedUpdates: function(callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        if (alreadyBatchingUpdates) {
          callback(a, b, c, d, e);
        } else {
          // 事务出现了!!!这里是事务的执行阶段  
          transaction.perform(callback, null, a, b, c, d, e); 
        }
      },
    };
    

    5)ReactDefaultBatchingStrategy中的transaction是如何定义的?

    // 第二个事务 - 更改isBatchingUpdates布尔值,合并state改变
    var RESET_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: function() {
        ReactDefaultBatchingStrategy.isBatchingUpdates = false;
      },
    };
    
    // 第一个事务 - 更新组件
    var FLUSH_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
    };
    
    var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
    
    function ReactDefaultBatchingStrategyTransaction() {
      this.reinitializeTransaction();
    }
    
    Object.assign(
      ReactDefaultBatchingStrategyTransaction.prototype,
      Transaction.Mixin,
      {
        getTransactionWrappers: function() {
          return TRANSACTION_WRAPPERS;
        },
      }
    );
    
    var transaction = new ReactDefaultBatchingStrategyTransaction();
    

    其中wrapper RESET_BATCHED_UPDATES负责在close阶段重置ReactDefaultBatchingStrategyisBatchingUpdatesfalse。而wrapper FLUSH_BATCHED_UPDATES负责在close执行flushBatchedUpdates

    两个事务的运行次序为 FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]

    由此,在上图中添加batchedUpdates中的事务操作后,流程图如下:

    image

    2.再回头看看我们之前的问题

    在使用React封装的事件时会进入一个事务,使得 isBatchingUpdate 为 true 。

    所以,随后的setState在调用时会进入 dirtyComponent 队列,在下一次batch update中进行更新。所以在下一次batch update之前, this.state 都不会得到更新。

    如果 setState 函数进行了 setTimeout 的包裹,由于EventLoop的特点,会保证 setState 一定是在前一条message之后,也就是上一次batch update完之后进行执行, isBatchingUpdate 为 false,此时的 setState 会直接触发一次完整的batch update,保证 this.state 被同步更新。

    3. 还有什么方式可以同步更新state?

    当我们使用原生的事件机制时(比如 addEventListener ),由于缺少了React的封装,会使得 setState 直接触发 batch update更新,从而同步更新state。

    componentDidMount() {
       document.querySelector('#foo').addEventListener('click', () => {
         this.setState({count: this.state.count + 1});
         console.log(this.state.count);
         this.setState({count: this.state.count + 1});
         console.log(this.state.count);
         })
      }
    

    微信公众号:

    相关文章

      网友评论

        本文标题:React进阶篇(四)事务

        本文链接:https://www.haomeiwen.com/subject/xjdcwhtx.html