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

Virtual DOM模型

1.Virtual DOM模型負責Virtual DOM底層框架的構建工做,它擁有一整套的Virtual DOM標籤,
並負責虛擬節點及其屬性的構建,更新,刪除等工做.
2.其實,構建一套簡易Virtual DOM模型並不複雜,它只須要具有一個DOM標籤所需的基本元素便可.node

{
    // 標籤名
    tagName: 'div',
    // 屬性
    properties: {
        // 樣式
        style: {}
    },
    // 子節點
    children: [],
    // 惟一標識
    key: 1
}

3.Virtual DOM中的節點稱爲ReactNode,它分爲3種類型:ReactElement,ReactFragment,ReactText.
其中,ReactElement又分爲ReactComponentElement和ReactDOMElement.react

建立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方法app

// 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

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;
}
相關文章
相關標籤/搜索