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

    919b18f3f576d213c7d468e36ab7c297.png
    阅读源码成了今年的学习目标之一,在选择 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 源码学习(八):组件更新
    9. React 源码学习(九):“脱胎换骨”
    10. React 源码学习(十):Fiber
    11. React 源码学习(十一):Scheduling
    12. React 源码学习(十二):Reconciliation

    引发组件更新的方法就是 this.setState ,按照注释代码看来 this.setState 是不可变的,则 this._pendingState 是用来存放挂起的 state ,他不会直接更新到 this.state ,让我们来看到代码:

    // core/ReactCompositeComponent.js
    var ReactCompositeComponentMixin = {
      setState: function(partialState) {
        // 如果“挂起状态”存在,则与之合并,否则与现有状态合并。
        this.replaceState(merge(this._pendingState || this.state, partialState));
      },
      replaceState: function(completeState) {
        var compositeLifeCycleState = this._compositeLifeCycleState;
        // 生命周期校验
        invariant(
          this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
          compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
          'replaceState(...): Can only update a mounted (or mounting) component.'
        );
        invariant(
          compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
          compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
          'replaceState(...): Cannot update while unmounting component or during ' +
          'an existing state transition (such as within `render`).'
        );
    
        // 将合并完的状态给挂起状态,若不满足下面更新条件,则只存入挂起状态结束此函数
        this._pendingState = completeState;
    
        // 如果我们处于安装或接收道具的中间,请不要触发状态转换,因为这两者都已经这样做了。
        // 若复合组件生命周期不在挂载中和更新 props 时,我们会操作更新方法
        if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
            compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
          // 变更复合组件生命周期为更新 state
          this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
    
          // 准备更新 state ,并释放挂起状态
          var nextState = this._pendingState;
          this._pendingState = null;
    
          // 进入 React 调度事务,加入 _receivePropsAndState 方法
          var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
          transaction.perform(
            this._receivePropsAndState,
            this,
            this.props,
            nextState,
            transaction
          );
          ReactComponent.ReactReconcileTransaction.release(transaction);
    
          // 调度事务完成后置空复合组件生命周期
          this._compositeLifeCycleState = null;
        }
      },
    };
    

    那么到此为止,你可以知道 this.setState 并非事实更新 this.state 的,比如我们看到在 componentWillMount 中去使用 this.setState 并不会马上更新到 this.state ,那么我们继续阅读后面代码:

    // core/ReactCompositeComponent.js
    var ReactCompositeComponentMixin = {
      _receivePropsAndState: function(nextProps, nextState, transaction) {
        // shouldComponentUpdate 方法不存在或返回 true
        if (!this.shouldComponentUpdate ||
            this.shouldComponentUpdate(nextProps, nextState)) {
          // Will set `this.props` and `this.state`.
          this._performComponentUpdate(nextProps, nextState, transaction);
        } else {
          // 如果确定某个组件不应该更新,我们仍然需要设置props和state。
          // shouldComponentUpdate 返回 false 的情况
          this.props = nextProps;
          this.state = nextState;
        }
      },
      _performComponentUpdate: function(nextProps, nextState, transaction) {
        // 存入旧的 props 和 state
        // 用于传入 componentDidUpdate
        var prevProps = this.props;
        var prevState = this.state;
    
        if (this.componentWillUpdate) {
          this.componentWillUpdate(nextProps, nextState, transaction);
        }
    
        // 更新 props 和 state
        this.props = nextProps;
        this.state = nextState;
    
        // 更新组件
        this.updateComponent(transaction);
    
        if (this.componentDidUpdate) {
          transaction.getReactOnDOMReady().enqueue(
            this,
            this.componentDidUpdate.bind(this, prevProps, prevState)
          );
        }
      },
      updateComponent: function(transaction) {
        // 这里的更新比较硬核
        // 先把已渲染的旧的组件赋值至 currentComponent
        var currentComponent = this._renderedComponent;
        // 直接渲染新的组件(是不是很硬核)
        var nextComponent = this._renderValidatedComponent();
        // 如果是同样的组件则进入此判断
        // 通过 constructor 来判断是否为同一个
        // 比如:
        // React.DOM.a().constructor !== React.DOM.p().constructor
        // React.DOM.a().constructor === React.DOM.a().constructor
        // 或
        // React.createClass({ render: () => null }).constructor ===
        // React.createClass({ render: () => null }).constructor
        if (currentComponent.constructor === nextComponent.constructor) {
          // 若新的组件 props 下 isStatic 为真则不更新
          // 知道这一个可以对部分组件进行手动优化,以免不必要的计算
          if (!nextComponent.props.isStatic) {
            // 这里会调用对应的方法
            // ReactCompositeComponent.receiveProps
            // ReactNativeComponent.receiveProps
            // ReactTextComponent.receiveProps
            // 除了 ReactTextComponent 都会调用 ReactComponent.Mixin.receiveProps 来更新 ref 相关
            // 这个我们稍后来解读
            currentComponent.receiveProps(nextComponent.props, transaction);
          }
        } else {
          // These two IDs are actually the same! But nothing should rely on that.
          // 旧的 _rootNodeID 和新的 _rootNodeID
          var thisID = this._rootNodeID;
          var currentComponentID = currentComponent._rootNodeID;
          // 卸载旧组件
          currentComponent.unmountComponent();
          // 挂载新组件(也挺硬核的)
          var nextMarkup = nextComponent.mountComponent(thisID, transaction);
          // 在新 _rootNodeID 下更新 markup 标记
          ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
            currentComponentID,
            nextMarkup
          );
          // 赋值新的组件
          this._renderedComponent = nextComponent;
        }
      },
    };
    

    上面代码看来,一个是不替换组件的情况下更新组件,另一个则是直接更新 markup 标记。我们按照顺序一个个看过来吧,先看到 ReactCompositeComponent.receiveProps

    // core/ReactCompositeComponent.js
    var ReactCompositeComponentMixin = {
      receiveProps: function(nextProps, transaction) {
        // 校验参数
        if (this.constructor.propDeclarations) {
          this._assertValidProps(nextProps);
        }
        // 更新 ref
        ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
    
        // 更新复合组件生命周期为更新 props
        this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
        // 执行钩子函数,在这个函数内执行 this.setState 是不会立即更新 this.state 的
        if (this.componentWillReceiveProps) {
          this.componentWillReceiveProps(nextProps, transaction);
        }
        // 进入复合组件生命周期更新 state
        this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
        // When receiving props, calls to `setState` by `componentWillReceiveProps`
        // will set `this._pendingState` without triggering a re-render.
        // 如果上面执行过 componentWillReceiveProps ,并且里面操作了 this.setState
        // 那么 this._pendingState 会有值,并且是与 this.state 合并过的
        var nextState = this._pendingState || this.state;
        // 释放 this._pendingState
        this._pendingState = null;
        // 执行的是 currentComponent._receivePropsAndState 方法
        // 但是这个 currentComponent 一定是 ReactCompositeComponent
        this._receivePropsAndState(nextProps, nextState, transaction);
        // 置空复合组件生命周期
        this._compositeLifeCycleState = null;
      },
    };
    

    再是我们来看看 ReactNativeComponent.receiveProps

    // core/ReactNativeComponent.js
    ReactNativeComponent.Mixin = {
      receiveProps: function(nextProps, transaction) {
        // 日常校验
        invariant(
          this._rootNodeID,
          'Trying to control a native dom element without a backing id'
        );
        assertValidProps(nextProps);
        // 日常更新 ref
        ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
        // 重点来了,更新 DOM 属性
        this._updateDOMProperties(nextProps);
        // 更新 DOM 子节点
        this._updateDOMChildren(nextProps, transaction);
        // 都执行完后更新 props
        this.props = nextProps;
      },
      _updateDOMProperties: function(nextProps) {
        // 这里开始解读更新 DOM 属性
        // 保存旧 props
        var lastProps = this.props;
        // 遍历新 props
        for (var propKey in nextProps) {
          var nextProp = nextProps[propKey];
          var lastProp = lastProps[propKey];
          // 以新 props 键为准取对应的值
          // 若 2 个值相等则跳过
          if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
            continue;
          }
          // CSS 样式
          if (propKey === STYLE) {
            if (nextProp) {
              nextProp = nextProps.style = merge(nextProp);
            }
            var styleUpdates;
            // 遍历 nextProp
            for (var styleName in nextProp) {
              if (!nextProp.hasOwnProperty(styleName)) {
                continue;
              }
              // 旧的 styleName 与新的 styleName 值不同时
              // 将新的值加入 styleUpdates
              if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
                if (!styleUpdates) {
                  styleUpdates = {};
                }
                styleUpdates[styleName] = nextProp[styleName];
              }
            }
            // 操作更新 CSS 样式
            if (styleUpdates) {
              // ReactComponent.DOMIDOperations => ReactDOMIDOperations
              // 他会通过 ID 对真实 node 进行相应的更新
              ReactComponent.DOMIDOperations.updateStylesByID(
                this._rootNodeID,
                styleUpdates
              );
            }
            // 判断若是 dangerouslySetInnerHTML 则在不同的情况下进行相应的更新
          } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
            var lastHtml = lastProp && lastProp.__html;
            var nextHtml = nextProp && nextProp.__html;
            if (lastHtml !== nextHtml) {
              ReactComponent.DOMIDOperations.updateInnerHTMLByID(
                this._rootNodeID,
                nextProp
              );
            }
            // 判断 content 的情况更新
          } else if (propKey === CONTENT) {
            ReactComponent.DOMIDOperations.updateTextContentByID(
              this._rootNodeID,
              '' + nextProp
            );
            // 对事件进行监听
            // 比较好奇的是旧的 propKey 若存在着事件监听,这里似乎没有做什么处理
            // 这样不就内存溢出了吗?难道说不会有这种情况???
            // 想多了啦,更新 props 的情况,同样的事件会被覆盖
            // 在对应 this._rootNodeID 的情况下。(希望如此,没有证实过,但是理解如此)
          } else if (registrationNames[propKey]) {
            putListener(this._rootNodeID, propKey, nextProp);
          } else {
            // 剩余的就是更新 DOM 属性啦
            ReactComponent.DOMIDOperations.updatePropertyByID(
              this._rootNodeID,
              propKey,
              nextProp
            );
          }
        }
      },
      _updateDOMChildren: function(nextProps, transaction) {
        // 来更新 DOM 子节点了
        // 当前 this.props.content 类型
        var thisPropsContentType = typeof this.props.content;
        // 是否 thisPropsContentEmpty 为空
        var thisPropsContentEmpty =
          this.props.content == null || thisPropsContentType === 'boolean';
        // 新的 nextProps.content 类型
        var nextPropsContentType = typeof nextProps.content;
        // 是否 nextPropsContentEmpty 为空
        var nextPropsContentEmpty =
          nextProps.content == null || nextPropsContentType === 'boolean';
    
        // 最后使用的 content :
        // 若 thisPropsContentEmpty 不为空则取 this.props.content 否则
        // this.props.children 类型为 string 或 number 的情况下取 this.props.children 否则
        // null
        var lastUsedContent = !thisPropsContentEmpty ? this.props.content :
          CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
    
        // 使用内容 content :
        // 若 nextPropsContentEmpty 不为空则取 nextProps.content 否则
        // nextProps.children 类型为 string 或 number 的情况下取 nextProps.children 否则
        // null
        var contentToUse = !nextPropsContentEmpty ? nextProps.content :
          CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
    
        // Note the use of `!=` which checks for null or undefined.
    
        // 最后使用的 children :
        // 若 lastUsedContent 不为 null or undefined 则取 null 否则
        // 取 this.props.children ,以 content 优先
        var lastUsedChildren =
          lastUsedContent != null ? null : this.props.children;
        // 使用 children :
        // 若 contentToUse 不为 null or undefined 则取 null 否则
        // 取 nextProps.children ,以 content 优先
        var childrenToUse = contentToUse != null ? null : nextProps.children;
    
        // 需要使用 content 情况
        if (contentToUse != null) {
          // 是否需要移除 children 判断结果:
          // 最后使用的 children 存在并且 children 不再需要使用
          var childrenRemoved = lastUsedChildren != null && childrenToUse == null;
          if (childrenRemoved) {
            // 更新子节点
            this.updateMultiChild(null, transaction);
          }
          // 若没满足上面条件则说明不需要更新掉 children
          // 并且新旧 content 不相等的情况下进行 DOM 操作
          if (lastUsedContent !== contentToUse) {
            ReactComponent.DOMIDOperations.updateTextContentByID(
              this._rootNodeID,
              '' + contentToUse
            );
          }
        } else {
          // 反之看是否需要移除 content
          // 若最后使用的 content 存在且 content 不再需要使用
          var contentRemoved = lastUsedContent != null && contentToUse == null;
          if (contentRemoved) {
            // 进行 DOM 操作
            ReactComponent.DOMIDOperations.updateTextContentByID(
              this._rootNodeID,
              ''
            );
          }
          // 更新子节点
          // 压扁更新,与挂载时一样
          this.updateMultiChild(flattenChildren(nextProps.children), transaction);
        }
      },
    };
    

    关于 DOM 操作一系列的方法这里不准备做解读,可以直接查看源码 core/ReactDOMIDOperations.js ,道理都是一样的。但是,这里需要看下 updateMultiChild 方法,因为这里已经涉及到 Diff 实现,但是在讲 Diff 之前,我们先把 ReactTextComponent.receiveProps 给解读掉,其实方法里面很简单,就是操作了 ReactDOMIDOperations 相关的方法,具体实现直接看源码就行,那么接下来,我们来看到 updateMultiChild

    // core/ReactMultiChild.js
    // 直接看到 updateMultiChild
    var ReactMultiChildMixin = {
      enqueueMarkupAt: function(markup, insertAt) {
        this.domOperations = this.domOperations || [];
        this.domOperations.push({insertMarkup: markup, finalIndex: insertAt});
      },
      enqueueMove: function(originalIndex, finalIndex) {
        this.domOperations = this.domOperations || [];
        this.domOperations.push({moveFrom: originalIndex, finalIndex: finalIndex});
      },
      enqueueUnmountChildByName: function(name, removeChild) {
        if (ReactComponent.isValidComponent(removeChild)) {
          this.domOperations = this.domOperations || [];
          this.domOperations.push({removeAt: removeChild._domIndex});
          removeChild.unmountComponent && removeChild.unmountComponent();
          delete this._renderedChildren[name];
        }
      },
    
      /**
       * Reconciles new children with old children in three phases.
       *
       * - Adds new content while updating existing children that should remain.
       * - Remove children that are no longer present in the next children.
       * - As a very last step, moves existing dom structures around.
       * - (Comment 1) `curChildrenDOMIndex` is the largest index of the current
       *   rendered children that appears in the next children and did not need to
       *   be "moved".
       * - (Comment 2) This is the key insight. If any non-removed child's previous
       *   index is less than `curChildrenDOMIndex` it must be moved.
       *
       * @param {?Object} children Flattened children object.
       */
      updateMultiChild: function(nextChildren, transaction) {
        // 一些补全判断操作
        if (!nextChildren && !this._renderedChildren) {
          return;
        } else if (nextChildren && !this._renderedChildren) {
          this._renderedChildren = {}; // lazily allocate backing store with nothing
        } else if (!nextChildren && this._renderedChildren) {
          nextChildren = {};
        }
        // 用于更新子节点时,记录的父节点 ID 前缀加 dot
        var rootDomIdDot = this._rootNodeID + '.';
        // DOM markup 标记缓冲
        var markupBuffer = null;  // Accumulate adjacent new children markup.
        // DOM markup 标记缓冲等待插入的数量
        var numPendingInsert = 0; // How many root nodes are waiting in markupBuffer
        // 新子节点的循环用索引 index
        var loopDomIndex = 0;     // Index of loop through new children.
        var curChildrenDOMIndex = 0;  // See (Comment 1)
        // 遍历新的 children
        for (var name in nextChildren) {
          if (!nextChildren.hasOwnProperty(name)) {continue;}
          var curChild = this._renderedChildren[name];
          var nextChild = nextChildren[name];
          // 通过 constructor 来判断 curChild 和 nextChild 是否为同一个
          if (shouldManageExisting(curChild, nextChild)) {
            if (markupBuffer) {
              // 若 DOM markup 标记缓冲存在,将其加入队列
              // 标记位置为 loopDomIndex - numPendingInsert
              // 这里和下面是一样的道理,请看到循环结束后
              this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
              // 清空 DOM markup 标记缓冲
              markupBuffer = null;
            }
            // 初始化 DOM markup 标记缓冲等待插入的数量为 0
            numPendingInsert = 0;
            // _domIndex 在挂载中依次按照顺序进行排序,若他小于目前的子节点顺序
            // 则进行移动操作,移动操作则是记录原 index 和现 index (也就是新子节点的循环用索引 index )
            if (curChild._domIndex < curChildrenDOMIndex) { // (Comment 2)
              // 我没有办法联想到此情况
              this.enqueueMove(curChild._domIndex, loopDomIndex);
            }
            // curChildrenDOMIndex 则取大值
            curChildrenDOMIndex = Math.max(curChild._domIndex, curChildrenDOMIndex);
            // 硬核式递归更新!!同样会进入到 Diff
            !nextChild.props.isStatic &&
              curChild.receiveProps(nextChild.props, transaction);
            // 更新 _domIndex 属性
            curChild._domIndex = loopDomIndex;
          } else {
            // 若 curChild 和 nextChild 不为同一个的时候
            if (curChild) {               // !shouldUpdate && curChild => delete
              // 卸载旧子节点加入队列,并操作卸载组件
              this.enqueueUnmountChildByName(name, curChild);
              // curChildrenDOMIndex 则取大值
              curChildrenDOMIndex =
                Math.max(curChild._domIndex, curChildrenDOMIndex);
            }
            if (nextChild) {              // !shouldUpdate && nextChild => insert
              // 对应位置传入新子节点
              this._renderedChildren[name] = nextChild;
              // 生成新的 markup 标记
              // ID 为父 ID 加 dot 加现在的 name
              var nextMarkup =
                nextChild.mountComponent(rootDomIdDot + name, transaction);
              // 累加 DOM markup 标记缓冲
              markupBuffer = markupBuffer ? markupBuffer + nextMarkup : nextMarkup;
              // DOM markup 标记缓冲等待插入的数量
              numPendingInsert++;
              // 新的子节点 _domIndex 更新
              nextChild._domIndex = loopDomIndex;
            }
          }
          // 若新子节点存在,则新子节点的循环用索引 index 累加 1
          loopDomIndex = nextChild ? loopDomIndex + 1 : loopDomIndex;
        }
        if (markupBuffer) {
          // 将 DOM markup 标记缓冲加入队列
          // 这里的 loopDomIndex - numPendingInsert 可以解释下
          // 会使得 markupBuffer 存在的情况就是进入第二个分支,那么同样的,
          // 会使得 numPendingInsert 增加的情况也是第二个分支,那么在这里插入的 DOM markup 标记
          // 是最后插入的,他需要从整个循环 DOM 索引减去等待数量来确定插入位置
          // 举个例子,你在进入第二个分支时,旧节点存在的情况下一定会被移除
          // 新节点存在的情况下一定会被生成 DOM markup 标记 并且累加相应的数量
          // loopDomIndex 也会随之增加,loopDomIndex 也一定大于等于 numPendingInsert
          // 如:旧节点 <div></div><p></p>
          // 新节点 <div></div><span></span><p></p>
          // 这种情况下 loopDomIndex 为 3 , numPendingInsert 为 2 ,插入位置为 1
          this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
        }
        // 遍历旧 children
        for (var childName in this._renderedChildren) { // from other direction
          if (!this._renderedChildren.hasOwnProperty(childName)) { continue; }
          var child = this._renderedChildren[childName];
          if (child && !nextChildren[childName]) {
            // 旧的存在,新的不存在加入队列
            this.enqueueUnmountChildByName(childName, child);
          }
        }
        // 执行 DOM 操作队列
        this.processChildDOMOperationsQueue();
      },
      processChildDOMOperationsQueue: function() {
        if (this.domOperations) {
          // 执行队列
          ReactComponent.DOMIDOperations
            .manageChildrenByParentID(this._rootNodeID, this.domOperations);
          this.domOperations = null;
        }
      },
    };
    

    在上面这个执行队列,我们需要看到相关的 DOM 操作:

    // domUtils/DOMChildrenOperations.js
    var MOVE_NODE_AT_ORIG_INDEX = keyOf({moveFrom: null});
    var INSERT_MARKUP = keyOf({insertMarkup: null});
    var REMOVE_AT = keyOf({removeAt: null});
    
    var manageChildren = function(parent, childOperations) {
      // 用于获得 DOM 中原生的 Node
      // 符合 MOVE_NODE_AT_ORIG_INDEX 和 REMOVE_AT
      var nodesByOriginalIndex = _getNodesByOriginalIndex(parent, childOperations);
      if (nodesByOriginalIndex) {
        // 移除对应的 Node
        _removeChildrenByOriginalIndex(parent, nodesByOriginalIndex);
      }
      // 对应的插入
      _placeNodesAtDestination(parent, childOperations, nodesByOriginalIndex);
    };
    

    那么到此, Diff 实现算是解读完成,最后关于 ref 我们在这里也直接解读掉, ref 为引用,看到官方注释:“ ReactOwners are capable of storing references to owned components. ”,那么首先我们得知道 [OWNER] 是什么,他是:“引用组件所有者的属性键。”,那么他的值就是该组件的所有者(也就是父组件实例),这句话的依据在哪里呢?

    // core/ReactCompositeComponent.js
    var ReactCompositeComponentMixin = {
      _renderValidatedComponent: function() {
        // render 方法执行前,我们将 this 也就是当前复合组件传入 ReactCurrentOwner.current
        // render 方法执行结束后,我们将置空 ReactCurrentOwner.current
        ReactCurrentOwner.current = this;
        var renderedComponent = this.render();
        ReactCurrentOwner.current = null;
        return renderedComponent;
      },
    };
    

    那么执行 render 方法时,发生了什么?回忆一下。返回的是 ReactCompositeComponent 或者 ReactNativeComponent 或者 ReactTextComponent ,那么他们在被实例化的过程中获得了 ReactCurrentOwner.current

    // core/ReactComponent.js
    var ReactComponent = {
      Mixin: {
        construct: function(initialProps, children) {
          // Record the component responsible for creating this component.
          // 记录负责创建此组件的组件。
          // 将其记录下来。
          this.props[OWNER] = ReactCurrentOwner.current;
        },
      }
    };
    

    那么讲了这么多,他和 ref 有什么关系呢,那还确实有关系。在挂载、更新、卸载组件时都会发生 ref 的更新,若你对子组件添加了 ref 属性,那么他对应的键会出现在他拥有者的 this.refs 上,那么你就可以通过拥有者调用引用上的方法。

    那么到此,实现组件更新。

    展开全文
  • react-native-global-props 一个简单的javascript实现,可将自定义默认道具添加到react-native组件。 是否曾经希望您可以设置适用于像CSS这样的react-native组件的全局样式? body { background-color : 'teal' }...
  • React源码分析

    2018-04-02 03:43:36
    组件是什么样的? 下面是一个简单的组件 ...这是因为我们在创建组件的时候,使用了extends.React.Component,即继承了Component,看下Component的源码 function Component(props, context, updater) { t...

    组件是什么样的?

    下面是一个简单的组件 打印一下这个组件,发现其实是一个js对象,而不是真实的dom。 那么react是如何将react组件生成js对象的呢? 这是因为我们在创建组件的时候,使用了extends.React.Component,即继承了Component,看下Component的源码

    function Component(props, context, updater) {
      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      // renderer.
      this.updater = updater || ReactNoopUpdateQueue;
    }
    Component.prototype.setState = function (partialState, callback) {}
    Component.prototype.forceUpdate = function (callback) {}
    复制代码

    我们声明了一个组件,继承了Component,它的原型上有setState等方法。 小结

    组件初始化

    我们在react类和我们平时写的js类都一样,唯一的区别在与react类多了一个render()方法,输出类似“这是A组件”的结构并挂载到真实DOM上,才能触发组件的生命周期并成为DOM树的一部分。 首先我们看下render()方法

    发现render()实际是是调用一个React.creatEment('div',null),实际是实际是ReactElement方法 这个方法的作用是:每一个组件的对象都是通过React.creatEment()创建出来的ReactElement类型的对象,ReactElement对象记录了组件的特征,比如key,type,ref,props等,这些特征与渲染成页面息息相关。 下图是ReactElement方法源码:

    小结

    组件的挂载

    我们知道react是通过ReactDOM.render(component,monutNode)的形式进行挂载的(可以挂载自定义组件,原生dom,字符串) 挂载过程是怎么样的呢?

    ReactDOM.render实际调用了内部的ReactMount.render,进而执行ReactMount._renderSubtreeIntoContainer。从字面意思上就可以看出是将"子DOM"插入容器的逻辑,我们看下源码实现:

    render: function (element, container, callback) {
        return renderSubtreeIntoContainer(null, element, container, false, callback);
      },
      function renderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback){}
    复制代码

    我们看下renderSubtreeIntoContainer的入参:

    parentComponent:当前组件的父组件,第一次渲染时为null children:要插入DOM中的组件,如helloWorld container:要插入的容器,如document.getElementById('root')

    小结

    组件的生命周期

    我们知道,根据ReactDOM.render()的入参类型不同,在内部通过工厂方法生成四种不同类型的封装组件:

    ReactEmptyComponent(空对象)

    ReactTextComponent(字符串)

    ReactDOMComponent(原生dom)

    ReactCompositeComponent(自定义组件,这个类型才会有生命周期)

    在执行挂载流程时,通过执行每种封装组件内部的mountComponent方法触发生命周期,但显然生命周期只在React自定义组件中存在,也就是ReactCompositeComponent。

    小结

    转载于:https://juejin.im/post/5abe05ea5188255c61631d6c

    展开全文
  • React源码浅析

    2020-08-08 13:26:53
    class HelloMessage extends React.Component { render() { return ( <div> Hello {this.props.name} </div> ); } } ReactDOM.render( <HelloMessage name="Taylor" />, document....

    0、虚拟dom与diff算法

    什么是虚拟dom

    用JavaScript对象表示DOM信息和结构,当状态变更的时候,重新渲染这个JavaScript的对象结构,这个JavaScript对象称为virtual dom;

    为什么使用虚拟dom

    DOM操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能.相对于DOM对象,js对象处理起来更快,而且更贱哒.通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行dom操作,从而提高性能.

    虚拟dom如何工作

    react中用jsx语法描述试图,通过babel-loader转译后他们变成React.c re a te E le ment()形式,该函数将生成vdom来描述真是dom.将来如果有状态变化,vdom将作出相应的变化,再通过diff算法对比新旧vdom区别,从而作出最终dom操作

    diff算法策略
    1、同级比较,web ui中dom节点跨层级的移动操作特别少,可以忽略不计
    0
    2、拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的属性结构 例如div->p, CompA->CompB
    3、对于同一层级的一组子节点,通过唯一的key进行区分

    当节点处于同一层级时,Diff提供三种DOM操作:删除、移动、插入。
    01
    如图2所示,首先将OldVnode 和 NewVnode的首尾位置分别标记为oldS、oldE、newS、newE。

    (1) oldS和newS相同,不发生变化,oldS++,newS++。

    oldS = a,oldE = d
    newS = a, newE = c

    (2) newS与OldVnode不匹配,oldS前面插入f,newS++。

    oldS = b,oldE = d
    newS = f, newE = c

    (3) newS与oldE相同,oldE移动到oldS前面,newS++,oldE–。

    oldS = b,oldE = d
    newS = d, newE = c

    (4) newE与oldE相同,不发生变化,newE–,oldE–。

    oldS = b,oldE = c
    newS = e, newE = c

    (5) 都不相同,oldS前插入newE,删除oldS,oldS++,newS++,newE–,oldE–。

    oldS = b,oldE = b
    newS = e, newE = e

    (6) oldS > oldE,Diff结束,最后结果为:a、f、d、e、c。
    参考

    1、JSX与JS对比

    代码来源
    JSX写法

    class HelloMessage extends React.Component {
      render() {
        return (
          <div>
            Hello {this.props.name}
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <HelloMessage name="Taylor" />,
      document.getElementById('hello-example')
    );
    

    JS写法

    class HelloMessage extends React.Component {
      render() {
        return React.createElement(
          "div",
          null,
          "Hello ",
          this.props.name
        );
      }
    }
    
    ReactDOM.render(React.createElement(HelloMessage, { name: "Taylor" }), document.getElementById('hello-example'));
    

    其中对比我们发现Reac转换t中比较重要的两个方法是React.createElement、ReactDOM.render,下面我就就分别对两个方法进行分析.

    2、React.createElement

    创建kreact.js代码如下,代码中打印数据如图1

    // kreact.js
    function createElement(type, props, ...children) {
        props.children = children;
        delete props._source;
        delete props._self;
        // type: 标签类型如div
        // vtype: 组件的类型
        let vtype;
        if (typeof type === "string") {
          vtype = 1;
        } else if (typeof type === "functio") {
          if (type.isClassComponent) {
            // 类组件
            vtype = 2;
          } else {
            // 函数组件
            vtype = 3;
          }
        } 
        return createVNode(vtype, type, props)
    }
    export default {createElement}
    export class Component {
      // 区分组件是function还是class
      static isClassComponent = true;
      constructor(props) {
        this.props = props;
        this.state = {};
      }
      setState() {
    
      }
    }
    

    图1

    3、ReactDOM.render

    我们把其中虚拟dom转dom的代码单独提取出来,那这里面的源码稍微简单

    // kreact-domjs
    function render(vnode, container) {
      const node = initVNode(vnode);
      container.appendChild(node);
    }
    export default {render};
    
    

    虚拟dom单独提取成initVNode

    
    // kvdom.js
    // vtype元素类型: 1-html元素  2-function组件  3-class组件
    export function createVNode(vtype, type, props) {
      const vnode = {vtype, type, props}
      return vnode
    }
    
    // 转换虚拟dom为dom
    export function initVNode(vnode) {
      const {vtype} = vnode;
      if(!vtype) {
        // 文本节点
        return document.createTextNode(vnode);
      }
      if (vtype === 1) {
        // 原生元素
        return createElement(vnode)
      } else if (vtype === 2) {
        // class元素
        return createClassComp(vnode)
      } else if (vtype === 3) {
        // function元素
        return createFuncComp(vnode)
      }
    }
    
    
    function createElement() {
      // 根据type创建元素
      const { type, props } = vnode;
      const node = document.createElement(type);
    
      // 处理属性
      const { key, chidren, ...rest } = props;
      Object.keys(rest).forEach((k) => {
        // 处理特别属性名: className
        if (k === "className") {
          node.setAttribute("class", rest[k]);
        } else if (k === "style" && typeof rest[k] === "object") {
          const style = Object.keys(rest[k])
            .map((s) => s + ":" + rest[k][s])
            .join(";");
          node.setAttribute("style", style);
        } else if (k.startsWith("on")) {
          // conClick
          const event = k.toLowerCase();
          node[event] = rest[k];
        } else {
          node.setAttribute(k, rest[k]);
        }
      });
      // 递归自元素
      chidren.forEach((c) => {
        if (Array.isArray(c)) {
          c.forEach((n) => node.appendChild(initVNode(n)));
        } else {
          node.appendChild(initVNode(c));
        }
        node.appendChild(initVNode(v));
      });
      return node;
    }
    // class组件转换
    function createClassComp() {
      // type是class组件声明
      const {type, props} = vnode;
      const component = new type(props);
      const vdom = component.render();
      return initVNode(vdom)
    }
    function createFuncComp() {
      // type是函数
      const {type, props} = vnode;
      const vdom = type(props);
      return initVNode(vdom);
    }
    

    那么到这里一个简易版的react解析就完成了,后面有机会再补充下…

    展开全文
  • React源码理解

    2018-03-21 14:19:00
    ,在相应的js中会有一个React.createElement的方法,接受component、props... children的参数,还有ref、key等属性,使用js构建出一个虚拟dom。 虚拟dom:在js中构造出dom,用js对dom进行增删改...

    渲染阶段   

    React中的jsx文件,babel会将jsx转化成js。

    假设在React中render一个<div>,在相应的js中会有一个React.createElement的方法,接受component、props... children的参数,还有ref、key等属性,使用js构建出一个虚拟dom。

    虚拟dom:在js中构造出dom,用js对dom进行增删改,最后将修改的部分应用到真实dom中。

    构造出虚拟的dom后,调用instantiateReactComponent的方法,这个方法会根据传入的vdom的类型,3种类型,1.string\value类型 2.原生的类型例如div 3.自定义类型

    构建出相应的dom后,调用mountComponent方法,最终将产生的添加到相应的节点中。

    createInstanceForText();
    ReactCompositeComponentWrapper是一个工厂类

    觉得写的比较清晰的一篇文章: https://www.jianshu.com/p/2a6fe61d918c

     

    setState之后阶段

    1.通过ReactInstanceMap方法获得相应渲染对象。

    2.对渲染对象的_pendingStateQueue操作,将需要更新的状态push进去。

    3.检查是否处于一个批量更新的状态,若处于批量更新状态,则将该渲染对象放入dirtyComponent中。若不处于,则调用ReactDefaultBatchingStrategyTransaction开启一个新的事物,进行vdom的比较。

    4.internalInstance.updateComponent(partialState);

    可参考链接:https://github.com/sixwinds/blog/issues/10

     

    转载于:https://www.cnblogs.com/bounceFront/p/8616755.html

    展开全文
  • ReactElement算是React源码中比较简单的部分了,直接看源码: var ReactElement = function(type, key, ref, self, source, owner, props) { var element = { // This tag allow us to uniquely identify ...
  • React源码解析(一)

    2020-03-08 23:35:08
    React源码解析(一) github地址 要点掌握:三个API作用,Component、render、createElement jsx模板渲染的过程: createElement function createElement(type, props, ...children) { if (props) { delete ...
  • 首先我们回顾一下 defaultProps 的用法 import React from 'react';...{this.props.name}</div> } } App.defaultProps = { name: "Hello React" }; ReactDOM.render(<App />, document.getE
  • React源码之ReactElement

    2019-11-18 10:33:38
    import React from "react"; class Aa extends React.Component{ render() { return ( ... 1111 {this.props.children} </div> ); } } function App() { return ( <div class...
  • tcomb-react - Library allowing you to check all the props of your React components react-responsive - Media queries in react for responsive design react-cursor - Functional state management ...
  • function ChildrenDemo(props) { console.log(props.children, 'children30');... console.log(React.Children.map(props.children, item => [item, [item, [item]]]), 'children31'); // console.log(React....
  • 我们平常写的class组件都需要继承React.Component这个基类,从而在类都实例中能使用相应的方法。下面让我们看看React.Component这个类究竟长什么样。 Component const emptyObject = {}; function Component...
  • react相关库源码浅析, react ts3 项目 总览: 你将会明白: 开发环境下,key和ref会存在于react元素的props上,但是获取到的值为undefined,并报错。在生产环境下,key和ref不会存在于react元素的props上的。 react...
  • 在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 ...
  • :smiling_face_with_sunglasses: React Adopt-像pro一样组成render props组件 :scroll: 表中的内容 :face_with_monocle: 为什么 是React生态系统的新炒作,这是事实。 因此,当您需要一起使用多个render props组件...
  • 实例化过程 一、ReactBaseClasses基类解析 ... this.props = props; this.context = context; // If a component has string refs, we will assign a different object later. this.refs = emptyObjec
  • 平时我们可能会有这样的需求,就是需要获取某个dom节点或者子组件的实例来更新,而并不是只是通过props等来更新dom节点或者组件。如果没有一个好的方法我们可能自己去获取节点(例:querySelector等)或者去绑定事件来...
  • Children 这个对象提供了帮你处理组件内部props.children的相关方法。...// React.Children.map(this.props.children, (child) => null) function mapChildren(children, func, context) { ...
  • <div><p>这是我的 React 源码解读课的第一篇文章,首先来说说为啥要写这个系列文章: <ul><li>现在工作中基本都用 React 了,由此想了解下内部原理</li><li>市面上 Vue 的源码解读数不胜数,...
  • React源码解析第三节

    2020-07-21 14:12:23
    源码前言 function Component (props, context,updater) Component.prototype.setState = function(partialState,callback) { … this.updater.enqueueSetState(this,partialState,callback,) } updater....
  • 可通过style道具自定义的React Native按钮组件。 在iOS下呈现TouchableNativeFeedback在Android下呈现TouchableOpacity 。 安装 apsl-react-native-button>=2.6.0需要React Native 0.28或更高版本。 apsl-react-...
  • React源码解析:setState

    2018-01-17 10:24:00
    先来几个例子热热身: ......... ...constructor(props){ super(props); this.state = { index: 0 } } componentDidMount() { this.setState({ index: this.state.index + 1 ...
  • 这里我们来看一下,传递给render方法的第一个参数是...import React from './react' import ReactDOM from './react-dom' class Home extends Component { constructor(props) { super(props); this.state = { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 418
精华内容 167
关键字:

propsreact源码