React 源码解析(一) —— Virtual DOM

897 阅读3分钟

Virtual DOM

Virtual DOM 的主要思想就是模拟 DOM 的树状结构,在内存中创建保存映射 DOM 信息的节点数据,在由于交互等因素需要视图更新时,先通过对节点数据进行 diff 后得到差异结果后,再一次性对DOM 进行批量更新操作,这就好比在内存中创建了一个平行世界,浏览器中 DOM 树的每一个节点与属性数据都在这个平行世界中存在着另一个版本的虚拟 DOM 树,所有复杂曲折的更新逻辑都在平行世界中的 Virtual DOM 处理完成,只将最终的更新结果发送给浏览器中的 DOM 树执行,这样就避免了冗余琐碎的 DOM 树操作负担,进而有效提高了性能。

React 中的 Virtual DOM

React 实际是通过 React.createElement 创建 JavaScript 对象 (Virtual DOM) 来模拟 DOM 的树状结构

<div>
    Virtual DOM
</div>

被转换过后的对象如下:

源码解析

ps. 下述代码均为简化后的源码,看起来方便点

主要就是看 createElement 方法是如何将元素转换为对应的 js 对象

React.createElement(
    "div",
    {className: 'test'},
    "1"
)

createElement 的入参

  • type:类型参数,可以是 HTML 元素(<div> 等),也可以是 React 组件类型
  • propsprops 入参
  • children: 子元素,从第三个参数开始传入的参数都是子元素

createElement 的处理流程

  1. 处理入参,props

    • refkey 从 props 中剥离出来(这就是为什么子组件 props 中取不到 refkey
    • __self__sourceprops 中剥离出来
    var key = null;
    var ref = null;
    var source = null;
    var self = null;
    
    // 将 props 中的 ref、key、__self、__source 剥离出来
    if (props !== null) {
        if (props.ref) {
            ref = props.ref
            props.delete(ref);
        }
    
        if (props.key) {
            key = '' + props.key;
            props.delete(key);
        }
    
        if(props.__self) {
            self = props.__self;
            props.delete(__self);
        }
    
        if(props.__source) {
            self = props.__source;
            props.delete(__source);
        }
    }
    
  2. 处理入参,children

    • props 对象上添加 children 属性,其值就是入参 children
    • 如果有多个 children 入参的话,则合并成一个数组
    // 将所有子元素整合到 props.children 
    var childrenLength = arguments.length - 2;
    if (childrenLength === 1) {
        // 只有一个子元素
        props.children = children;
    } else if (childrenLength > 1) {
        // 多个子元素,合并成一个数组
        var childArray = Array(childrenLength);
        for (var i = 0; i < childrenLength; i++) {
            childArray[i] = arguments[i + 2];
        }
        Object.freeze(childArray);
        props.children = childArray;
    }
    
  3. 处理 class 组件上的 defaultProps

    • 组件默认的 props
    • 如果 props 上没有对应属性的话,则将 defaultProps 属性值赋到 props
    // 将 defaultProps 赋值到 props 上
     if (type && type.defaultProps) {
         for (let propName in type.defaultProps) {
             if (props[propName] === undefined) {
                 props[propName] = defaultProps[propName];
             }
         }
     }
    
  4. 整合成 js 对象

const REACT_ELEMENT_TYPE = Symbol.for('react.element');

let ReactCurrentOwner = {
    current: null,
}

function createElement(type, props, children) {
    // 处理 props 
    ...

    // 处理 children
    ...

    // 处理 defaultProps
    ...

    // 整合成 js 对象
    const element = {
        $$typeof: REACT_ELEMENT_TYPE,
        type: type,
        key: key,
        ref: ref,
        props: props,
        _owner: ReactCurrentOwner.current,   // 创建当前组件的对象,默认值为null
    };

    element._store = {};

    Object.defineProperty(element._store, 'validated', {
        configurable: false,
        enumerable: false,
        writable: true,
        value: true    // 源码中是有校验函数,校验为合法的 React 对象后,value 才会变成 true 的,此处省略,直接为 true
    });

    Object.defineProperty(element, '_self', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: self
    }); // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.

    Object.defineProperty(element, '_source', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: source
    });

    if (Object.freeze) {
        Object.freeze(element.props);
        Object.freeze(element);
    }

    return element;
}
  1. 最后呈现的对象

参考