react源码_react源码调试 - CSDN
  • 前言 距离第一篇《React源码解析(一)》已经过去将近4个月的时间,由于是我第一次进行源码解析相关的写作,思路和文笔还不够成熟。一百多天以来,我基于读者反馈反思这几篇文章中的不足,同时也在不断学习借...

    前言

    距离第一篇《React源码解析(一)》已经过去将近4个月的时间,由于是我第一次进行源码解析相关的写作,思路和文笔还不够成熟。一百多天以来,我基于读者反馈反思这几篇文章中的不足,同时也在不断学习借鉴其他优秀作者的写作方法和写作思路。最终总结出对于自己的源码写作来说,需要改进的几点:

    问题

    1.示例代码太多

    这可能是源码解析写作的通病吧。其实大多数人从vue和react这样框架的生命周期API就可以大致猜出内部流程是什么,示例代码太多反而会产生枯燥感,而且部分示例代码又长又臭,甚至不得不加上大段的注释,严重降低了阅读体验。

    2.解析流水账

    既然代码是给计算机看的流水账,那解析就不能再是给人看的流水账。对于源码解析来说,最大的误区是去弄懂每一行甚至每一个变量的意义,而这最终会导致事倍功半。所以源码解析应以高屋建瓴的意识去写作,而不是钻牛角尖,或者像和尚念经一样干干巴巴,逐字逐句地阐述。

    3.缺乏提炼

    我们初高中上语文课的时候,总会被问到这篇文章的中心思想是什么。同样,写完源码解析和读完源码解析,作者和读者收获了什么?很多源码解析文章并没有告诉我们,读者读完好像明白了点什么又好像没明白,一脸茫然。

    针对以上三个问题,结合已有的《React源码解析》系列,我做出了如下改进:

    改进

    1.减少示例代码,尽可能使用语言概括和图形表达

    人对图形的接受和理解度远远高于对语言的处理和理解,这也是为什么《图解HTTP》等系列书籍畅销全世界的原因。新的《React源码解析》去掉了许多无用代码和语言描述,改为使用思维导图或流程图,提升阅读体验,降低理解难度。当然,对于源码中重要的段落依然需要展示。

    2.美化文章排版

    在进行必要的代码示例时,如果代码较长,那么无论使用掘金编辑器还是代码截图,最后实际的阅读体验并不好,尤其在手机上时这一点更为明显。新的《React源码解析》将采用carbon展示代码。

    3.思想提炼

    阅读源码的最终作用并不是为了再造一个vue/react,而是借鉴其中的思想和方法,应用到实际业务中去,最终提升我们编程的能力。新的《React源码解析》将在每一篇文章的结尾阐述是什么和为什么,使读者读完有所收获,不会似懂非懂甚至白白浪费宝贵的时间。

    3.总结

    新的《React源码解析》依旧分为四篇,分别阐述React中重要的概念,目录如下:

    思维导图如下图(在mac下查看效果最好):

    四篇文章对导图的每个部分都做了详细的说明。如有欠妥之处,欢迎读者朋友们不吝指正和讨论,共同学习进步。

    联系邮箱:ssssyoki@foxmail.com

    展开全文
  • React源码解析

    2018-03-28 21:51:13
    研究 React 源码是结合网上的一些分析文章+自己看代码理解 最开始看是因为项目中遇到性能问题,网上没有相关资料,所以想找到具体影响的点 以下的代码解析以 15.4.1 版本为基础,且去除了开发环境的warning,为了...

    前言

    • 适合有一定 React 项目经验阅读,默认对 React 的常用 api 较为熟悉
    • 研究 React 源码是结合网上的一些分析文章+自己看代码理解
    • 最开始看是因为项目中遇到性能问题,网上没有相关资料,所以想找到具体影响的点
    • 以下的代码解析以 15.4.1 版本为基础,且去除了开发环境的warning,为了区分,保留的注释都为英文,新增的注释为中文,尽量保持原注释
    • 文中有部分自己的演绎、理解、猜测,如有误烦请指出

    基础概念

    ReactElement

    • 数据类,只包含 props refs key
    • React.creatElement(ReactElement.js) 创建,React.createClassrender 中返回的实际也是个ReactElement

    ReactComponent

    • 控制类,包含组件状态,操作方法等
    • 包括字符组件、原生 DOM 组件、自定义组件(和空组件)
    • 在挂载组件(mountComponent)的时候,会调用到 instantiateReactComponent
      方法,利用工厂模式,通过不同的输入返回不同的 component

    代码(instantiateReactComponent.js):

    function instantiateReactComponent(node, shouldHaveDebugID) {
    var instance;
    if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
    } else if (typeof node === 'object') {
    var element = node;
    // Special case string values
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      // This is temporarily available for custom components that are not string
      // representation, we can drop this code path.
    } else {
      instance = new ReactCompositeComponentWrapper(element);
    }
    } else if (typeof node === 'string' || typeof node === 'number') {
    instance = ReactHostComponent.createInstanceForText(node);
    } else {
    }
    // These two fields are used by the DOM and ART diffing algorithms
    // respectively. Instead of using expandos on components, we should be
    // storing the state needed by the diffing algorithms elsewhere.
    instance._mountIndex = 0;
    instance._mountImage = null;
    return instance;
    }
    • ReactDOMTextComponent 只关心文本,ReactDOMComponent 会稍微简单一些,ReactCompositeComponent 需要关心的最多,包括得到原生 DOM 的渲染内容

    ReactClass

    这个比较特殊,对比 ES5 写法: var MyComponent = React.createClass({}),ES6写法:class MyComponent extends React.Component,为什么用createClass却得到了Component呢?通过源码来看,这两个 api 的实现几乎是一样的,也可以看到,ES6 的写法简洁的多,不用那些getInitialState等特定 api,React 在之后的版本也会抛弃createClass这个 api。并且,在此 api 中,React 进行了autobind
    ReactClass.js:

    var ReactClass = {
      createClass: function (spec) {
        // ensure that Constructor.name !== 'Constructor'
        var Constructor = identity(function (props, context, updater) {
          // Wire up auto-binding
          if (this.__reactAutoBindPairs.length) {
            bindAutoBindMethods(this);
          }
          this.props = props;
          this.context = context;
          this.refs = emptyObject;
          this.updater = updater || ReactNoopUpdateQueue;
          this.state = null;
          // ReactClasses doesn't have constructors. Instead, they use the
          // getInitialState and componentWillMount methods for initialization.
          var initialState = this.getInitialState ? this.getInitialState() : null;
          this.state = initialState;
        });
        Constructor.prototype = new ReactClassComponent();
        Constructor.prototype.constructor = Constructor;
        Constructor.prototype.__reactAutoBindPairs = [];
        injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
        mixSpecIntoComponent(Constructor, spec);
        // Initialize the defaultProps property after all mixins have been merged.
        if (Constructor.getDefaultProps) {
          Constructor.defaultProps = Constructor.getDefaultProps();
        }
        // Reduce time spent doing lookups by setting these on the prototype.
        for (var methodName in ReactClassInterface) {
          if (!Constructor.prototype[methodName]) {
            Constructor.prototype[methodName] = null;
          }
        }
        return Constructor;
      }
    }
    var ReactClassComponent = function () {};
    _assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);

    ReactComponent.js:

    function ReactComponent(props, context, updater) {
      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
    }
    ReactComponent.prototype.isReactComponent = {};
    ReactComponent.prototype.setState = function (partialState, callback) {
      this.updater.enqueueSetState(this, partialState);
      if (callback) {
        this.updater.enqueueCallback(this, callback, 'setState');
      }
    };
    ReactComponent.prototype.forceUpdate = function (callback) {
      this.updater.enqueueForceUpdate(this);
      if (callback) {
        this.updater.enqueueCallback(this, callback, 'forceUpdate');
      }
    };

    对象池

    • 开辟空间是需要一定代价的
    • 如果引用释放而进入 gc,gc 会比较消耗性能和时间,如果内存抖动(大量的对象被创建又在短时间内马上被释放)而频繁 gc 则会影响用户体验
    • 既然创建和销毁对象是很耗时的,所以要尽可能减少创建和销毁对象的次数
    • 使用时候申请(getPooled)和释放(release)成对出现,使用一个对象后一定要释放还给池子(释放时候要对内部变量置空方便下次使用)

    代码(PooledClass.js):

    // 只展示部分
    var oneArgumentPooler = function (copyFieldsFrom) {
      var Klass = this;
      if (Klass.instancePool.length) {
        var instance = Klass.instancePool.pop();
        Klass.call(instance, copyFieldsFrom);
        return instance;
      } else {
        return new Klass(copyFieldsFrom);
      }
    };
    var standardReleaser = function (instance) {
      var Klass = this;
      if (Klass.instancePool.length < Klass.poolSize) {
        Klass.instancePool.push(instance);
      }
    };
    var DEFAULT_POOL_SIZE = 10;
    var DEFAULT_POOLER = oneArgumentPooler;
    var addPoolingTo = function (CopyConstructor, pooler) {
      // Casting as any so that flow ignores the actual implementation and trusts
      // it to match the type we declared
      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,
      threeArgumentPooler: threeArgumentPooler,
      fourArgumentPooler: fourArgumentPooler,
      fiveArgumentPooler: fiveArgumentPooler
    };
    module.exports = PooledClass;

    使用例子(ReactUpdate.js):

    var transaction = ReactUpdatesFlushTransaction.getPooled();
    destructor: function () {
        this.dirtyComponentsLength = null;
        CallbackQueue.release(this.callbackQueue);
        this.callbackQueue = null;
        ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
        this.reconcileTransaction = null;
      }
    ReactUpdatesFlushTransaction.release(transaction);
    • 可以看到,如果短时间内生成了大量的对象占满了池子,后续的对象是不能复用只能新建的
    • 对比连接池、线程池:完成任务后并不销毁,而是可以复用去执行其他任务

    事务机制

    • React 通过事务机制来完成一些特定操作,比如 merge state,update component

    示意图(Transaction.js):

    这里写图片描述

    代码(Transaction.js):

    var TransactionImpl = {
      perform: function (method, scope, a, b, c, d, e, f) {
        var errorThrown;
        var ret;
        try {
          this._isInTransaction = true;
          // Catching errors makes debugging more difficult, so we start with
          // errorThrown set to true before setting it to false after calling
          // close -- if it's still set to true in the finally block, it means
          // one of these calls threw.
          errorThrown = true;
          this.initializeAll(0);
          ret = method.call(scope, a, b, c, d, e, f);
          errorThrown = false;
        } finally {
          try {
            if (errorThrown) {
              // If `method` throws, prefer to show that stack trace over any thrown
              // by invoking `closeAll`.
              try {
                this.closeAll(0);
              } catch (err) {}
            } else {
              // Since `method` didn't throw, we don't want to silence the exception
              // here.
              this.closeAll(0);
            }
          } finally {
            this._isInTransaction = false;
          }
        }
        return ret;
      },
      // 执行所有 wrapper 中的 initialize 方法
      initializeAll: function (startIndex) {
      },
      // 执行所有 wrapper 中的 close 方法
      closeAll: function (startIndex) {
      }
    };
    module.exports = TransactionImpl;

    可以看到和后端的事务是有差异的(有点类似AOP),虽然都叫transaction,并没有commit,而是自动执行,初始方法没有提供rollback,有二次封装提供的(ReactReconcileTransaction.js)
    下文会提到事务机制的具体使用场景

    事件分发

    • 框图(ReactBrowserEventEmitter.js)
      这里写图片描述

    • 组件上声明的事件最终绑定到了 document 上,而不是 React 组件对应的 DOM 节点,这样简化了 DOM 原生事件,减少了内存开销

    • 以队列的方式,从触发事件的组件向父组件回溯,调用相应 callback,也就是 React 自身实现了一套事件冒泡机制,虽然 React对合成事件封装了stopPropagation,但是并不能阻止自己手动绑定的原生事件的冒泡,所以项目中要避免手动绑定原生事件

    • 使用对象池来管理合成事件对象的创建和销毁,好处在上文中有描述

    • ReactEventListener:负责事件注册和事件分发

    • ReactEventEmitter:负责事件执行

    • EventPluginHub:负责事件的存储,具体存储在listenerBank

    • Plugin: 根据不同的事件类型,构造不同的合成事件,可以连接原生事件和组件

    • 当事件触发时,会调用ReactEventListener.dispatchEvent,进行分发:找到具体的 ReactComponent,然后向上遍历父组件,实现冒泡

    • 代码较多,就不具体分析了,这种统一收集然后分发的思路,可以用在具体项目中

    生命周期

    整体流程:
    这里写图片描述

    • 主要讲述mountupdate,里面也有很多相类似的操作
    • componentWillMountrendercomponentDidMount 都是在 mountComponent 中被调用

    分析 ReactCompositeComponent.js 中的mountComponent,发现输出是return {?string} Rendered markup to be inserted into the DOM.

    mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
        var _this = this;
        this._context = context;
        this._mountOrder = nextMountID++;
        this._hostParent = hostParent;
        this._hostContainerInfo = hostContainerInfo;
        var publicProps = this._currentElement.props;
        var publicContext = this._processContext(context);
        var Component = this._currentElement.type;
        var updateQueue = transaction.getUpdateQueue();
        // Initialize the public class
        var doConstruct = shouldConstruct(Component);
        // 最终会调用 new Component()
        var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
        var renderedElement;
        // Support functional components
        if (!doConstruct && (inst == null || inst.render == null)) {
          renderedElement = inst;
          inst = new StatelessComponent(Component);
          this._compositeType = CompositeTypes.StatelessFunctional;
        } else {
          // 大家经常在用户端用到的 PureComponent,会对 state 进行浅比较然后决定是否执行 render
          if (isPureComponent(Component)) {
            this._compositeType = CompositeTypes.PureClass;
          } else {
            this._compositeType = CompositeTypes.ImpureClass;
          }
        }
        // These should be set up in the constructor, but as a convenience for
        // simpler class abstractions, we set them up after the fact.
        inst.props = publicProps;
        inst.context = publicContext;
        inst.refs = emptyObject;
        inst.updater = updateQueue;
        this._instance = inst;
        // Store a reference from the instance back to the internal representation
        // 以 element 为 key,存在了 Map 中,之后会用到
        ReactInstanceMap.set(inst, this);
        var initialState = inst.state;
        if (initialState === undefined) {
          inst.state = initialState = null;
        }
        this._pendingStateQueue = null;
        this._pendingReplaceState = false;
        this._pendingForceUpdate = false;
        var markup;
        if (inst.unstable_handleError) {
          markup = this.performInitialMountWithErrorHandling(renderedElement, hostParent, hostContainerInfo, transaction, context);
        } else {
          markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
        }
        if (inst.componentDidMount) {
           transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
        }
        return markup;
      }
    function shouldConstruct(Component) {
      return !!(Component.prototype && Component.prototype.isReactComponent);
    }
    可以看到,mountComponet 先做实例对象的初始化(props, state 等),然后调用performInitialMount挂载(performInitialMountWithErrorHandling最终也会调用performInitialMount,只是多了错误处理),然后调用componentDidMount
    transaction.getReactMountReady()会得到CallbackQueue,所以只是加入到队列中,后续执行
    我们来看performInitialMount(依然在 ReactCompositeComponent.js 中)
    performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
        var inst = this._instance;
        var debugID = 0;
        if (inst.componentWillMount) {
          inst.componentWillMount();
          // When mounting, calls to `setState` by `componentWillMount` will set
          // `this._pendingStateQueue` without triggering a re-render.
          if (this._pendingStateQueue) {
            inst.state = this._processPendingState(inst.props, inst.context);
          }
        }
        // If not a stateless component, we now render
        // 返回 ReactElement,这也就是上文说的 render 返回 ReactElement
        if (renderedElement === undefined) {
          renderedElement = this._renderValidatedComponent();
        }
        var nodeType = ReactNodeTypes.getType(renderedElement);
        this._renderedNodeType = nodeType;
        var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY);
        this._renderedComponent = child;
        var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
        return markup;
      }
    • performInitialMount 中先调用componentWillMount,这个过程中 merge state,然后调用_renderValidatedComponent(最终会调用inst.render() )返回 ReactElement,然后调用_instantiateReactComponentReactElement 创建 ReactComponent,最后进行递归渲染。

    • 挂载之后,可以通过setState来更新(机制较为复杂,后文会单独分析),此过程通过调用updateComponent来完成更新。我们来看updateComponent(依然在 ReactCompositeComponent.js 中)

    updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
        var inst = this._instance;
        var willReceive = false;
        var nextContext;
        // context 相关,React 建议少用 context
        // Determine if the context has changed or not
        if (this._context === nextUnmaskedContext) {
          nextContext = inst.context;
        } else {
          nextContext = this._processContext(nextUnmaskedContext);
          willReceive = true;
        }
        var prevProps = prevParentElement.props;
        var nextProps = nextParentElement.props;
        // Not a simple state update but a props update
        if (prevParentElement !== nextParentElement) {
          willReceive = true;
        }
        // An update here will schedule an update but immediately set
        // _pendingStateQueue which will ensure that any state updates gets
        // immediately reconciled instead of waiting for the next batch.
        if (willReceive && inst.componentWillReceiveProps) {
          inst.componentWillReceiveProps(nextProps, nextContext);
        }
        var nextState = this._processPendingState(nextProps, nextContext);
        var shouldUpdate = true;
        if (!this._pendingForceUpdate) {
          if (inst.shouldComponentUpdate) {
            shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
          } else {
            if (this._compositeType === CompositeTypes.PureClass) {
              // 这里,就是上文提到的,PureComponent 里的浅比较
              shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
            }
          }
        }
        this._updateBatchNumber = null;
        if (shouldUpdate) {
          this._pendingForceUpdate = false;
          // Will set `this.props`, `this.state` and `this.context`.
          this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
        } else {
          // If it's determined that a component should not update, we still want
          // to set props and state but we shortcut the rest of the update.
          this._currentElement = nextParentElement;
          this._context = nextUnmaskedContext;
          inst.props = nextProps;
          inst.state = nextState;
          inst.context = nextContext;
        }
      }
    • updateComponent中,先调用componentWillReceiveProps,然后 merge state,然后调用shouldComponentUpdate判断是否需要更新,可以看到,如果组件内部没有自定义,且用的是 PureComponent,会对 state 进行浅比较,设置shouldUpdate,最终调用_performComponentUpdate来进行更新。而在_performComponentUpdate中,会先调用componentWillUpdate,然后调用updateRenderedComponent进行更新,最后调用componentDidUpdate(过程较简单,就不列代码了)。下面看一下updateRenderedComponent的更新机制(依然在 ReactCompositeComponent.js 中)
    _updateRenderedComponent: function (transaction, context) {
        var prevComponentInstance = this._renderedComponent;
        var prevRenderedElement = prevComponentInstance._currentElement;
        var nextRenderedElement = this._renderValidatedComponent();
        var debugID = 0;
        if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
          ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context));
        } else {
          var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
          ReactReconciler.unmountComponent(prevComponentInstance, false);
          var nodeType = ReactNodeTypes.getType(nextRenderedElement);
          this._renderedNodeType = nodeType;
          var child = this._instantiateReactComponent(nextRenderedElement, nodeType !== ReactNodeTypes.EMPTY);
          this._renderedComponent = child;
          var nextMarkup = ReactReconciler.mountComponent(child, transaction, this._hostParent, this._hostContainerInfo, this._processChildContext(context), debugID);
          this._replaceNodeWithMarkup(oldHostNode, nextMarkup, prevComponentInstance);
        }
      },

    可以看到,如果需要更新,则调用ReactReconciler.receiveComponent,会递归更新子组件,否则直接卸载然后挂载。所以,重点是在shouldUpdateReactComponent的判断,React 为了简化 diff,所以有一个假设:在组件层级、type、key 不变的时候,才进行比较更新,否则先 unmount 然后重新 mount。来看shouldUpdateReactComponent(shouldUpdateReactComponent.js) :

    function shouldUpdateReactComponent(prevElement, nextElement) {
      var prevEmpty = prevElement === null || prevElement === false;
      var nextEmpty = nextElement === null || nextElement === false;
      if (prevEmpty || nextEmpty) {
        return prevEmpty === nextEmpty;
      }
      var prevType = typeof prevElement;
      var nextType = typeof nextElement;
      // 如果前后两次都为文本元素,则更新
      if (prevType === 'string' || prevType === 'number') {
        return nextType === 'string' || nextType === 'number';
      } else {
        // 如果为 ReactDomComponent 或 ReactCompositeComponent,则需要层级 type 和 key 相同,才进行 update(层级在递归中保证相同)
        return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;
      }
    }

    接下来是重头戏:setState,上文中已经提到了此 api 为:

    ReactComponent.prototype.setState = function (partialState, callback) {
      this.updater.enqueueSetState(this, partialState);
      if (callback) {
        this.updater.enqueueCallback(this, callback, 'setState');
      }
    };

    可以看到这里只是简单的调用enqueueSetState放入队列中,而我们知道,不可能这么简单的。来看enqueueSetState(ReactUpdateQueue.js中),this.updater会在 mount 时候赋值为updateQueue

    enqueueSetState: function (publicInstance, partialState) {
        var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
        if (!internalInstance) {
          return;
        }
        // 获取队列,如果为空则创建
        var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
        // 将待 merge 的 state 放入队列
        queue.push(partialState);
        // 将待更新的组件放入队列
        enqueueUpdate(internalInstance);
      },
      function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
          // 上文提到的以 element 为 key 存入 map,这里可以取到 component
        var internalInstance = ReactInstanceMap.get(publicInstance);
        if (!internalInstance) {
          return null;
        }
        return internalInstance;
      }

    再来看enqueueUpdate(ReactUpdates.js):

    function enqueueUpdate(component) {
      if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
      }
      dirtyComponents.push(component);
      if (component._updateBatchNumber == null) {
        component._updateBatchNumber = updateBatchNumber + 1;
      }
    }
    • 可以看到,如果不处于isBatchingUpdates时,则调用batchingStrategy.batchedUpdates,如果处于的话,则将 component 放入 dirtyComponents 中等待以后处理。这样保证了避免重复 render,因为mountComponentupdateComponent 执行的开始,会将isBatchingUpdates设置为true,之后以事务的方式处理,包括最后时候将isBatchingUpdates置为false。
    • 大家一定对 batchingStrategydirtyComponents的定义,batchingStrategyReactUpdates.injection 注入,而dirtyComponents 是定义在ReactUpdates.js 中,也就是说二者都为全局的
    • 综上,在特定生命周期getInitialState,componentWillMount,render,componentWillUpdate中调用setState,并不会引起updateComponent(componentDidMount、componentDidUpdate 中会)。来看batchedUpdates(ReactDefaultBatchingStrategy.js):
    batchedUpdates: function (callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        // The code is written this way to avoid extra allocations
        if (alreadyBatchingUpdates) {
          return callback(a, b, c, d, e);
        } else {
          // 注意这里,上一个代码块中可以看到,当 isBatchingUpdates 为 false 时,callback 为 enqueueUpdate 自身
          // 所以即以事务的方式处理
          return transaction.perform(callback, null, a, b, c, d, e);
        }
      }
      var transaction = new ReactDefaultBatchingStrategyTransaction();
    • 可以看到,当以事务的方式调用进入enqueueUpdate时,isBatchingUpdates已经为true,所以执行dirtyComponents.push(component);
    • 注意到callbakc其实就是自身enqueueUpdate,当isBatchingUpdates为false时,也用transaction.perform调用enqueueUpdate,使得结果一样
    • 详细介绍事务 transaction 的应用,上文中提到过,事务可以利用wrapper封装,开始和结束时会调用所有 wrapper 的相应方法,来看这两个wrapper: RESET_BATCHED_UPDATES FLUSH_BATCHED_UPDATES(ReactDefaultBatchingStrategy.js):
    var RESET_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: function () {
        ReactDefaultBatchingStrategy.isBatchingUpdates = false;
      }
    };
    var FLUSH_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
    };
    // flushBatchedUpdates 在 ReactUpdates.js 中
    var flushBatchedUpdates = function () {
      // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
      // asapEnqueued 为提前执行回调,暂不分析
      while (dirtyComponents.length || asapEnqueued) {
        if (dirtyComponents.length) {
          var transaction = ReactUpdatesFlushTransaction.getPooled();
          transaction.perform(, null, transaction);
          ReactUpdatesFlushTransaction.release(transaction);
        }
        if (asapEnqueued) {
        }
      }
    };
    • 但是,仔细看上面的过程,把组件放入 dirtyComponents 后,事务结束马上就执行 close方法进行了处理了,和之前理解的流程好像不太一致?这时候再回头看mountComponent和updateComponent,它们的参数:@param {ReactReconcileTransaction} transaction,也就是说整个过程都在ReactReconcileTransaction事务中(事件回调同理),自然在其中的生命周期调用setState不用引起重复 render,只会将 state 放入队列和将组件放入 dirtyComponents 中,然后在结束后统一处理
    • ReactReconcileTransaction中 initialize 用于清空回调队列;close 用于触发回调函数componentDidMount、componentDidUpdate 执行
    • 我开始一直比较疑惑的是ReactDefaultBatchingStrategy.batchedUpdates中的ReactDefaultBatchingStrategyTransaction和ReactReconcileTransaction到底是什么关系?我试图找出两个transaction 中 wrapper 是否有 merge 的情况,发现没有。目前大概的理解和结论是这样的:整个生命周期就是一个 transaction,即对应ReactDefaultBatchingStrategy.batchedUpdates,而ReactReconcileTransaction粒度较小,负责单个组件(所以也能看到,前者直接new,而后者利用了对象池)。通过各自 wrapper 可以看到,前者([FLUSH_BATCHED_UPDATES,RESET_BATCHED_UPDATES])负责了全部组件更新 和 callback,后者([SELECTION_RESTORATION, EVENT_SUPPRESSION,ON_DOM_READY_QUEUEING)负责了各自组件自身的问题,如 focus 等。
    • 例证:ReactDom 中调用render(插入过程),实际最终调用了 ReactMount的_renderNewRootComponent,其中执行了ReactUpdates.batchedUpdates(batchedMountComponentIntoNode,componentInstance, container, shouldReuseMarkup, context);(注意出现了batchedUpdates),而batchedMountComponentIntoNode中调用了ReactUpdates.ReactReconcileTransaction.getPooled,这样,嵌套关系就联系起来了
    • 例证: ReactEventListenerdispatchEvent,会调用ReactUpdates.batchedUpdates(handleTopLevelImpl,bookKeeping); 和上述同理
    • 熟悉 React 生命周期的同学一定对父子组件各生命周期的执行顺序很清晰(比如 componentWillMount是从父到子),以上述的理论,是如何保证的么?上文中可以看到,FLUSH_BATCHED_UPDATESclose方法利调用了runBatchedUpdates,来看这个方法(ReactUpdates.js):
    function runBatchedUpdates(transaction) {
      var len = transaction.dirtyComponentsLength;
      // reconcile them before their children by sorting the array.
      dirtyComponents.sort(mountOrderComparator);
      // Any updates enqueued while reconciling must be performed after this entire
      // batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
      // C, B could update twice in a single batch if C's render enqueues an update
      // to B (since B would have already updated, we should skip it, and the only
      // way we can know to do so is by checking the batch counter).
      updateBatchNumber++;
      for (var i = 0; i < len; i++) {
        // If a component is unmounted before pending changes apply, it will still
        // be here, but we assume that it has cleared its _pendingCallbacks and
        // that was is a noop.
        var component = dirtyComponents[i];
        // If performUpdateIfNecessary happens to enqueue any new updates, we
        // shouldn't execute the callbacks until the next render happens, so
        // stash the callbacks first
        var callbacks = component._pendingCallbacks;
        component._pendingCallbacks = null;
        ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
        if (callbacks) {
          for (var j = 0; j < callbacks.length; j++) {
            transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
          }
        }
      }
    }
    function mountOrderComparator(c1, c2) {
      return c1._mountOrder - c2._mountOrder;
    }
    • flushBatchedUpdates在事务ReactUpdatesFlushTransaction中,此事务是对ReactReconcileTransactionCallbackQueue的封装,结束时置空dirtyComponents 并通知回调
    • performUpdateIfNecessary最终会调用updateComponent,进行更新

    diff 算法

    • 传统对于树的 diff 算法,时间复杂度要达到 o(n^3),这对于用户端显然是不能接受的。而 React基于几个基础假设,将时间复杂度优化为 o(n)
    • 假设(策略)
    • Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
    • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
    • 对于同一层级的一组子节点,它们可以通过唯一 id进行区分

    场景

    • tree diff:只对比同层级节点(注意前文中所有代码中,都是只比较prevRenderedElement和nextRenderedElement)
    • component diff: 如果类型相同则继续比较,如果类型不同则直接卸载再挂载,即上文中提到的shouldUpdateReactComponent(虽然当两个
      component 是不同类型但结构相似时,React diff 会影响性能,但正如 React 官方博客所言:不同类型的component 是很少存在相似 DOM tree 的机会,因此为这种极端情况而做太多比较是不值得的)
    • element diff: 当一组节点处于同一层级时,React 对于每个节点提供了三种操作,分别为INSERT_MARKUP(插入)、MOVE_EXISTING(移动)、 REMOVE_NODE(删除)
    • 上文的代码中,除了关心 type,还关心 key,这也是 diff 算法的关键,如图
      这里写图片描述

    • 首先对新集合的节点进行循环遍历,for (name in nextChildren),如果存在相同节点,则进行操作,是否移动是通过比较child._mountIndex < lastIndex,符合则进行节点移动操作(即在老集合中的位置和 lastIndex 比较),lastIndex 表示访问过的节点在老集合中最右的位置(即最大的位置)。这是一种顺序优化手段,lastIndex 一直在更新,表示访问过的节点在老集合中最右的位置,如果新集合中当前访问的节点比 lastIndex大,说明当前访问节点在老集合中就比上一个节点位置靠后,则该节点不会影响其他节点的位置,因此不用添加到差异队列中,即不执行移动操作,只有当访问的节点比lastIndex 小时,才需要进行移动操作。来看具体过程:

    • 从新集合中取得 B,判断老集合中存在相同节点 B,通过对比节点位置判断是否进行移动操作,B 在老集合中的位置 B._mountIndex = 1,此时 lastIndex = 0,不满足 child._mountIndex < lastIndex 的条件,因此不对 B 进行移动操作;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),其中
      prevChild._mountIndex 表示 B 在老集合中的位置,则 lastIndex = 1,并将 B的位置更新为新集合中的位置prevChild._mountIndex = nextIndex,此时新集合中 B._mountIndex = 0,nextIndex++ 进入下一个节点的判断
    • 从新集合中取得 A,判断老集合中存在相同节点 A,通过对比节点位置判断是否进行移动操作,A 在老集合中的位置 A._mountIndex = 0,此时 lastIndex = 1,满足 child._mountIndex < lastIndex的条件,因此对 A 进行移动操作 enqueueMove(this, child._mountIndex, toIndex),其中 toIndex 其实就是nextIndex,表示 A 需要移动到的位置;更新 lastIndex =Math.max(prevChild._mountIndex, lastIndex),则 lastIndex = 1,并将 A的位置更新为新集合中的位置 prevChild._mountIndex = nextIndex,此时新集合中A._mountIndex = 1,nextIndex++ 进入下一个节点的判断。
    • 从新集合中取得 D,判断老集合中存在相同节点 D,通过对比节点位置判断是否进行移动操作,D 在老集合中的位置 D._mountIndex= 3,此时 lastIndex = 1,不满足 child._mountIndex < lastIndex的条件,因此不对 D 进行移动操作;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),则lastIndex = 3,并将 D 的位置更新为新集合中的位置 prevChild._mountIndex = nextIndex,此时新集合中D._mountIndex = 2,nextIndex++ 进入下一个节点的判断。
    • 从新集合中取得 C,判断老集合中存在相同节点 C,通过对比节点位置判断是否进行移动操作,C 在老集合中的位置 C._mountIndex= 2,此时 lastIndex = 3,满足 child._mountIndex < lastIndex 的条件,因此对 C 进行移动操作 enqueueMove(this, child._mountIndex, toIndex);更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),则 lastIndex = 3,并将 C的位置更新为新集合中的位置 prevChild._mountIndex = nextIndex,此时新集合中 C._mountIndex= 3,nextIndex++ 进入下一个节点的判断,由于 C 已经是最后一个节点,因此 diff 到此完成。
    • 当有新的 Component 插入时,逻辑一致,不做具体分析了
    • 当完成集合中所有节点 diff,还需要遍历老集合,如果存在新集合中没有但老集合中有的节点,则删除

    代码(ReactMultiChild.js),针对 element diff(tree diff 和 component diff 在之前的代码中已经提到过):

    _updateChildren: function (nextNestedChildrenElements, transaction, context) {
          var prevChildren = this._renderedChildren;
          var removedNodes = {};
          var mountImages = [];
          var nextChildren = this._reconcilerUpdateChildren(prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context);
          if (!nextChildren && !prevChildren) {
            return;
          }
          var updates = null;
          var name;
          // `nextIndex` will increment for each child in `nextChildren`, but
          // `lastIndex` will be the last index visited in `prevChildren`.
          var nextIndex = 0;
          var lastIndex = 0;
          // `nextMountIndex` will increment for each newly mounted child.
          var nextMountIndex = 0;
          var lastPlacedNode = null;
          for (name in nextChildren) {
            if (!nextChildren.hasOwnProperty(name)) {
              continue;
            }
            var prevChild = prevChildren && prevChildren[name];
            var nextChild = nextChildren[name];
            if (prevChild === nextChild) {
              updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
              lastIndex = Math.max(prevChild._mountIndex, lastIndex);
              prevChild._mountIndex = nextIndex;
            } else {
              if (prevChild) {
                // Update `lastIndex` before `_mountIndex` gets unset by unmounting.
                lastIndex = Math.max(prevChild._mountIndex, lastIndex);
                // The `removedNodes` loop below will actually remove the child.
              }
              // The child must be instantiated before it's mounted.
              updates = enqueue(updates, this._mountChildAtIndex(nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context));
              nextMountIndex++;
            }
            nextIndex++;
            lastPlacedNode = ReactReconciler.getHostNode(nextChild);
          }
          // Remove children that are no longer present.
          for (name in removedNodes) {
            if (removedNodes.hasOwnProperty(name)) {
              updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
            }
          }
          if (updates) {
            processQueue(this, updates);
          }
          this._renderedChildren = nextChildren;
        },

    综上,在开发中,保持稳定的结构有助于性能提升,当有一组节点时,除了要设置 key,也要避免将靠后的节点移动到靠前的位置

    一些其他的点

    interface(ReactClass.js)

    var ReactClassInterface = {
      mixins: 'DEFINE_MANY',
      statics: 'DEFINE_MANY',
      propTypes: 'DEFINE_MANY',
      contextTypes: 'DEFINE_MANY',
      childContextTypes: 'DEFINE_MANY',
      // ==== Definition methods ====
      getDefaultProps: 'DEFINE_MANY_MERGED',
      getInitialState: 'DEFINE_MANY_MERGED',
      getChildContext: 'DEFINE_MANY_MERGED',
      render: 'DEFINE_ONCE',
      // ==== Delegate methods ====
      componentWillMount: 'DEFINE_MANY',
      componentDidMount: 'DEFINE_MANY',
      componentWillReceiveProps: 'DEFINE_MANY',
      shouldComponentUpdate: 'DEFINE_ONCE',
      componentWillUpdate: 'DEFINE_MANY',
      componentDidUpdate: 'DEFINE_MANY',
      componentWillUnmount: 'DEFINE_MANY',
      // ==== Advanced methods ====
      updateComponent: 'OVERRIDE_BASE'
    };
    function validateMethodOverride(isAlreadyDefined, name) {
      var specPolicy = ReactClassInterface.hasOwnProperty(name) ? ReactClassInterface[name] : null;
      // Disallow overriding of base class methods unless explicitly allowed.
      if (ReactClassMixin.hasOwnProperty(name)) {
        !(specPolicy === 'OVERRIDE_BASE') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.', name) : _prodInvariant('73', name) : void 0;
      }
      // Disallow defining methods more than once unless explicitly allowed.
      if (isAlreadyDefined) {
        !(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.', name) : _prodInvariant('74', name) : void 0;
      }
    }

    可以看到,和后端中interface(或是抽象类)还是有区别的,但是可以起到规范和检查的作用,实际项目中可以借鉴

    展开全文
  • react源码解读(1)

    2018-09-22 14:54:04
    通过react的脚手架工具建立一个react项目,打开index.js,这个是项目的入口文件 ...ReactDOM是 react-dom这个库,所以直接看react-dom这个源码,找到render方法,看看到底是个什么鬼! 找到这个传说中的...

    通过react的脚手架工具建立一个react项目,打开index.js,这个是项目的入口文件

    就一句话,ReactDOM.render(<Router />,document.getElementById('root'));

    ReactDOM是 react-dom这个库,所以直接看react-dom这个源码,找到render方法,看看到底是个什么鬼!

    找到这个传说中的render方法,继续看一下庐山真面目!

    首先看一下方法的参数 element, container, callback !

    element 就是 React当中的ReactDom ,React实现了独立于浏览器的DOM系统

    container 浏览器中的Dom节点

    发现这个render函数其实就是执行了legacyRenderSubtreeIntoContainer这个方法,

    下面来看下这个方法:

    看一下这个root对象的生成过程!以下是react方法中的root对象生成的过程

    legacyRenderSubtreeIntoContainer -> legacyCreateRootFromDOMContainer -> ReactRoot -> createContainer ->createFiberRoot -> createHostRootFiber -> createFiber -> FiberNode(生成一个FiberNode节点)

    可以知道这个root其实是一个ReactRoot对象

    再看下这个root对象有哪些属性和方法

    root = {
        current: uninitializedFiber,  // fiberNode节点
        containerInfo: containerInfo,
        pendingChildren: null,

        earliestPendingTime: NoWork,
        latestPendingTime: NoWork,
        earliestSuspendedTime: NoWork,
        latestSuspendedTime: NoWork,
        latestPingedTime: NoWork,

        pendingCommitExpirationTime: NoWork,
        finishedWork: null,
        context: null,
        pendingContext: null,
        hydrate: hydrate,
        remainingExpirationTime: NoWork,
        firstBatch: null,
        nextScheduledRoot: null
      };

    这个就是root对象,current就是 fiberNode节点

    展开全文
  • react源码总览(翻译)

    2018-12-10 08:59:13
    react也有段时间了, 是时候看看人家源码了. 看源码之前看到官方文档 有这么篇文章介绍其代码结构了, 为了看源码能顺利些, 遂决定将其翻译来看看, 小弟英语也是半瓢水, 好多单词得查词典, 不当之处请批评. 直接从...

      用react也有段时间了, 是时候看看人家源码了. 看源码之前看到官方文档 有这么篇文章介绍其代码结构了, 为了看源码能顺利些, 遂决定将其翻译来看看, 小弟英语也是半瓢水, 好多单词得查词典, 不当之处请批评. 直接从字面翻译的, 后面看源码后可能会在再修改下.
      
      下面是翻译
      
      这部分将给你介绍下react代码的基本结构, 代码约定和它的基本实现.
      
      如果你想为react贡献代码的话, 我们希望这篇指南能让你写代码更加舒服.
      
      我们不推荐将这些约定用在react应用中, 因为这些约定大多是基于一些历史原因存在的, 随着时间推移可能会发生变化.
      
      外部依赖
      
      react 几乎没有外部依赖. 通常require()指向的是react自己代码库的一个文件. 但是也有一些例外.
      
      由于react想要通过库共享一些诸如Relay的小工具, 所以存在fbjs repository, 而且我们让他们是同步的. 我们没有依赖任何node生态系统下的小模块, 因为我们希望facebook的工程师的能能再任何必要的时候修改他们. fbjs中的任何工具都不能被认为是公共api, 并且他们只是为Facebook的一些工程使用, 比如react.
      
      一级目录
      
      克隆了react的仓库后你会发现在里边有几个一级目录.
      
      packages目录包括一些元数据(如package.json)和react库提供的所有包的源码(src的下面), 如果你想修改代码, src下面就是你要花时间最多的地方.
      
      fixtures目录包括了为贡献者准备的一些小的react的测试应用
      
      build是react打包输出的目录. 他不在代码库管理范畴, 但是当你第一次打包后就会生成.
      
      文档是放在和react不同的另一个仓库管理的.
      
      还有一些其他一级目录, 他们大多是工具层面的, 在你贡献代码时可能不会用到他们能.
      
      共同测试(Colocated Tests)
      
      我们没有搞个一级目录来做单元测试. 我们把它放在了被测试文件相邻的被称为__tests__的目录.
      
      举个例子, 对于setInnerHTML.js这个文件的测试被放在与他同级的__tests__/setInnerHTML-test.js这个里边.
      
      这个词不知道怎么翻译
      
      Warnings and Invariants
      
      react中使用warning模块显示警告信息.
      
      var warning = require('warning');
      
      warning(
      
      2 + 2 ==www.dasheng178.com= 4,
      
      'Math is not working today.'
      
      );
      
      当警告条件是false的时候会展示警告信息
      
      可以这么理解, 条件应该指示正常的情况, 而不是异常的情况. 就是说第一个参数是true表示的是正常, false是异常.
      
      最好避免使用console取代warnings.
      
      var warning = require(www.michenggw.com'warning');
      
      var didWarnAboutMath = false;
      
      if (!didWarnAboutMath) {
      
      warning(
      
      2 + 2 === 4,
      
      'Math is not working today.'
      
      );
      
      didWarnAboutMath =www.mhylpt.com true;
      
      }
      
      警告只会在开发模式被开启. 生产环境下被去掉了. 如果你想阻止某些代码块的执行, 那么你可以用invariant模块.
      
      var invariant = require('invariant');
      
      invariant(
      
      2 + 2 === 4,
      
      'You shall not pass!'
      
      );
      
      当条件为false时, 这个方法会直接抛出异常.
      
      “Invariant” 就是说这个条件为真, 你可以认为他就是做了个断言.
      
      保持开发环境和生产环境一致是很重要的, 因此invariant在生产环境和开发环境都可以抛出异常. 生产环境下的错误消息被自动替换成错误码, 以防增加代码体积.
      
      Development and Production
      
      你可以使用__DEV__这个为全局变量指定仅仅在开发环境才执行的代码块.
      
      他是在编译过程中工作的, 他是在commonjs编译的时候检查process.env.NODE_ENV !== 'production'这个值.
      
      单独编译的时候, 他在未压缩版是true, 在压缩版直接被去掉了.
      
      if (__DEV__) {
      
      // 这里边的代码只会带开发环境执行
      
      }
      
      Flow
      
      我们最近开始引入flow做静态类型检查, 在文件头的注释里标注了@flow的使用了类型检查.
      
      我们接受在现有代码加入flow类型检查的pull request (不错哎, 可以试着提个pull request哦). Flow的签名类似下面这样.
      
      ReactRef.detachRefs = function(
      
      instance: ReactInstance,
      
      element: ReactElement | string | number | null | false,
      
      ): void {
      
      // ...
      
      }
      
      时机成熟的时候, 新代码要用Flow 签名, 你可以在本地运行yarn flow用Flow检查你的代码.
      
      动态植入
      
      react在一些模块使用了动态植入. 但是这个东西不太好, 因为他让代码比较难理解了. 他存在的理由是react一开始只把支持dom作为目标的. 但是后来杀出了个React Native, 他是基于react的, 我们不得不加入动态植入好让react native 重载一些行为.
      
      你可能会看到模块像下面这样声明它的动态依赖
      
      // Dynamically injected
      
      var textComponentClass www.gcyL157.com= null;
      
      // Relies on dynamically injected value
      
      function createInstanceForText(text) {
      
      return new textComponentClass(www.mingcheng178.com text);
      
      }
      
      var ReactHostComponent www.yongshiyule178.com = {
      
      createInstanceForText,
      
      // Provides an opportunity for dynamic injection
      
      injection: {
      
      injectTextComponentClass: function(componentClass) {
      
      textComponentClass = componentClass;
      
      },
      
      },
      
      };
      
      module.exports = ReactHostComponent;
      
      注入的部分没有以任何方式特殊处理. 但是规定, 它的意思是这个模块想在运行时有一些依赖(可能是平台特定的)被注入进去.
      
      代码里边有几个注入的入口. 未来, 我们将废弃掉这种动态植入的机制, 方案是在编译时以静态方式处理他们.
      
      多包
      
      react是个monorepo, 他的仓库包含了多个独立的包, 因此他们的修改可以合在一起, 而且issues也可以放在一个地方.
      
      React核心
      
      react的核心是所有顶级api, 包括:
      
      React.createElement()
      
      React.Component
      
      React.Children
      
      react核心只包括定义组件必要的api, 并不包括reconciliation算法和平台特定代码. React DOM和React Native都使用了他们.
      
      react核心的相关代码在packages/react里边. npm使用时在react这个包里边, 浏览器版的是react.js, 他挂载一个被称为React的全局变量.
      
      Renderers
      
      react起初是为DOM创造的, 但是后台通过RN被用来支持原生环境了. 这里介绍加react内部的“renderers”的理念.
      
      “renderers”管理了react树如何变成平台可调用的东西.
      
      Renderers也在packages里边
      
      React DOM Renderer 把react 组件渲染进 DOM. 他实现了顶级的ReactDOM APIs, 在react-dom这个npm包里被暴露出来. 浏览器版叫react-dom.js, 通过ReactDOM这个全局变量暴露出来.
      
      React Native Renderer把react组件渲染到原生视图层里. 他被RN内部使用.
      
      React Test Renderer 把react组件渲染成JSON树, 他被Jest的一个特性Snapshot Testing使用, 在react-test-renderer这个npm包里可用.
      
      另一个官方唯一支持的渲染器是react-art, 他曾经是个独立的库, 现在被移进来了.
      
      注意
      
      技术上react-native-renderer是很薄的一层, 只是用来和RN的实现相互配合, 真正的平台相关代码是RN库里一些native view.
      
      Reconcilers(协调器)
      
      相当多的渲染器, 如Reat DOM, React Native 需要共享一套逻辑. 尤其reconciliation算法需要足够的相似, 以便让rendering, 自定义组件, 状态, 生命周期函数和refs能跨平台工作.
      
      为了解决这个问题, 不同的渲染器共用一些代码. 我们把React 中的这个部分叫做"reconciler". 当一个更新比如setState要执行了,Reconcilers就去在组件上调用render(), 然后mounts, updates, 或者unmounts他们.
      
      Reconcilers没有独立成包, 因为他现在还没有公共API. 相反, 他仅仅是在渲染器被使用, 比如React DOM , React Native.
      
      Stack Reconciler
      
      Stack Reconciler 是在react15之前实现使用的, 现在已经不用了, 但是下一部分的文档还会有详细的介绍.
      
      Fiber Reconciler
      
      "Fiber"是为了解决stack reconciler固有问题和修复长期存在的bug所做的努力, 他从react16开始成为默认的Reconciler.
      
      他的主要目标是:
      
      在chunks里分离可中断的工作
      
      在过程中重建, 重用work或者改变他的优先级(瞎翻译的)的能力
      
      在父子组件前进或回退以只是react中的布局的能力
      
      在render方法里返回多个元素的能力
      
      更好的支持错误边际
      
      你可在这里和这里阅读更多关于Fiber架构的相关信息. 但是React16对他做了封装, 默认不支持异步特性了.
      
      他的源码在packages/react-reconciler里边.
      
      事件系统
      
      react实现了一个对renders透明的事件系统, 这个系统被用于react dom 和react native. 源码在packages/events;
      
      这里有个视频https://www.youtube.com/watch?v=dRo_egw7tBc

    展开全文
  • reactjs源码分析

    千次阅读 2015-09-16 00:31:04
    前端的发展特别快,经历过jQuery一统天下的工具库时代后,现在各种框架又开始百家争鸣了。angular,ember,backbone,vue,avalon,ploymer还有reactjs,作为一个前端真是稍不留神就感觉要被淘汰了,就在去年大家还...
  • 初入React源码(一)

    2019-06-10 17:09:39
    React是我接触的第二个框架,我最初开始接触的是vue,但是并没有深入的理解过vue,然后在工作过程中,我开始使用了React,现在已经觉得React会比vue更加实用,但是这只是个人观点,可能只是我更喜欢React的写法吧,...
  • javascript前端框架,react Facebook官方源码,通过查看源码分析react生命周期,每个钩子函数执行时机。
  • 源码分析是大多数开发者忽略的问题,然而想要在技术上有更深的造诣,源码分析必不可少! React 项目托管在 GitHub 项目开发指引也可见官网 How to Contribute 文章使用的版本为v16.8.6 开始 克隆代码到本地 git ...
  • React 把组件看作状态机(有限状态机), 使用state来控制本地状态, 使用props来传递状态. 前面我们探讨了 React 如何映射状态到 UI 上(初始渲染), 那么接下来我们谈谈 React 时如何同步状态到 UI 上的, 也就是: React...
  • React源码学习

    2019-03-24 12:13:40
    React源码学习(1) React源码学习-创建组件过程(2) React源码学习-解密setState(3) redux源码解析之createStore.js redux源码解析 前言 Virtual Dom实际上是在浏览器端用JavaScript实现的DOM API,它之与...
  • React-从源码分析React Fiber工作原理

    千次阅读 2018-04-28 11:46:53
    本文的demo仓库在...为什么要重写React React16 以前 React16 以前,对virtural dom的更新和渲染是同步的。就是当一次更新或者一次加载开始以后,diff virtual dom并且渲染的过程是一口气完成的。如果组件...
  • 最近换工作,发现几乎都有问到react源码,索性开始学习,对于一个菜鸟说来,难度很大,一开始不知道怎么开始,搜了很多文章,最后发现一个作者的源码解读专栏非常适合我,本节内容地址:... 学完后为了理清思路和记录...
  • React源码阅读:概况

    2019-02-28 09:11:20
    前言 本文主要介绍一些React的设计思想和相关...React源码阅读:虚拟DOM的初始化 项目结构 React的相关代码都放在packages里。 ├── packages ------------------------------- React实现的相关代码 │ ├── ...
  • 在不久前, v17.0.0-alpha.0 已经...对于想学习 React 源码的同学来说,这同时是 机遇 与 挑战 。 挑战是: React 内部运行流程真的很复杂。 机遇是:学懂之后,即使除去 前端领域 的知识外,你的收获将不限于: 编.
  • 前几天有小伙伴和我聊天,谈到现在前端面试越来越难,动不动就是xxx原理,有没有看过xx源码之类的问题, 之后就问我应该怎么来学习现在主流框架的源码,于是有了这...React源码获取 在这里我选择用的的版本是16.10.0 ,
  • 阅读源码成了今年的学习目标之一,在选择 Vue 和 React 之间,我想先阅读 React 。 在考虑到读哪个版本的时候,我想先接触到源码早期的思想可能会更轻松一些,最终我选择阅读 0.3-stable 。 那么接下来,我将从几个...
  • React源码结构树: 在这些目录结构中,renderers是React代码的核心部分,它包含了大部分功能的实现。renderers源码目录:在renderers中,reconciler(协调器)是最核心的部分,包含React中自定义组件的实现、组件...
  • React源码分析1 -- 框架

    千次阅读 2017-03-02 16:33:14
    1 源码结构我们分析的React源码version为16.0.0-alpha.3,源码目录如下图所示。含义如下 addons:插件,一些比较有用的工具,如transition动画 isomorphic: 同构,服务端在页面初次加载时,将所有方法渲染好,一次性...
  • 这是 React 源码解析系列第一篇文章,系列文章基于 v16.8.6版本。 Hooks 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 Hooks 非常简单,比如 useState 可以在 ...
  • 阅读源码成了今年的学习目标之一,在选择 Vue 和 React 之间,我想先阅读 React 。 在考虑到读哪个版本的时候,我想先接触到源码早期的思想可能会更轻松一些,最终我选择阅读 0.3-stable 。 那么接下来,我将从几个...
1 2 3 4 5 ... 20
收藏数 30,472
精华内容 12,188
关键字:

react源码