React学习之解读React源码

42次阅读

共计 6137 个字符,预计需要花费 16 分钟才能阅读完成。

首先理解 ReactElement 和 ReactClass 的概念。想要更好的利用 react 的虚拟 DOM,diff 算法的优势,我们需要正确的优化、组织 react 页面。

  • 理解 ReactElement 和 ReactClass 的概念

ReactElement

一个描述 DOM 节点或 component 实例的字面级对象。它包含一些信息,包括组件类型 type 和属性 props。就像一个描述 DOM 节点的元素 (虚拟节点)。它们可以被创建通过 React.createElement 方法或 jsx 写法
分为 DOM Element 和 Component 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!' 
  } 
}  

ReactElement

ReactClass 是平时我们写的 Component 组件(类或函数),例如上面的 Button 类。ReactClass 实例化后调用 render 方法可返回 DOM Element。

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。

react 更新机制

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

  1. 自定义元素的更新,主要是更新 render 出的节点,做甩手掌柜交给 render 出的节点的对应 component 去管理更新。
  2. text 节点的更新很简单,直接更新文案。
  3. 浏览器基本元素的更新,分为两块:
  • 先是更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。
  • 然后是子节点的更新,子节点更新主要是找出差异对象,找差异对象的时候也会使用上面的 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 方法源码

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 对象类型也是字符串或数字,return (nextType === 'string' || nextType === 'number'); 
  } else { 
      // 如果先前的 ReactElement 对象类型是对象,新的 ReactElement 对象类型也是对象,并且标签类型和 key 值相同,则需要更新 
     return ( 
       nextType === 'object' && 
       prevElement.type === nextElement.type && 
       prevElement.key === nextElement.key 
     ); 
   } 
 }

基本元素
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。

正文完
 0