资讯专栏INFORMATION COLUMN

深入理解react(源码分析)

villainhr / 2669人阅读

摘要:渲染过程过程理解是调用方法,将我们的根虚拟节点渲染到元素中。根据的类型不同,分别实例化类。并且处理特殊属性,比如事件绑定。之后根据差异对象操作元素位置变动,删除,添加等。各个组件独立管理层层嵌套,互不影响,内部实现的渲染功能。

原文链接

理解ReactElement和ReactClass的概念

ReactElement

ReactClass

react渲染过程

react更新机制

reactdiff算法

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渲染过程

过程理解:

// element是 Component Elements
ReactDOM.render({
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: "OK!"
  }
}, document.getElementById("root"));

调用React.render方法,将我们的element根虚拟节点渲染到container元素中。element可以是一个字符串文本元素,也可以是如上介绍的ReactElement(分为DOM Elements, Component Elements)。

根据element的类型不同,分别实例化ReactDOMTextComponent, ReactDOMComponent, ReactCompositeComponent类。这些类用来管理ReactElement,负责将不同的ReactElement转化成DOM(mountComponent方法),负责更新DOM(receiveComponent方法,updateComponent方法, 如下会介绍)等。

ReactCompositeComponent实例调用mountComponent方法后内部调用render方法,返回了DOM Elements。再对如图的步骤2️⃣递归。

react更新机制

每个类型的元素都要处理好自己的更新:

自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。

text节点的更新很简单,直接更新文案。

浏览器基本元素的更新,分为两块:

先是更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。

然后是子节点的更新,子节点更新主要是找出差异对象,找差异对象的时候也会使用上面的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算法

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91049.html

相关文章

  • React深入深入分析虚拟DOM的渲染原理和特性

    摘要:导读的虚拟和算法是的非常重要的核心特性,这部分源码也非常复杂,理解这部分知识的原理对更深入的掌握是非常必要的。本篇文章从源码出发,分析虚拟的核心渲染原理首次渲染,以及对它做的性能优化点。 showImg(https://segmentfault.com/img/remote/1460000018891457); 导读 React的虚拟DOM和Diff算法是React的非常重要的核心特性...

    BlackMass 评论0 收藏0
  • 前端学习资源整理

    稍微整理了一下自己平时看到的前端学习资源,分享给大家。 html MDN:Mozilla开发者网络 SEO:前端开发中的SEO css 张鑫旭:张鑫旭的博客 css精灵图:css精灵图实践 栅格系统:详解CSS中的栅格系统 媒体查询:css媒体查询用法 rem布局:手机端页面自适应布局 移动前端开发之viewport的深入理解:深入理解viewport 淘宝前端布局:手机淘宝移动端布局 fl...

    siberiawolf 评论0 收藏0
  • jquery源码分析

    摘要:前言随着前端的不断发展,很多开发人员已经开始使用等框架,但是很少有人去深入分析以及的源码本人也是,至今还停留在使用的层面。最近还在写一些的笔记,有兴趣的小白也可以看下我的博客文章源码分析地址 前言 随着前端的不断发展,很多开发人员已经开始使用react、vue等web框架,但是很少有人去深入分析vue以及react的源码(本人也是,至今还停留在使用的层面)。框架的使用势必会有更新迭代的...

    SHERlocked93 评论0 收藏0
  • React 内部机制探秘 - React Component 和 Element(文末附彩蛋demo

    摘要:内部机制探秘和文末附彩蛋和源码这篇文章比较偏基础,但是对入门内部机制和实现原理却至关重要。当然也需要明白一些浅显的内部工作机制。当改变出现时,相比于真实更新虚拟的性能优势非常明显。直到最终,会得到完整的表述树的对象。 React 内部机制探秘 - React Component 和 Element(文末附彩蛋demo和源码) 这篇文章比较偏基础,但是对入门 React 内部机制和实现原...

    wenshi11019 评论0 收藏0
  • 深入理解JavaScript

    摘要:深入之继承的多种方式和优缺点深入系列第十五篇,讲解各种继承方式和优缺点。对于解释型语言例如来说,通过词法分析语法分析语法树,就可以开始解释执行了。 JavaScript深入之继承的多种方式和优缺点 JavaScript深入系列第十五篇,讲解JavaScript各种继承方式和优缺点。 写在前面 本文讲解JavaScript各种继承方式和优缺点。 但是注意: 这篇文章更像是笔记,哎,再让我...

    myeveryheart 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<