解读React源码(二):Virtual DOM模型

Virtual DOM模型

1.Virtual DOM模型负责Virtual DOM底层框架的构建工作,它拥有一整套的Virtual DOM标签,
并负责虚拟节点及其属性的构建,更新,删除等工作.
2.其实,构建一套简易Virtual DOM模型并不复杂,它只需要具备一个DOM标签所需的基本元素即可.

{
    // 标签名
    tagName: 'div',// 属性
    properties: {
        // 样式
        style: {}
    },// 子节点
    children: [],// 唯一标识
    key: 1
}

3.Virtual DOM中的节点称为ReactNode,它分为3种类型:ReactElement,ReactFragment,ReactText.
其中,ReactElement又分为ReactComponentElement和ReactDOMElement.

创建React元素

// 输入jsx
const app = <Nav color="blue"><Profile>click</Profile></Nav>;

// 输出js
const app = React.createElement(
    Nav,{color: 'blue'},React.createElement(Profile,null,'click')
);

通过jsx创建的虚拟元素最终会被编译成调用React的createElement方法

// createElement只是做了简单修正,返回一个ReactElement实例对象
// 也就是虚拟元素的实例
ReactElement.createElement = function(type,config,children) {
    // 初始化参数
    var propName;
    var props = {};
    var key = null;
    var ref = null;
    var self = null;
    var source = null;

    // 如果存在config,则提取里面的内容
    if (config != null) {
        ref = config.ref === undefined ? null : config.ref;
        key = config.key === undefined ? null : '' + config.key;
        self = config._self === undefined ? null : config._self;
        source = config._source === undefined ? null : config._source;
        // 复制config里的内容到props(id和className等)
        for (propName in config) {
            if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
                props[propName] = config[propName];
            }
        }
    }

    // 处理children,全部挂载到props的children属性上,如果只有一个参数,直接赋值给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];
        }
        props.children = childArray;
    }

    // 如果某个prop为空且存在认的prop,则将认prop赋给当前的prop
    if (type && type.defaultProps) {
        var defaultProps = type.defaultProps;
        for (propName in defaultProps) {
            if (typeof props[propName] === 'undefined') {
                props[propName] = defaultProps[propName]
            }
        }
    }

    // 返回一个ReactElement实例对象
    return ReactElement(type,key,ref,self,source,ReactCurrentOwner.current,props);
}

初始化组件入口

1.当使用React创建组件时,首先会调用instantiateReactComponent,这就是初始化组件的入口函数,
它通过判断node类型来区分不同组件的入口.

// 初始化组件入口
function instantiateReactComponent(node,parentCompositeType) {
    var instance;

    // 空组件 (ReactEmptyComponent)
    if (node === null || node === false) {
        instance = ReactEmptyComponent.create(instantiateReactComponent);
    }

    if (typeof node === 'object') {
        var element = node;
        if (typeof element.type === 'string') {
            // DOM标签 (ReactDOMComponent)
            instance = ReactNativeComponent.createInternalComponent(element);
        } else if (isInternalComponentType(element.type)) {
            // 不是字符串表示的自定义组件暂无法使用,此处将不做组件初始化操作
            instance = new element.type(element);
        } else {
            // 自定义组件
            instance = new ReactCompositeComponentWrapper();
        }
    } else if (typeof node === 'string' || typeof node === 'number') {
        // 字符串或数字
        instance = ReactNativeComponent.createInstanceForText(node);
    } else {
        // 不做处理
    }

    // 设置实例
    instance.construct(node);
    // 初始化参数
    instance._mountIndex = 0;
    instance._mountimage = null;

    return instance;
}

文本组件

1.当node类型为文本节点时是不算Virtual DOM元素的,但React为了保持渲染的一致性,
将其封装为文本组件ReactDOMTextComponent.

DOM标签组件

1.Virtual DOM模型涵盖了几乎所有的原生DOM标签,如<div>,<p>,<span>等.
当开发者使用React时,此时的<div>并不是原生的<div>标签,他其实是React生成
Virtual DOM对象,只不过标签名称相同罢了.

_createOpenTagMarkupAndPutListeners: function(transaction,props) {
    var ret = '<' + this._currentElement.type;
    // 拼凑出属性
    for (var propKey in props) {
        var propValue = props[propKey];

        if (registrationNameModules.hasOwnProperty(propKey)) {
            // 针对当前的节点添加事件代理
            if (propValue) {
                enqueuePutListener(this,propKey,propValue,transaction);
            }
        } else {
            if (propKey === STYLE) {
                if (propValue) {
                    // 合并样式
                    propValue = this._prevIoUsstylecopy = Object.assign({},props.style);
                }
                propValue = CsspropertyOperations.createMarkupForStyles(propValue,this);
            }
            // 创建属性标识
            var markup = null;
            if (this._tag != null && isCustomComponent(this._tag,props)) {
                markup = DOMPropertyOperations.createMarkupForProperty(propKey,propValue);
            }
            if (markup) {
                ret += ' ' + markup;
            }
        }
    }
    // 对于静态页面,不需要设置react-id,这样可以节省大量字节
    if (transaction.renderToStaticmarkup) {
        return ret;
    }
    // 设置reactid
    if (!this._nativeParent) {
        ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
    }
    ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);

    return ret;
}

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...