深切明白react(源码剖析)

原文链接

明白ReactElement和ReactClass的观点

起首让我们明白两个观点:

ReactElement

一个形貌DOM节点或component实例的字面级对象。它包含一些信息,包含组件范例type和属性props。就像一个形貌DOM节点的元素(假造节点)。它们能够被建立经由过程React.createElement要领或jsx写法

分为DOM ElementComponent Elements两类:

  • DOM Elements

当节点的type属性为字符串时,它代表是一般的节点,如div,span

{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}
  • Component Elements

当节点的type属性为一个函数或一个类时,它代表自定义的节点

class Button extends React.Component {
  render() {
    const { children, color } = this.props;
    return {
      type: 'button',
      props: {
        className: 'button button-' + color,
        children: {
          type: 'b',
          props: {
            children: children
          }
        }
      }
    };
  }
}

// Component Elements
{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}

ReactClass

ReactClass是日常平凡我们写的Component组件(类或函数),比方上面的Button类。ReactClass实例化后挪用render要领可返回DOM Element

react衬着历程

《深切明白react(源码剖析)》

历程明白:

// element是 Component Elements
ReactDOM.render({
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}, document.getElementById('root'));
  1. 挪用React.render要领,将我们的element根假造节点衬着到container元素中。element能够是一个字符串文本元素,也能够是如上引见的ReactElement(分为DOM Elements, Component Elements)。

  2. 依据element的范例差别,离别实例化ReactDOMTextComponent, ReactDOMComponent, ReactCompositeComponent类。这些类用来治理ReactElement,担任将差别的ReactElement转化成DOM(mountComponent要领),担任更新DOM(receiveComponent要领,updateComponent要领, 以下会引见)等。

  3. ReactCompositeComponent实例挪用mountComponent要领后内部挪用render要领,返回了DOM Elements。再对如图的步骤2️⃣递归。

react更新机制

《深切明白react(源码剖析)》

每一个范例的元素都要处置惩罚好本身的更新:

  1. 自定义元素的更新,主假如更新render出的节点,做甩手掌柜交给render出的节点的对应component去治理更新。

  2. text节点的更新很简单,直接更新案牍。

  3. 浏览器基础元素的更新,分为两块:

    1. 先是更新属性,对照出前后属性的差别,部分更新。而且处置惩罚特别属性,比方事宜绑定。

    2. 然后是子节点的更新,子节点更新主假如找出差别对象,找差别对象的时刻也会运用上面的shouldUpdateReactComponent来推断,假如是能够直接更新的就会递归挪用子节点的更新,如许也会递归查找差别对象。不可直接更新的删除之前的对象或增加新的对象。以后依据差别对象操纵dom元素(位置更改,删除,增加等)。

第一步:挪用this.setState


ReactClass.prototype.setState = function(newState) {
    //this._reactInternalInstance是ReactCompositeComponent的实例
    this._reactInternalInstance.receiveComponent(null, newState);
}

第二步:挪用内部receiveComponent要领

这里重要分三种状况,文本元素,基础元素,自定义元素。

自定义元素:

receiveComponent要领源码

// receiveComponent要领
ReactCompositeComponent.prototype.receiveComponent = function(nextElement, transaction, nextContext) {
    var prevElement = this._currentElement;
    var prevContext = this._context;

    this._pendingElement = null;

    this.updateComponent(
      transaction,
      prevElement,
      nextElement,
      prevContext,
      nextContext
    );

}

updateComponent要领源码

// updateComponent要领
ReactCompositeComponent.prototype.updateComponent = function(
    transaction,
    prevParentElement,
    nextParentElement,
    prevUnmaskedContext,
    nextUnmaskedContext
) {
   // 简写.....
   
    // 不是state更新而是props更新
    if (prevParentElement !== nextParentElement) {
      willReceive = true;
    }

    if (willReceive && inst.componentWillReceiveProps) {
        // 挪用生命周期componentWillReceiveProps要领
    }
    
    // 是不是更新元素
    if (inst.shouldComponentUpdate) {
        // 假如供应shouldComponentUpdate要领
        shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
    } else {
        if (this._compositeType === CompositeTypes.PureClass) {
          // 假如是PureClass,浅层对照props和state
          shouldUpdate =
            !shallowEqual(prevProps, nextProps) ||
            !shallowEqual(inst.state, nextState);
        }
    }
    
    if (shouldUpdate) {
      // 更新元素
      this._performComponentUpdate(
        nextParentElement,
        nextProps,
        nextState,
        nextContext,
        transaction,
        nextUnmaskedContext
      );
    } else {
      // 不更新元素,但仍然设置props和state
      this._currentElement = nextParentElement;
      this._context = nextUnmaskedContext;
      inst.props = nextProps;
      inst.state = nextState;
      inst.context = nextContext;
    }
       
   // .......

}

内部_performComponentUpdate要领源码

   // 内部_updateRenderedComponentWithNextElement要领
    ReactCompositeComponent.prototype._updateRenderedComponentWithNextElement = function() {
    
    // 剖断两个element需不须要更新
    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
      // 假如须要更新,就继承挪用子节点的receiveComponent的要领,传入新的element更新子节点。
      ReactReconciler.receiveComponent(
        prevComponentInstance,
        nextRenderedElement,
        transaction,
        this._processChildContext(context)
      );
    } else {
      // 卸载之前的子节点,装置新的子节点
      var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
      ReactReconciler.unmountComponent(
        prevComponentInstance,
        safely,
        false /* skipLifecycle */
      );

      var nodeType = ReactNodeTypes.getType(nextRenderedElement);
      this._renderedNodeType = nodeType;
      var child = this._instantiateReactComponent(
        nextRenderedElement,
        nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
      );
      this._renderedComponent = child;

      var nextMarkup = ReactReconciler.mountComponent(
        child,
        transaction,
        this._hostParent,
        this._hostContainerInfo,
        this._processChildContext(context),
        debugID
      );
    
    }

shouldUpdateReactComponent函数源码


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') {
    // 假如先前的ReactElement对象范例是字符串或数字,新的ReactElement对象范例也是字符串或数字,则须要更新,新的ReactElement对象范例是对象,则不应当更新,直接替代。
    return (nextType === 'string' || nextType === 'number');
  } else {
      // 假如先前的ReactElement对象范例是对象,新的ReactElement对象范例也是对象,而且标签范例和key值雷同,则须要更新
    return (
      nextType === 'object' &&
      prevElement.type === nextElement.type &&
      prevElement.key === nextElement.key
    );
  }
}

文本元素:

receiveComponent要领源码

 ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) {
     //跟之前保留的字符串比较
    if (nextText !== this._currentElement) {
      this._currentElement = nextText;
      var nextStringText = '' + nextText;
      if (nextStringText !== this._stringText) {
        this._stringText = nextStringText;
        var commentNodes = this.getHostNode();
        // 替代文本元素
        DOMChildrenOperations.replaceDelimitedText(
          commentNodes[0],
          commentNodes[1],
          nextStringText
        );
      }
    }
  }

基础元素:

receiveComponent要领源码

ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) {
    var prevElement = this._currentElement;
    this._currentElement = nextElement;
    this.updateComponent(transaction, prevElement, nextElement, context);
}

updateComponent要领源码

ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) {
    // 略.....
    //须要零丁的更新属性
    this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag);
    //再更新子节点
    this._updateDOMChildren(
      lastProps,
      nextProps,
      transaction,
      context
    );

    // ......
}

this._updateDOMChildren要领内部挪用diff算法,请看下一节……..

react Diff算法

《深切明白react(源码剖析)》

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;
    var nextIndex = 0;
    var lastIndex = 0;
    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) {
          // 同一个援用,申明是运用的同一个component,所以我们须要做挪动的操纵
          // 挪动已有的子节点
          // NOTICE:这里依据nextIndex, lastIndex决议是不是挪动
        updates = enqueue(
          updates,
          this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex)
        );
        
        // 更新lastIndex
        lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        // 更新component的.mountIndex属性
        prevChild._mountIndex = nextIndex;
        
      } else {
        if (prevChild) {
          // 更新lastIndex
          lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        }
        
        // 增加新的子节点在指定的位置上
        updates = enqueue(
          updates,
          this._mountChildAtIndex(
            nextChild,
            mountImages[nextMountIndex],
            lastPlacedNode,
            nextIndex,
            transaction,
            context
          )
        );
        
        
        nextMountIndex++;
      }
      
      // 更新nextIndex
      nextIndex++;
      lastPlacedNode = ReactReconciler.getHostNode(nextChild);
    }
    
    // 移撤除不存在的旧子节点,和旧子节点和新子节点差别的旧子节点
    for (name in removedNodes) {
      if (removedNodes.hasOwnProperty(name)) {
        updates = enqueue(
          updates,
          this._unmountChild(prevChildren[name], removedNodes[name])
        );
      }
    }
  }

react的长处与总结

长处

  • 假造节点。在UI方面,不须要马上更新视图,而是天生假造DOM后一致衬着。

  • 组件机制。各个组件自力治理,层层嵌套,互不影响,react内部完成的衬着功用。

  • 差别算法。依据基础元素的key值,推断是不是递归更新子节点,照样删除旧节点,增加新节点。

总结

想要更好的应用react的假造DOM,diff算法的上风,我们须要准确的优化、构造react页面。比方将一个页面render的ReactElement节点分解成多个组件。在须要优化的组件手动增加 shouldComponentUpdate 来防止不须要的 re-render

    原文作者:rainie
    原文地址: https://segmentfault.com/a/1190000007252756
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞