幫你讀懂preact源碼(二)

上篇文章已引見過idff的處置懲罰邏輯重要分為三塊,處置懲罰textNode,element及component,但細緻怎樣處置懲罰component還沒有細緻引見,接下來說一下preact是怎樣處置懲罰component的。

組件的diff

經由過程進修元素節點的diff操縱,我們無妨斗膽勇敢猜測一下,組件是做了以下diff操縱:

  • 組件差別範例或許不存在就建立,走響應的生命周期鈎子
  • 比較組件的屬性
  • 比較組件的孩子

事實上和我們的猜測很相似,在舉行下一步之前,我們先了解下preact中的數據結構:

// 以下JSX
<App>
    <Child></Child>
</App>

// App組件的實例,會有以下屬性

{
    base,   // 對應組件襯着的dom
    _component, // 指向Child組件
}

// Child組件有以下屬性

{
    base,    // 與App組件實例指向同一個dom
    _parentComponent,   // 指向App組件
}

// 對應的dom節點,即前文中的base對象

{ 
    _component    // 指向App組件,而不是Child組件
}

然後我們看一下buildComponentFromVNode邏輯:

  • 假如組件範例雷同挪用setComponentProps
  • 假如組件範例差別:

    • 接納老的組件
    • 建立新的組件實例
    • 挪用setComponentProps
    • 接納老的dom
  • 返回dom
    function buildComponentFromVNode(dom, vnode, context, mountAll) {
        let c = dom && dom._component,
            originalComponent = c,
            oldDom = dom,
            isDirectOwner = c && dom._componentConstructor === vnode.nodeName, // 組件範例是不是變了
            isOwner = isDirectOwner,
            props = getNodeProps(vnode);

        while (c && !isOwner && (c = c._parentComponent)) { // 假如組件範例變了,一向向上遍歷;看範例是不是雷同
            isOwner = c.constructor === vnode.nodeName;
        }
        // 此時isOwner就代表組件範例是不是雷同
        // 假如組件範例雷同,只設置屬性;然後將dom指向c.base
        if (c && isOwner && (!mountAll || c._component)) { 
            setComponentProps(c, props, 3, context, mountAll);
            dom = c.base;
        } else {
            if (originalComponent && !isDirectOwner) {   // 組件範例差別就先卸載組件
                unmountComponent(originalComponent);
                dom = oldDom = null;
            }
            // 建立組件的重要邏輯就是return new vnode.nodeName()
            c = createComponent(vnode.nodeName, props, context);
            
            if (dom && !c.nextBase) {
                c.nextBase = dom;
                // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229:
                oldDom = null;
            }
            setComponentProps(c, props, 1, context, mountAll);
            dom = c.base;

            if (oldDom && dom !== oldDom) {
                oldDom._component = null;
                recollectNodeTree(oldDom, false);
            }
        }
        return dom;
    }

能夠看到組件進一步diff的中心邏輯在setComponentProps要領中,setComponentProps大抵做了兩件事:

  • 挪用襯着前的生命周期鈎子: componentWillMount 與 componentWillReceiveProps
  • 挪用renderComponent

renderComponent重要邏輯為:

  • 挪用shouldComponentUpdate 或 componentWillUpdate生命周期鈎子
  • 挪用組件的render要領

    • 假如render的結果是一個組件,做相似與buildComponentFromVNode的操縱
    • 假如render的結果是dom節點,挪用diff操縱
  • 替換新的節點,卸載老的節點或組件
  • 為組件的base增加組件援用_component
  • 挪用組件的生命周期鈎子componentDidUpdate,componentDidMount。

至此,我們已大抵了解了preact的大抵全流程,接下來我們看一下它的diffChildren的算法:

  • 將原始dom的子節點分為兩部分,有key的放在keyed map內里,沒有key的放在children數組內里。
  • 遍歷vchildren,經由過程key找到keyed中的child,假如child不存在,從children中掏出雷同範例的子節點
  • 對child與vchild舉行diff,此時獲得的dom節點就是新的dom節點
  • 然後與老的dom節點對應的節點比較,操縱dom樹。
  • 末了刪除新的dom樹中不存在的節點。
function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) {
    let originalChildren = dom.childNodes,
        children = [],
        keyed = {},
        keyedLen = 0,
        min = 0,
        len = originalChildren.length,
        childrenLen = 0,
        vlen = vchildren ? vchildren.length : 0,
        j,
        c,
        f,
        vchild,
        child;

    if (len !== 0) {
        for (var i = 0; i < len; i++) {
            var _child = originalChildren[i],
                props = _child.__preactattr_,
                key = vlen && props ? _child._component ? _child._component.__key : props.key : null;
            if (key != null) {
                keyedLen++;
                keyed[key] = _child;
            } else if (props || (_child.splitText !== undefined ? isHydrating ? _child.nodeValue.trim() : true : isHydrating)) {
                children[childrenLen++] = _child;
            }
        }
    }
    // 遍歷假造dom節點
    // 取child(有key,證實它兩個是要對應比較的)
    // 假如child和originchildren[i]比較
    // originchild沒有,過剩,不然插進去到originchild前面
    if (vlen !== 0) {
        for (var i = 0; i < vlen; i++) {
            vchild = vchildren[i];
            child = null;

            // attempt to find a node based on key matching
            var key = vchild.key;
            if (key != null) {
                if (keyedLen && keyed[key] !== undefined) {
                    child = keyed[key];
                    keyed[key] = undefined;
                    keyedLen--;
                }
            }
            // attempt to pluck a node of the same type from the existing children
            else if (!child && min < childrenLen) {
                for (j = min; j < childrenLen; j++) { //從min今後最先遍歷,假如是雷同範例的節點就拿出來,誰人位置設為undefined
                    if (children[j] !== undefined && isSameNodeType(c = children[j], vchild, isHydrating)) {
                        child = c;
                        children[j] = undefined;
                        if (j === childrenLen - 1) childrenLen--; 
                        if (j === min) min++; 
                        break;
                    }
                }
            }

            // morph the matched/found/created DOM child to match vchild (deep)
            child = idiff(child, vchild, context, mountAll);
            f = originalChildren[i];
            if (child && child !== dom && child !== f) {
                if (f == null) {
                    dom.appendChild(child);
                } else if (child === f.nextSibling) {
                    removeNode(f); 
                } else {
                    dom.insertBefore(child, f);
                }
            }
        }
    }

    // remove unused keyed children:
    // keyedLen標識老的鳩合中另有的元素,但沒在新的鳩合中運用
    if (keyedLen) {
        for (var i in keyed) {
            if (keyed[i] !== undefined) recollectNodeTree(keyed[i], false);
        }
    }

    // remove orphaned unkeyed children:
    // min代表拿走的元素
    while (min <= childrenLen) {
        if ((child = children[childrenLen--]) !== undefined) recollectNodeTree(child, false);
    }
}

從上面能夠看出,preact只處置懲罰了罕見的運用場景,沒有做迥殊的優化步伐,這也致使它在一些狀況下的性能比react低,如:從a b到b a。
而react中會紀錄lastIndex,對其做了響應的優化,節點的Index > lastIndex的狀況下,不做挪動操縱。
然則假如react中有length > 2,最前面的節點位置與末了面的節點位置交換的狀況下,因為index一向小於lastIndex,就會落空上述的優化結果。
這類狀況,在snabbdom中獲得了優化,snabbdom經由過程oldStartIdx,oldEndIdx,newStartIdx,newEndIdx四個指針,在每次循環中先處置懲罰特殊狀況,並經由過程減少指針局限,獲得性能上的提拔。

    原文作者:黑色的影子
    原文地址: https://segmentfault.com/a/1190000014945482
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞