react事务机制 - CSDN
精华内容
参与话题
  • React中,组件的更新本质上都是由setState操作改变state引起的。因此组件更新的入口在于setState,同样经过撸源码和打断点分析画了以下的组件更新的流程图: setState的定义在组件mountComponent的时候定义: ...

    在React中,组件的更新本质上都是由setState操作改变state引起的。因此组件更新的入口在于setState,同样经过撸源码和打断点分析画了以下的组件更新的流程图:

    setState的定义在组件mountComponent的时候定义:

    inst = new Component(publicProps, publicContext, ReactUpdateQueue);
    复制代码
    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);
      if (callback) {
        this.updater.enqueueCallback(this, callback, 'setState');
      }
    };
    复制代码

    所以setState的真正的定义在 ReactUpdateQueue.js

    enqueueSetState: function (publicInstance, partialState) {
      var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
      if (!internalInstance) {
        return;
      }
      var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
      // 将state添加到对应的component的_pendingStateQueue数组中。
      queue.push(partialState);
      enqueueUpdate(internalInstance);
    }
    
    enqueueCallback: function (publicInstance, callback, callerName) {
      var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
      if (!internalInstance) {
        return null;
      }
      // 将callback添加到对应的component的_pendingCallbacks数组中。
      if (internalInstance._pendingCallbacks) {
        internalInstance._pendingCallbacks.push(callback);
      } else {
        internalInstance._pendingCallbacks = [callback];
      }
      enqueueUpdate(internalInstance);
    }
    复制代码

    两个方法最后都调用enqueueUpdate:

    function enqueueUpdate(internalInstance) {
      ReactUpdates.enqueueUpdate(internalInstance);
    }
    
    function enqueueUpdate(component) {
      // 确认需要的事务是否注入了。
      ensureInjected();
      // batchingStrategy.isBatchingUpdates为false的时候,
      // 或者说当不处于批量更新的时候,用事务的方式批量的进行component的更新。
      if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
      }
      // 当处于批量更新阶段时,不进行state的更新操作,而是将需要更新的component添加到dirtyComponents数组中
      dirtyComponents.push(component);
    }
    复制代码

    这里需要注意enqueueUpdate中根据batchingStrategy.isBatchingUpdates分别进入不同的流程,当isBatchingUpdates为true的时候表示已经处于批量更新的过程中了,这时候会将所有的有改动的组件push到dirtyComponents中。当isBatchingUpdates为false的时候会执行更新操作,这里先认为当isBatchingUpdates为false的时候进行的操作是更新组件,实际上的过程是更复杂的,稍后马上解释具体的过程。这里我们先理解下React中的事务的概念,事务的概念根据源码中的注释就可以非常清楚的了解了:

     *                       wrappers (injected at creation time)
     *                                      +        +
     *                                      |        |
     *                    +-----------------|--------|--------------+
     *                    |                 v        |              |
     *                    |      +---------------+   |              |
     *                    |   +--|    wrapper1   |---|----+         |
     *                    |   |  +---------------+   v    |         |
     *                    |   |          +-------------+  |         |
     *                    |   |     +----|   wrapper2  |--------+   |
     *                    |   |     |    +-------------+  |     |   |
     *                    |   |     |                     |     |   |
     *                    |   v     v                     v     v   | wrapper
     *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
     * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
     * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | +---+ +---+   +---------+   +---+ +---+ |
     *                    |  initialize                    close    |
     *                    +-----------------------------------------+
    复制代码

    简单来说当使用transaction.perform执行方法method的时候会按顺序先执行WRAPPER里面的initialize方法然后执行method最后再执行close方法。

    React提供了基础的事务对象Transaction,不同的事务的区别就在于initialize和close方法的不同,这个可以通过定义getTransactionWrappers方法来传入WRAPPER数组,具体的用法看下源码就好了,不过实际使用中是不会要自己去定义事务的,当然要的话也阻止不了~。

    回到enqueueUpdate,其调用的batchingStrategy.batchedUpdates方法在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);
        }
      }
    };
    复制代码

    可以看到isBatchingUpdates的初始值是false的,在调用batchedUpdates方法的时候会将isBatchingUpdates变量设置为true。然后根据设置之前的isBatchingUpdates的值来执行不同的流程。

    对于enqueueUpdate的效果就是,当执行enqueueUpdate的时候如果isBatchingUpdates为true的话(已经处于批量执行操作),则不会进行更新操作,而是将改动的component添加到dirtyComponents数组中;如果isBatchingUpdates为false的话,会执行batchedUpdates将isBatchingUpdates置为true然后调用enqueueUpdate方法,这个时候会用事务的方式来执行enqueueUpdate。

    根据流程图可以知道,事务ReactDefaultBatchingStrategyTransaction的initialize是foo没有任务操作,接着会执行method即:将改动的组件push到dirtyComponent中,最后执行close方法执行flushBatchedUpdate方法再把isBatchingUpdates重置为false。在flushBatchedUpdates方法中事务执行runBatchedUpdates方法将dirtyComponent中的component依次(先父组件在子组件的顺序)进行更新操作。这里具体的更新的过程看流程图就可以理解了,需要注意的是updateChildren方法这个方法是virtual DOM的Diff算法的核心代码,作用就是根据更新前后组件的不同进行有效的更新,具体的部分,之后单独的文章再介绍。

    在更新的过程中需要注意的一个方法是_processPendingState方法:

    _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;
     }
    复制代码

    可以看到当setState传入的是函数的时候,函数被调用的时候的传入的参数是merge了已经遍历的queue的state的nextState,如果传入的不是函数则直接merge state至nextState。这也解释了,为什么用回调函数的形式使用setState的时候可以解决state是按照顺序最新的state了。

    从流程图可以看到在保证组件更新完毕后会将setState中传入的callback按照顺序依次push到事务的callback queue队列中,在事务结束的时候close方法中notifyAll就是执行这些callbacks,这样保证了回调函数是在组件完全更新完成后执行的,也就是setState的回调函数传入的state是更新后的state的原因。

    在了解了以上的组件更新的流程后,可以看一个场景,栗子如下:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    ReactDOM.render(<App />, document.getElementById('root'));
    
    import React, { Component } from 'react';
    import Hello from './Hello';
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          appText: 'hello App',
          helloText: 'heiheihei'
        };
      }
    
      handleAppClick = () => {
        console.log('App is clicked ~');
        this.setState({
          appText: 'App is clicked ~'
        });
      }
    
      render() {
        const { appText, helloText } = this.state;
        console.log('render App');
        return (
          <div className="app-container">
            <div          
              onClick={this.handleAppClick}
            >{appText}</div>
            <Hello
              text={helloText}
              handleAppClick={this.handleAppClick}
            />
          </div>
        );
      }
    }
    
    export default App;
    
    import React, { Component } from 'react';
    
    class Hello extends Component {
        constructor(props) {
            super(props);
            this.state = {
                text: 'hello Hello'
            };
        }
    
        componentWillReceiveProps(nextProps) {
            this.setState({
              text: nextProps.text + '~'
            });
        }
    
        handleClick = () => {
            this.setState({
                text: 'Hello is clicked ~'
            });
            this.props.handleAppClick();
        }
    
        render() {
            const { text } = this.state;
            console.log('render Hello');
            return (
                <div>
                    <div
                        onClick={this.handleClick}
                        style={{ color: '#e00' }}
                    >{text}</div>
                </div>
            );
        }
    }
    
    export default Hello;
    复制代码

    点击

    hello Hello
    复制代码

    后组件的渲染如下,可以看到父组件到子组件按顺序更新了一次:

    render App
    render Hello
    复制代码

    而不是:

    render Hello
    render App
    render Hello
    复制代码

    批量更新的时候组件的顺序由:

    dirtyComponents.sort(mountOrderComparator);
    复制代码

    处理的。

    到这里你需要知道这个结果产生的原因在于不是只有setState的调用栈会改变isBatchingUpdates的值

    回顾《React事件机制》的流程图可以知道事件的统一回调函数dispatchEvent调用了ReactUpdates.batchedUpdates用事务的方式进行事件的处理,也就是说点击事件的处理本身就是在一个大的事务中,在setState执行的时候isBatchingUpdates已经是true了,setState做的就是将更新都统一push到dirtyComponents数组中,在事务结束的时候按照上述的流程进行批量更新,然后将批量执行关闭结束事务。

    事务的机制在这里也保证了React更新的效率,此外在更新组件的时候的virtual DOM的Diff算法也起到很大的作用,这个在后续的文章再介绍。

    参考资料

    展开全文
  • React原理学习

    2020-10-16 10:33:29
    React原理学习 vdom和diff h函数 vnode数据结构 patch函数 diff算法 只比较同一层级,不跨级比较 tag不相同,则直接删掉重建,不再深度比较 tag和key两者都相同,认为是相同节点,不再深度比较 JSX本质 查看...

    vdom和diff

    • h函数
    • vnode数据结构
    • patch函数

    diff算法

    • 只比较同一层级,不跨级比较
    • tag不相同,则直接删掉重建,不再深度比较
    • tag和key两者都相同,认为是相同节点,不再深度比较

    JSX本质

    查看react编译结果:Babel

    编译后为createElement函数(h函数 ),返回vnode。

    // 参数一:标签名 / 组件变量
    // 参数二:attributes
    // 参数三及往后:children / text
    React.createElement('tagname', null, [])
    React.createElement('tagname', null, child1, child2, ...) 
    

    组件名首字母大写(React规定),用于区分html tag / 自定义tag

    合成事件

    • 所有事件挂载到document上
    • event不是原生的,是SyntheticEvent合成事件对象
    • 和Vue事件不同,和dom事件也不同

    在这里插入图片描述

    优势:

    • 更好的兼容性和跨平台
    • 载到document,减少内存消耗,避免频繁解绑
    • 方便事件的统一管理(事务机制)

    setState和batchUpdate

    • 有时异步(普通使用),有时同步(setTimeout、DOM事件)
    • 有时合并(对象形式),有时不合并(函数形式)

    在这里插入图片描述

    同步setState过程

    isBatchingUpdates = true
    
    this.setState({
    	count: this.state.count + 1,
    });
    
    isBatchingUpdates = false
    

    异步setState过程

    isBatchingUpdates = true
    
    setTimeout(() => {
    	// 此时 isBatchUpdates === false,直接更新
        this.setState({
            count: this.state.count + 1,
        });
    }, 0);
    
    isBatchingUpdates = false
    

    总结:

    • setState无所谓异步还是同步
    • 看是否能命中 batchUpdate 机制
    • 判断 isBatchingUpdates

    哪些能命中batchUpdate机制(React可以管理的入口)

    • 生命周期(和它调用的函数)
    • React中注册的事件(和它调用的函数)

    哪些不能batchUpdate函数(React管不到的入口)

    • setTimeout, setInterval
    • 自定义DOM事件

    transaction 事务机制

    定义initializeclose钩子,在自定义函数初始化时包裹。

    组件渲染和更新过程

    • JSX如何渲染为页面
    • setState之后如何更新页面
    • 面试考察全流程

    渲染过程

    • props state
    • render()生成vnode
    • patch(elem, node)

    组件更新过程

    • setState(newState) -> dirtyComponents
    • render生成newVnode
    • patch(node, newNode)

    更新的两个阶段(patch的拆分)

    • reconciliation阶段 - 执行diff算法,纯JS计算
    • commit阶段 - 将 diff 结果渲染 DOM

    React fiber

    性能问题:

    • JS是单线程,和DOM渲染共用一个线程
    • 当组件足够复杂,组件更新的计算和渲染压力大
    • 同时有DOM操作需求(动画,鼠标拖拽)将卡顿

    解决方案 fiber:

    • 将reconciliation 阶段进行任务拆分(commit无法拆分)
    • DOM需要渲染时暂停,空闲时恢复
      • window.requestIdleCallback

    React面试真题演练

    组件之间如何通讯?

    • 父子组件props
    • 自定义事件
    • redux和Context

    同步JSX本质是什么?

    • createElement
    • 执行返回vnode

    Context是什么,如何应用?

    • 父组件,向其下所有子孙组建传递信息
    • 一些简单的公共信息:主题色,语言等
    • 复杂的公共信息,请用redux

    shouldComponentUpdate用途

    • 性能优化
    • 配合不可变值

    redux单项数据流

    在这里插入图片描述

    setState场景题

    componentDidMount() {
    	// count 初始值为 0
        this.setState({ count: this.state.count + 1 })
        console.log('1', this.state.count)  // 0, 异步,合并
        
        this.setState({ count: this.state.count + 1 })
        console.log('2', this.state.count)  // 0, 异步,合并
        
        setTimeout(() => {
            this.setState({ count: this.state.count + 1 })
            console.log('3', this.state.count)  // 2,同步
        })
        
        setTimeout(() => {
            this.setState({ count: this.state.count + 1 })
            console.log('4', this.state.count)  // 3,同步
        })
    }
    

    什么是纯函数

    返回一个新值,没有副作用(不会修改其他值)

    重点:不可变值

    如arr1 = arr1.slice()

    React组件生命周期

    Lifecycle Diagram

    • 单组件生命周期
    • 父子组件生命周期
    • 注意SCU

    在这里插入图片描述

    React发起ajax应该在哪个生命周期

    componentDidMount

    渲染列表为何使用key

    diff算法通过tag和key来判断是否sameNode,减少渲染次数,优化性能

    函数组件和class组件区别

    • 纯函数,输入props,输出jsx

    • 没有实例,没有生命周期,没有state

    • 不能扩展其他方法

    什么是受控组件?

    • 表单的值,受state控制
    • 需要自行监听 onChange,更新state
    • 对比非受控组件

    何时使用异步组件

    • 加载大组件
    • 路由懒加载
    • React.Suspense和React.lazy API

    多个组件抽离公共逻辑

    • 高阶组件
    • Render Props
    • mixin已被React废弃

    Redux如何进行异步请求

    • 使用异步action
    • redux-thunk 插件

    React-router如何配置懒加载

    import React from 'react'
    import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
    
    const Home = React.lazy(() => import('./routes/Home'))
    const App = () => (
    	<Router>
    		<Switch>
    			<Route exact path="/" component={Home} />
    		</Switch>
    	</Router>
    )
    

    PureComponent是什么

    • 实现了浅比较的shouleComponentUpdate

    • 优化性能

    • 结合不可变值使用

    React事件和DOM事件的区别

    • 所有事件挂在到document上
    • event不是原生的,是SyntheticEvent合成事件对象
    • dispatchEvent

    React性能优化

    • 渲染列表时加key
    • 自定义时间、dom事件及时销毁
    • 合理使用异步组件
    • 减少函数bind this的次数合理使用SCU PureComponentmemo
    • 合理使用Immutable.js
    • webpack层面的优化
    • 前端通用的性能优化(图片懒加载)
    • 使用SSR

    React和Vue区别

    相同点:

    • 都支持组件化
    • 都是数据驱动视图
    • 都是用vdom操作DOM

    区别:

    • React使用JSX拥抱JS,Vue使用模板拥抱HTML
    • React函数式编程,Vue声明式编程
    • React需要自力更生,Vue把想要的都给你
    展开全文
  • react state事务机制

    2019-12-18 19:55:47
    react构建过程中会处于一个大的事务中,先将isBatchingUpdates设置为true,正在处于批量更新的过程,后续的setstate并不会立马被更新而是都会先进入到脏组件中,当该大事务结束时isBatchingUpdates设置为false,即...

    react构建过程中会处于一个大的事务中,先将isBatchingUpdates设置为true,正在处于批量更新的过程,后续的setstate并不会立马被更新而是都会先进入到脏组件中,当该大事务结束时isBatchingUpdates设置为false,即关闭批量更新的标志,然后就是以事务的形式开始执行函数flushBatchedUpdates(真正开始批量更新state),flushBatchedUpdates中又会先以事务执行runBatchedUpdates批量遍历脏组件更新state,然后再以事务执行ReactUpdatesFlushTransaction,这个是执行setstate中的回调函数,这样就保证了在setstate中的回调函数可以拿到最新的state。回调函数是在生命周期执行完毕之后才执行的即componentdidupdate执行完毕。

    https://segmentfault.com/a/1190000010785692

    展开全文
  • 阅读源码成了今年的学习目标之一,在选择 Vue 和 React 之间,我想先阅读 React 。 在考虑到读哪个版本的时候,我想先接触到源码早期的思想可能会更轻松一些,最终我选择阅读 0.3-stable 。 那么接下来,我将从几个...

    阅读源码成了今年的学习目标之一,在选择 Vue 和 React 之间,我想先阅读 React 。 在考虑到读哪个版本的时候,我想先接触到源码早期的思想可能会更轻松一些,最终我选择阅读 0.3-stable 。 那么接下来,我将从几个方面来解读这个版本的源码。

    1. React 源码学习(一):HTML 元素渲染
    2. React 源码学习(二):HTML 子元素渲染
    3. React 源码学习(三):CSS 样式及 DOM 属性
    4. React 源码学习(四):事务机制
    5. React 源码学习(五):事件机制
    6. React 源码学习(六):组件渲染
    7. React 源码学习(七):生命周期
    8. React 源码学习(八):组件更新

    什么是事务

    我们需要直接看到事务的整个概念:

    英文版请看源码,以下为 Google 翻译内容

    Transaction 创建一个黑盒子,它能够包装任何方法,以便在调用方法之前和之后维护某些不变量(即使在调用包装方法时抛出异常)。 实例化事务的人可以在创建时提供不变量的执行者。 Transaction 类本身将为您提供一个额外的自动不变量 - 任何事务实例在运行时不应该运行的不变量。您通常会创建一个 Transaction 的单个实例,以便多次重用,这可能用于包装多个不同的方法。包装器非常简单 - 它们只需要实现两种方法。

    /**
     * <pre>
     *                                 wrappers (创建时注入)
     *                                      +        +
     *                                      |        |
     *                    +-----------------|--------|--------------+
     *                    |                 v        |              |
     *                    |      +---------------+   |              |
     *                    |   +--|    wrapper1   |---|----+         |
     *                    |   |  +---------------+   v    |         |
     *                    |   |          +-------------+  |         |
     *                    |   |     +----|   wrapper2  |--------+   |
     *                    |   |     |    +-------------+  |     |   |
     *                    |   |     |                     |     |   |
     *                    |   v     v                     v     v   | wrapper
     *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
     * perform(任何方法)   | |   | |   |   |         |   |   | |   | | maintained
     * +----------------->|-|---|-|---|-->| 任何方法 |---|---|-|---|-|-------->
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | +---+ +---+   +---------+   +---+ +---+ |
     *                    |  initialize                    close    |
     *                    +-----------------------------------------+
     * </pre>
     */
    复制代码

    奖金:

    • 按方法名称和包装器索引报告时间度量标准。

    用例:

    • 在对帐之前/之后保留输入选择范围。即使出现意外错误也可以恢复选择。
    • 在重新排列DOM时停用事件,防止模糊/焦点,同时保证事后系统重新激活。
    • 在工作线程中进行协调后,将收集的DOM突变队列刷新到主UI线程。
    • 在呈现新内容后调用任何收集的 componentDidRender 回调。
    • (未来用例):包装 ReactWorker 队列的特定刷新以保留 scrollTop (自动滚动感知DOM)。
    • (未来用例):DOM更新之前和之后的布局计算。

    事务性插件 API:

    • 具有返回任何预计算的 initialize 方法的模块。
    • 和一个接受预计算的 close 方法。当包装过程完成或失败时,调用 close

    事务 Transaction

    // utils/Transaction.js
    var Mixin = {
      /**
       * 设置此实例,以便为收集指标做好准备。 这样做是为了使这个设置方法可以在已经初始化的实例上使用,
       * 其方式是在重用时不消耗额外的内存。 如果您决定将此 mixin 的子类化为 "PooledClass" ,那么这将非常有用。
       */
      reinitializeTransaction: function() {
        // this.getTransactionWrappers() 用于获取上面概念中的 wrappers
        // this.transactionWrappers 就是 wrappers
        this.transactionWrappers = this.getTransactionWrappers();
        // 初始化用于存储 initialize 时返回的内容的 this.wrapperInitData
        if (!this.wrapperInitData) {
          this.wrapperInitData = [];
        } else {
          this.wrapperInitData.length = 0;
        }
        // 时间方面,这里不做解读
        if (!this.timingMetrics) {
          this.timingMetrics = {};
        }
        this.timingMetrics.methodInvocationTime = 0;
        if (!this.timingMetrics.wrapperInitTimes) {
          this.timingMetrics.wrapperInitTimes = [];
        } else {
          this.timingMetrics.wrapperInitTimes.length = 0;
        }
        if (!this.timingMetrics.wrapperCloseTimes) {
          this.timingMetrics.wrapperCloseTimes = [];
        } else {
          this.timingMetrics.wrapperCloseTimes.length = 0;
        }
        // 初始化事务标记
        this._isInTransaction = false;
      },
      _isInTransaction: false,
      getTransactionWrappers: null,
      isInTransaction: function() {
        return !!this._isInTransaction;
      },
      // 在安全窗口内执行该功能。 将此用于顶级方法,这些方法会导致需要进行安全检查的大量计算/突变。
      perform: function(method, scope, a, b, c, d, e, f) {
        throwIf(this.isInTransaction(), DUAL_TRANSACTION);
        var memberStart = Date.now();
        // 报错存储
        var err = null;
        // method 返回内容
        var ret;
        try {
          // 开始 initialize
          this.initializeAll();
          // initialize 完成后调用 method
          ret = method.call(scope, a, b, c, d, e, f);
        } catch (ie_requires_catch) {
          // 抓报错,这里报错会被覆盖
          err = ie_requires_catch;
        } finally {
          var memberEnd = Date.now();
          this.methodInvocationTime += (memberEnd - memberStart);
          try {
            // 开始 close
            this.closeAll();
          } catch (closeAllErr) {
            // 抓报错,这里若前面存在报错,则取前面的报错
            err = err || closeAllErr;
          }
        }
        // 抛出报错
        if (err) {
          throw err;
        }
        // 返回 method 执行结果
        return ret;
      },
      initializeAll: function() {
        // 事务开始
        this._isInTransaction = true;
        var transactionWrappers = this.transactionWrappers;
        var wrapperInitTimes = this.timingMetrics.wrapperInitTimes;
        var err = null;
        // 遍历 wrappers
        for (var i = 0; i < transactionWrappers.length; i++) {
          var initStart = Date.now();
          var wrapper = transactionWrappers[i];
          try {
            // 执行 initialize ,并把返回值存入对应的 this.wrapperInitData[i]
            this.wrapperInitData[i] =
              wrapper.initialize ? wrapper.initialize.call(this) : null;
          } catch (initErr) {
            err = err || initErr;  // Remember the first error.
            // 有报错的话对应位置存入 Transaction.OBSERVED_ERROR
            this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
          } finally {
            var curInitTime = wrapperInitTimes[i];
            var initEnd = Date.now();
            wrapperInitTimes[i] = (curInitTime || 0) + (initEnd - initStart);
          }
        }
        // 抛出错误
        if (err) {
          throw err;
        }
      },
      /**
       * 调用 `this.transactionWrappers.close[i]`  函数中的每一个,向它们传递
       * `this.transactionWrappers.init[i]` 的相应返回值(对应于失败的初始值设定项的 `close`rs 将不被调用)。
       */
      closeAll: function() {
        throwIf(!this.isInTransaction(), MISSING_TRANSACTION);
        var transactionWrappers = this.transactionWrappers;
        var wrapperCloseTimes = this.timingMetrics.wrapperCloseTimes;
        var err = null;
        for (var i = 0; i < transactionWrappers.length; i++) {
          var wrapper = transactionWrappers[i];
          var closeStart = Date.now();
          // 获取 initialize 时对应的返回值
          var initData = this.wrapperInitData[i];
          try {
            // 返回值不等于 Transaction.OBSERVED_ERROR 时才执行 close
            if (initData !== Transaction.OBSERVED_ERROR) {
              wrapper.close && wrapper.close.call(this, initData);
            }
          } catch (closeErr) {
            err = err || closeErr;  // Remember the first error.
          } finally {
            var closeEnd = Date.now();
            var curCloseTime = wrapperCloseTimes[i];
            wrapperCloseTimes[i] = (curCloseTime || 0) + (closeEnd - closeStart);
          }
        }
        // 释放内存
        this.wrapperInitData.length = 0;
        // 事务关闭
        this._isInTransaction = false;
        // 抛出错误
        if (err) {
          throw err;
        }
      }
    };
    
    var Transaction = {
      Mixin: Mixin,
      OBSERVED_ERROR: {}
    };
    复制代码

    React 事务

    上面这段代码看完,我相信你对事务的整个概念已经有所了解,那么事务到底在处理什么的时候用到了呢?接下来就要揭晓本次同样重点的内容 React 调度事务, ReactReconcileTransaction

    // core/ReactReconcileTransaction.js
    // 这个就是 React 调度事务的 wrappers
    var TRANSACTION_WRAPPERS = [
      // 确保在可能的情况下,执行事务不会干扰选择范围(当前选定的文本输入)。
      // 通俗点讲就是,在 React 调度事务开始之时将选择的文本信息获取,再结束时还原选择信息。
      // 关于 Selection 的具体实现这里不做解读。
      SELECTION_RESTORATION,
      // 抑制由于高级别的DOM操作(如临时从DOM中删除文本输入)而可能无意中调度的事件(模糊/焦点)。
      // 通俗点讲就是,在 React 调度事务结束之前,抑制事件的传播。
      // 于 core/ReactEventTopLevelCallback.js 中 createTopLevelCallback 函数,关于 _topLevelListenersEnabled 的判断来抑制。
      // 事件控制,稍后做简单解读,但内容不会提及事件具体实现相关。
      EVENT_SUPPRESSION,
      // 提供 `ReactOnDOMReady` 队列,用于在执行事务期间收集 `onDOMReady` 回调。
      ON_DOM_READY_QUEUEING
    ];
    
    function ReactReconcileTransaction() {
      // 初始化事务
      this.reinitializeTransaction();
      // 这里涉及到 PooledClass
      // 看到 ReactOnDOMReady 时末端也有这样一句话:
      // PooledClass.addPoolingTo(ReactOnDOMReady)
      // 调用 getPooled 方法其实就是 new
      // 具体在下面解读 PooledClass
      this.reactOnDOMReady = ReactOnDOMReady.getPooled(null);
    }
    
    var Mixin = {
      // reinitializeTransaction 方法中使用,用于获取 React 调度事务的 wrappers
      getTransactionWrappers: function() {
        // 判断执行环境是否可以使用 DOM
        if (ExecutionEnvironment.canUseDOM) {
          return TRANSACTION_WRAPPERS;
        } else {
          return [];
        }
      },
      getReactOnDOMReady: function() {
        return this.reactOnDOMReady;
      },
      // 这里也牵扯到 PooledClass
      destructor: function() {
        // PooledClass 中 standardReleaser 方法
        ReactOnDOMReady.release(this.reactOnDOMReady);
        // 释放内存,同样的,在 ReactOnDOMReady 中也进行了操作: this._queue = null
        this.reactOnDOMReady = null;
      }
    };
    
    mixInto(ReactReconcileTransaction, Transaction.Mixin);
    mixInto(ReactReconcileTransaction, Mixin);
    
    // 这是一个加入 PooledClass 的方法
    PooledClass.addPoolingTo(ReactReconcileTransaction);
    复制代码

    在看完 ReactReconcileTransaction 其实似懂非懂,没关系,接下来要依次解读 PooledClassReactOnDOMReady

    工具方法 PooledClass

    以下关于 PooledClass 仅解读一个参数的方法,其余多参数情况都是一样的。

    // utils/PooledClass.js
    var oneArgumentPooler = function(copyFieldsFrom) {
      // 相当于执行 CopyConstructor.getPooled()
      // CopyConstructor 就是 addPoolingTo 传入的第一参数
      var Klass = this;
      // 一但执行过 release , CopyConstructor.instancePool.length 才会大于 0
      if (Klass.instancePool.length) {
        // 这个 instance 是做什么用的,相信看到 call ,你变会理解。
        // 用来绑定执行上下文。
        var instance = Klass.instancePool.pop();
        // 这里不再进行实例化,而是以 instance 为执行上下文直接调用,并将其返回。
        Klass.call(instance, copyFieldsFrom);
        return instance;
      } else {
        // 相当于直接 new CopyConstructor(copyFieldsFrom)
        return new Klass(copyFieldsFrom);
      }
    };
    
    var standardReleaser = function(instance) {
      var Klass = this;
      // 在 CopyConstructor.getPooled() 后,保存返回结果
      // 在 release 时作为参数传入,若存在 destructor 则执行。
      if (instance.destructor) {
        instance.destructor();
      }
      // 长度未超过默认大小,则将 instance 推入数组
      if (Klass.instancePool.length < Klass.poolSize) {
        Klass.instancePool.push(instance);
      }
    };
    
    // 默认的大小
    var DEFAULT_POOL_SIZE = 10;
    // 默认的 getPooled 方法
    var DEFAULT_POOLER = oneArgumentPooler;
    
    // 直接在 CopyConstructor 上添加属性的方法,并返回 CopyConstructor
    var addPoolingTo = function(CopyConstructor, pooler) {
      var NewKlass = CopyConstructor;
      NewKlass.instancePool = [];
      NewKlass.getPooled = pooler || DEFAULT_POOLER;
      if (!NewKlass.poolSize) {
        NewKlass.poolSize = DEFAULT_POOL_SIZE;
      }
      NewKlass.release = standardReleaser;
      return NewKlass;
    };
    
    var PooledClass = {
      addPoolingTo: addPoolingTo,
      oneArgumentPooler: oneArgumentPooler,
      twoArgumentPooler: twoArgumentPooler,
      fiveArgumentPooler: fiveArgumentPooler
    };
    复制代码

    要说 PooledClass 是做什么用的,具体我也还是没有 get 到,但是经过我的实践,在调用 var c = CopyConstructor.getPooled() 后,若在 c 上添加属性,如: c.v = '0.3' 。并且在调用 CopyConstructor.release(c) 这样的情况,在重新进行 CopyConstructor.getPooled() 时,这个 v 属性及值任然存在,当然前提是,你自己定义的 destructor 方法里不会销毁 v 的值。

    React DOM 准备完成后的执行队列

    那么现在我们来看下 ReactOnDOMReady

    // core/ReactOnDOMReady.js
    // 初始化队列
    function ReactOnDOMReady(initialCollection) {
      this._queue = initialCollection || null;
    }
    
    mixInto(ReactOnDOMReady, {
      // 入队
      enqueue: function(component, callback) {
        this._queue = this._queue || [];
        // 组件及回调
        this._queue.push({component: component, callback: callback});
      },
      // 执行队列
      notifyAll: function() {
        var queue = this._queue;
        if (queue) {
          // 清空队列
          this._queue = null;
          for (var i = 0, l = queue.length; i < l; i++) {
            var component = queue[i].component;
            var callback = queue[i].callback;
            // 回调传入组件,上下文绑定 component
            // 关于 getDOMNode 方法稍后解读(获得当前 component 的 node)
            callback.call(component, component.getDOMNode());
          }
          queue.length = 0;
        }
      },
      // 清空队列
      reset: function() {
        this._queue = null;
      },
      // PooledClass.release 时候使用
      destructor: function() {
        this.reset();
      }
    });
    
    // 添加 PooledClass 方法
    PooledClass.addPoolingTo(ReactOnDOMReady);
    复制代码

    和事务结合

    这段代码很简单了, ReactOnDOMReady 就是一个执行队列。回顾到上面代码发现, React 调度事务在被初始化的时候,同样 ReactOnDOMReady 也被初始化,在调度事务执行 wrappers 的过程时, ReactOnDOMReady 被相继执行。这个具体的过程,你看到 ON_DOM_READY_QUEUEING 变会明白。

    // core/ReactReconcileTransaction.js
    var ON_DOM_READY_QUEUEING = {
      // 在 React 调度事务 initialize 时, ReactOnDOMReady 队列被重置。
      initialize: function() {
        this.reactOnDOMReady.reset();
      },
      // 在 React 调度事务 close 时, ReactOnDOMReady 队列被依次执行。
      close: function() {
        this.reactOnDOMReady.notifyAll();
      }
    };
    复制代码

    同样的,在 React 调度事务执行 release 时, ReactOnDOMReady 也会执行 release

    getDOMNode 方法

    差点忘了提下, getDOMNode 方法是做什么用的,请直接看到源码:

    // core/ReactComponent.js
    var ReactComponent = {
      Mixin: {
        getDOMNode: function() {
          // 尝试获得 _rootNode
          var rootNode = this._rootNode;
          if (!rootNode) {
            // 尝试获得 _rootNodeID
            rootNode = document.getElementById(this._rootNodeID);
            if (!rootNode) {
              // TODO: Log the frequency that we reach this path.
              // 这里代码就不做详细解读了,反正就是为了获得对应的 Node ,一级级往下查找。
              rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
            }
            // 对其进行赋值,用于下次查询。
            this._rootNode = rootNode;
          }
          return rootNode;
        },
      }
    };
    复制代码

    React 事务中的事件控制

    那么现在,我们来看到 SELECTION_RESTORATION

    // core/ReactReconcileTransaction.js
    var EVENT_SUPPRESSION = {
      initialize: function() {
        // 获取 ReactEvent.isEnabled() 用于 close 时接收。
        // 其实就是 true
        var currentlyEnabled = ReactEvent.isEnabled();
        // 设置为 false
        ReactEvent.setEnabled(false);
        return currentlyEnabled;
      },
      // close 时设置为 true
      close: function(previouslyEnabled) {
        ReactEvent.setEnabled(previouslyEnabled);
      }
    };
    复制代码

    那么在这个真个 wrappers 执行过程,这个有什么用呢? 这需要看到 ReactEventTopLevelCallback 的一段分支逻辑:

    // core/ReactEventTopLevelCallback.js
    var ReactEventTopLevelCallback = {
      createTopLevelCallback: function(topLevelType) {
        return function(fixedNativeEvent) {
          // setEnabled 方法修改的就是 _topLevelListenersEnabled 的值。
          if (!_topLevelListenersEnabled) {
            return;
          }
          // 直接掉过后续逻辑
        };
      }
    };
    复制代码

    回顾渲染 HTML 元素事务调度过程

    那么到此,我们来回顾一下,之前说到的事务机制的运用是如何进行的,重新看到这段代码是不是会清晰了很多:

    // core/ReactComponent.js
    var ReactComponent = {
      Mixin: {
        mountComponentIntoNode: function(rootID, container) {
          // 初始化 React 调度事务
          var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
          // 进入 React 调度事务 wrappers 环节
          transaction.perform(
            this._mountComponentIntoNode,
            this,
            rootID,
            container,
            transaction
          );
          // 销毁这个 React 调度事务
          ReactComponent.ReactReconcileTransaction.release(transaction);
          // 整个挂载方法结束
        }
      }
    }
    复制代码

    这里附一张此方法的图:

    /**
     * <pre>
     *                    TRANSACTION_WRAPPERS (ExecutionEnvironment.canUseDOM 为 true 的情况)
     *                                       +                +     +
     *                                       |                |     |
     *                     +-----------------|----------------|-----|-------------------+
     *                     |                 v                |     |                   |
     *                     |      +-----------------------+   |     |                   |
     *                     |   +--| SELECTION_RESTORATION |---|-----|---+               |
     *                     |   |  +-----------------------+   v     |   |               |
     *                     |   |             +-------------------+  |   |               |
     *                     |   |     +-------| EVENT_SUPPRESSION |--|---|-----+         |
     *                     |   |     |       +-------------------+  v   |     |         |
     *                     |   |     |        +-----------------------+ |     |         |
     *                     |   |     |     +--| ON_DOM_READY_QUEUEING |-|-----|-----+   |
     *                     |   |     |     |  +-----------------------+ |     |     |   |
     *                     |   |     |     |                            |     |     |   |
     * perform(_mount      |   v     v     v                            v     v     v   | wrapper
     *         Component   | +---+ +---+ +---+   +----------------+   +---+ +---+ +---+ | invariants
     *         IntoNode)   | |   | |   | |   |   |                |   |   | |   | |   | | maintained
     * +------------------>|-|---|-|---|-|---|-->|     _mount     |---|---|-|---|-|---|-|-------->
     *                     | |   | |   | |   |   |    Component   |   |   | |   | |   | |
     *                     | |   | |   | |   |   |    IntoNode    |   |   | |   | |   | |
     *                     | |   | |   | |   |   |                |   |   | |   | |   | |
     *                     | +---+ +---+ +---+   +----------------+   +---+ +---+ +---+ |
     *                     |     initialize                                 close       |
     *                     +------------------------------------------------------------+
     * </pre>
     */
    复制代码

    那么至此,实现事务机制及 React 调度事务。


    关于 Reconciler

    Stack Reconciler

    我们知道浏览器渲染引擎是单线程的,在 React 15.x 版本及之前版本,计算组件树变更时将会阻塞整个线程,整个渲染过程是连续不中断完成的,而这时的其他任务都会被阻塞,如动画等,这可能会使用户感觉到明显卡顿,比如当你在访问某一网站时,输入某个搜索关键字,更优先的应该是交互反馈或动画效果,如果交互反馈延迟 200ms ,用户则会感觉较明显的卡顿,而数据响应晚200毫秒并没太大问题。这个版本的调和器可以称为栈调和器( Stack Reconciler ),其调和算法大致过程见 React Diff 算法React Stack Reconciler 实现

    Stack Reconcilier 的主要缺陷就是不能暂停渲染任务,也不能切分任务,无法有效平衡组件更新渲染与动画相关任务间的执行顺序,即不能划分任务优先级,有可能导致重要任务卡顿,动画掉帧等问题。

    Fiber Reconciler

    React 16 版本提出了一个更先进的调和器,它允许渲染进程分段完成,而不必须一次性完成,中间可以返回至主进程控制执行其他任务。而这是通过计算部分组件树的变更,并暂停渲染更新,询问主进程是否有更高需求的绘制或者更新任务需要执行,这些高需求的任务完成后才开始渲染。这一切的实现是在代码层引入了一个新的数据结构 - Fiber 对象,每一个组件实例对应有一个 fiber 实例,此 fiber 实例负责管理组件实例的更新,渲染任务及与其他 fiber 实例的联系。

    这个新推出的调和器就叫做纤维调和器( Fiber Reconciler ),它提供的新功能主要有:

    1. 可切分,可中断任务;
    2. 可重用各分阶段任务,且可以设置优先级;
    3. 可以在父子组件任务间前进后退切换任务;
    4. render 方法可以返回多元素(即可以返回数组);
    5. 支持异常边界处理异常;

    参阅《React Fiber初探》

    展开全文
  • React事务的一些理解

    2019-03-20 03:18:58
    学习React有一段时间了,刚接触不久对于React神奇的操作很好奇,迫不及待看了源码看过几遍源码总是一知半解,最近有时间再次学习React...那就是React内部的事务,个人觉得事务很重要,生命周期中的componentWillMoun...
  • React事务机制解析

    万次阅读 2019-01-11 23:28:57
    react有一定了解的开发人员应该都听说过react事务机制,这个机制几乎贯穿于react所有提供的方法,包括react中最常使用的setState函数 那么,react事务机制到底是一种什么样的机制呢,为了解释事务机制的实现...
  • React一到三年面试题以及进阶题分享

    万次阅读 2020-08-05 10:30:31
    笔者是众多React开发者之一,编写react已有不少时日了,面试的时候遇到了很多自己不懂的东西,每次面试后都会把自己感觉蛮重要的内容总结下来,于是便有了此篇文章。希望此文章能够帮助到大家。 React面试题正文: ...
  • setState可以说是React中使用频率最高的一个函数了,我们都知道,React是通过管理状态来实现对组件的管理的,当this.setState()被调用的时候,React会重新调用render方法来重新渲染UI 但实际使用的时候,我们会发现...
  • React源码分析5 — setState机制

    万次阅读 2017-03-02 12:01:52
    1 概述React作为一门前端框架,虽然只是focus在MVVM中的View部分,但还是实现了View和model的绑定。...React中利用队列机制来管理state,避免了很多重复的View刷新。下面我们来从源码角度探寻下setSt
  • React源码分析7 — React合成事件系统

    万次阅读 2017-03-10 23:37:16
    1 React合成事件特点React自己实现了一套高效的事件注册,存储,分发和重用逻辑,在DOM事件体系基础上做了很大改进,减少了内存消耗,简化了事件逻辑,并最大化的解决了IE等浏览器的不兼容问题。与DOM事件体系相比,...
  • setState详解

    万次阅读 2017-06-17 09:38:34
    我们都知道,React通过this.state来访问state,通过this.setState()方法来更新state。当this.setState()方法被调用的时候,React会重新调用render方法来重新渲染UIsetState异步更新React初学者常会写出this.state....
  • reactjs+ace+shiro+ssm+jpa的web框架的整合

    千次阅读 2019-01-31 13:34:53
     reactjs主要的框架开发,里面的react-router可以让页面跳转变的平滑  bootstrap_ace提供了多种多样的控件及样式,省去了后台开发人员的  jquery还是页面页务逻辑的主要语言  后端:  shiro主要来进行安权限...
  • 我们知道, 对于一般的React 应用, 浏览器会首先执行代码 ReactDOM.render来渲染顶层组件, 在这个过程中递归渲染嵌套的子组件, 最终所有组件被插入到DOM中. 我们来看看 调用ReactDOM.render 发生了什么 大致过程(只...
  • 最初开发ReactNative的时候,完全没接触过React,大学那会简单学了点JS。毕业后一直在某五百强外企上班。做的东西也乱七八糟的,搞过大数据,搞过物联网,不过看我博客的同学应该知道,我对iOS和Android还是比较喜欢...
  • React学习-- React源码(4)setState机制

    千次阅读 2017-06-15 16:47:19
    React通过this.state来访问state,通过this.setState来更新state。当setState被调用的时候,React会重新调用render方法来重新渲染组件。setState异步更新setState通过一个队列机制实现state更新。当执行setstate时,...
  • ReactNative之state基础用法+究极体验

    千次阅读 2018-04-16 16:21:26
    知识点前瞻:1.state使用、React事务(transaction)、setState机制、render刷新机制。一:基本用法。1.初始化state。方式一:直接初始化(不推荐)在class里可以直接初始化state(不推荐)方式二:在构造函数中初始...
  • React源码分析4 — React生命周期详解

    万次阅读 2017-02-27 14:25:50
    1 React生命周期流程调用流程可以参看上图。分为实例化,存在期和销毁三个不同阶段。介绍生命周期流程的文章很多,相信大部分同学也有所了解,我们就不详细分析了。很多同学肯定有疑问,这些方法在React内部是在哪些...
  • React实现浏览器打印部分内容

    千次阅读 2019-10-11 17:02:42
    React 浏览器打印
  • 并有热点框架(vue react node.js 全栈)前端资源以及后端视频资源和源码 并基于前端进阶和面试的需求 总结了常用插件和js算法 以及html/css和js热点面试题 因为csdn不可以有外链 所以答案链接在评论区!!! Vue...
  • 理解 RN 框架的一些东西,以便裁剪和对 RN 有个更深入的认识,所以本篇总结了我这段时间阅读源码的一些感触,主要总结了 React Native 启动流程、JS 调用 Java 流程、Java 调用 JS 流程。
1 2 3 4 5 ... 20
收藏数 2,330
精华内容 932
热门标签
关键字:

react事务机制