解讀React原始碼(二):Virtual DOM模型

蛋白發表於2017-08-04

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;
}

相關文章