深入理解react(源码分析)

MissZxy 8年前
   <ul>     <li> <p>理解ReactElement和ReactClass的概念</p>      <ul>       <li> <p>ReactElement</p> </li>       <li> <p>ReactClass</p> </li>      </ul> </li>     <li> <p>react渲染过程</p> </li>     <li> <p>react更新机制</p> </li>     <li> <p>reactdiff算法</p> </li>     <li> <p>react的优点与总结</p> </li>    </ul>    <h2><strong>理解ReactElement和ReactClass的概念</strong></h2>    <p>首先让我们理解两个概念:</p>    <h3><strong>ReactElement</strong></h3>    <p>一个描述DOM节点或component实例的字面级对象。它包含一些信息,包括组件类型 type 和属性 props 。就像一个描述DOM节点的元素(虚拟节点)。它们可以被创建通过 React.createElement 方法或 jsx 写法</p>    <p>分为 DOM Element 和 Component Elements 两类:</p>    <ul>     <li> <p>DOM Elements</p> </li>    </ul>    <p>当节点的type属性为字符串时,它代表是普通的节点,如 div,span</p>    <pre>  <code class="language-javascript">{    type: 'button',    props: {      className: 'button button-blue',      children: {        type: 'b',        props: {          children: 'OK!'        }      }    }  }</code></pre>    <ul>     <li> <p>Component Elements</p> </li>    </ul>    <p>当节点的type属性为一个函数或一个类时,它代表自定义的节点</p>    <pre>  <code class="language-javascript">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!'    }  }</code></pre>    <h3><strong>ReactClass</strong></h3>    <p>ReactClass是平时我们写的Component组件(类或函数),例如上面的 Button 类。ReactClass实例化后调用render方法可返回 DOM Element 。</p>    <h2><strong>react渲染过程</strong></h2>    <p><img src="https://simg.open-open.com/show/1fbd1b96f6023f4ae1074152b6460586.png"></p>    <p>过程理解:</p>    <pre>  <code class="language-javascript">// element是 Component Elements  ReactDOM.render({    type: Form,    props: {      isSubmitted: false,      buttonText: 'OK!'    }  }, document.getElementById('root'));</code></pre>    <ol>     <li> <p>调用 React.render 方法,将我们的 element 根虚拟节点渲染到 container 元素中。 element 可以是一个字符串文本元素,也可以是如上介绍的 ReactElement (分为DOM Elements, Component Elements)。</p> </li>     <li> <p>根据 element 的类型不同,分别实例化 ReactDOMTextComponent , ReactDOMComponent , ReactCompositeComponent 类。这些类用来管理 ReactElement ,负责将不同的 ReactElement 转化成DOM(mountComponent方法),负责更新DOM(receiveComponent方法,updateComponent方法, 如下会介绍)等。</p> </li>     <li> <p>ReactCompositeComponent 实例调用 mountComponent 方法后内部调用 render 方法,返回了 DOM Elements 。再对如图的步骤:two:递归。</p> </li>    </ol>    <h2><strong>react更新机制</strong></h2>    <p><img src="https://simg.open-open.com/show/f4397df88c142152bf27aade405be3d0.png"></p>    <p>每个类型的元素都要处理好自己的更新:</p>    <ol>     <li> <p>自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。</p> </li>     <li> <p>text节点的更新很简单,直接更新文案。</p> </li>     <li> <p>浏览器基本元素的更新,分为两块:</p>      <ol>       <li> <p>先是更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。</p> </li>       <li> <p>然后是子节点的更新,子节点更新主要是找出差异对象,找差异对象的时候也会使用上面的shouldUpdateReactComponent来判断,如果是可以直接更新的就会递归调用子节点的更新,这样也会递归查找差异对象。不可直接更新的删除之前的对象或添加新的对象。之后根据差异对象操作dom元素(位置变动,删除,添加等)。</p> </li>      </ol> </li>    </ol>    <h3><strong>第一步:调用this.setState</strong></h3>    <pre>  <code class="language-javascript">ReactClass.prototype.setState = function(newState) {      //this._reactInternalInstance是ReactCompositeComponent的实例      this._reactInternalInstance.receiveComponent(null, newState);  }</code></pre>    <h3><strong>第二步:调用内部receiveComponent方法</strong></h3>    <p>这里主要分三种情况,文本元素,基本元素,自定义元素。</p>    <p>自定义元素:</p>    <p>receiveComponent方法 </p>    <pre>  <code class="language-javascript">// 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      );    }</code></pre>    <p>updateComponent方法 </p>    <pre>  <code class="language-javascript">// 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;      }              // .......    }</code></pre>    <p>内部_performComponentUpdate方法</p>    <pre>  <code class="language-javascript">// 内部_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        );            }</code></pre>    <p>shouldUpdateReactComponent函数 </p>    <pre>  <code class="language-javascript">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      );    }  }</code></pre>    <p>文本元素:</p>    <p>receiveComponent方法</p>    <pre>  <code class="language-javascript">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          );        }      }    }</code></pre>    <p>基本元素:</p>    <p>receiveComponent方法 </p>    <pre>  <code class="language-javascript">ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) {      var prevElement = this._currentElement;      this._currentElement = nextElement;      this.updateComponent(transaction, prevElement, nextElement, context);  }</code></pre>    <p>updateComponent方法</p>    <pre>  <code class="language-javascript">ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) {      // 略.....      //需要单独的更新属性      this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag);      //再更新子节点      this._updateDOMChildren(        lastProps,        nextProps,        transaction,        context      );        // ......  }</code></pre>    <p>this._updateDOMChildren 方法内部调用diff算法,请看下一节........</p>    <h2><strong>react Diff算法</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9d0ee9adde396637d18257a14ddd8ae6.png"></p>    <p>diff算法</p>    <pre>  <code class="language-javascript">_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])          );        }      }    }</code></pre>    <h2><strong>react的优点与总结</strong></h2>    <h3><strong>优点</strong></h3>    <ul>     <li> <p>虚拟节点。在UI方面,不需要立刻更新视图,而是生成虚拟DOM后统一渲染。</p> </li>     <li> <p>组件机制。各个组件独立管理,层层嵌套,互不影响,react内部实现的渲染功能。</p> </li>     <li> <p>差异算法。根据基本元素的key值,判断是否递归更新子节点,还是删除旧节点,添加新节点。</p> </li>    </ul>    <h3><strong>总结</strong></h3>    <p>想要更好的利用react的虚拟DOM,diff算法的优势,我们需要正确的优化、组织react页面。例如将一个页面render的 ReactElement 节点分解成多个组件。在需要优化的组件手动添加 shouldComponentUpdate 来避免不需要的 re-render 。</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007252756</p>    <p> </p>