snabbdom源碼解析(六) 模塊

模塊

./src/modules 裏面,定義了一系列的模塊 , 這些模塊定義了相應的鉤子 。這些鉤子會在 patch 的不同階段觸發,以完成相應模塊的功能處理

瞭解生命週期更多的內容,請查看 鉤子

主要的模塊有 :

  • attributes.ts
  • class.ts
  • dataset.ts
  • eventlisteners.ts
  • hero.ts
  • module.ts
  • props.ts
  • style.ts

其中 attributes class dataset props 四個比較簡單,都是定義了 create update 兩個鉤子,

eventlisteners hero style 這三個模塊就複雜一點。

另外 module.ts 只是定義了這些模塊所用到的一些鉤子

// 定義模塊的鉤子
export interface Module {
    pre: PreHook;
    create: CreateHook;
    update: UpdateHook;
    destroy: DestroyHook;
    remove: RemoveHook;
    post: PostHook;
}

接下來我們來看看其他的模塊

attributes 模塊

文件位置 : ./src/modules/attributes.ts

我們先拉到最後

// 創建以及更新的鉤子
export const attributesModule = {
    create: updateAttrs,
    update: updateAttrs
} as Module;
export default attributesModule;

attributesModule 導出了兩個方法, 都是調用了 updateAttrs

這個表示,在創建元素的時候,以及更新的時候,都會觸發這兩個鉤子,來更新 attribute

updateAttrs

updateAttrs 主要接受兩個參數,oldVnodevnode

主要邏輯如下:

  • 遍歷新 vnode 所有的屬性,判斷在 oldVnode 中是否相等,修改不相等的屬性
  • 刪除不存在於 vnode 的屬性

代碼如下:

/**
 * 更新屬性
 */
function updateAttrs(oldVnode: VNode, vnode: VNode): void {
    var key: string,
        elm: Element = vnode.elm as Element,
        oldAttrs = (oldVnode.data as VNodeData).attrs,
        attrs = (vnode.data as VNodeData).attrs;

    if (!oldAttrs && !attrs) return;
    if (oldAttrs === attrs) return;
    oldAttrs = oldAttrs || {};
    attrs = attrs || {};

    // update modified attributes, add new attributes
    // 遍歷新的屬性,修改不相等的
    for (key in attrs) {
        const cur = attrs[key];
        const old = oldAttrs[key];
        if (old !== cur) {
            if (cur === true) {
                elm.setAttribute(key, '');
            } else if (cur === false) {
                elm.removeAttribute(key);
            } else {
                if (key.charCodeAt(0) !== xChar) {
                    // 如果不是 x 開頭
                    elm.setAttribute(key, cur);
                } else if (key.charCodeAt(3) === colonChar) {
                    // Assume xml namespace
                    elm.setAttributeNS(xmlNS, key, cur);
                } else if (key.charCodeAt(5) === colonChar) {
                    // Assume xlink namespace
                    elm.setAttributeNS(xlinkNS, key, cur);
                } else {
                    elm.setAttribute(key, cur);
                }
            }
        }
    }
    // remove removed attributes
    // use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value)
    // the other option is to remove all attributes with value == undefined
    // 刪除多餘的屬性
    for (key in oldAttrs) {
        if (!(key in attrs)) {
            elm.removeAttribute(key);
        }
    }
}

class 模塊

文件位置 : ./src/modules/class.ts

attribute 類似 , class 也是定義了 createupdate 兩個鉤子,統一由 updateClass 處理

這塊邏輯比較簡單 ,直接看代碼吧


function updateClass(oldVnode: VNode, vnode: VNode): void {
    var cur: any,
        name: string,
        elm: Element = vnode.elm as Element,
        oldClass = (oldVnode.data as VNodeData).class,
        klass = (vnode.data as VNodeData).class;

    // 新老的 className 都沒有
    if (!oldClass && !klass) return;

    // 新老的 className 沒變
    if (oldClass === klass) return;

    oldClass = oldClass || {};
    klass = klass || {};

    // 刪除不存在與新的 classList 的 className
    for (name in oldClass) {
        if (!klass[name]) {
            elm.classList.remove(name);
        }
    }

    // 新增 或刪除 class
    for (name in klass) {
        cur = klass[name];
        if (cur !== oldClass[name]) {
            (elm.classList as any)[cur ? 'add' : 'remove'](name);
        }
    }
}

dataset 模塊

文件位置 : ./src/modules/dataset.ts

attribute 類似 , dataset 也是定義了 createupdate 兩個鉤子,統一由 updateDataset 處理

這塊邏輯比較簡單 ,直接看代碼吧

const CAPS_REGEX = /[A-Z]/g;

/**
 * 更新或創建 dataset
 */
function updateDataset(oldVnode: VNode, vnode: VNode): void {
    let elm: HTMLElement = vnode.elm as HTMLElement,
        oldDataset = (oldVnode.data as VNodeData).dataset,
        dataset = (vnode.data as VNodeData).dataset,
        key: string;

    // 不變的情況下不處理
    if (!oldDataset && !dataset) return;
    if (oldDataset === dataset) return;

    oldDataset = oldDataset || {};
    dataset = dataset || {};
    const d = elm.dataset;

    // 刪除多餘的 dataset
    for (key in oldDataset) {
        if (!dataset[key]) {
            if (d) {
                if (key in d) {
                    delete d[key];
                }
            } else {
                // 將駝峯式改爲中劃線分割  eg: userName ----> user-name
                elm.removeAttribute(
                    'data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase()
                );
            }
        }
    }

    // 修改有變化的 dataset
    for (key in dataset) {
        if (oldDataset[key] !== dataset[key]) {
            if (d) {
                d[key] = dataset[key];
            } else {
                elm.setAttribute(
                    // 將駝峯式改爲中劃線分割  eg: userName ----> user-name
                    'data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(),
                    dataset[key]
                );
            }
        }
    }
}

props 模塊

文件位置 : ./src/modules/props.ts

attribute 類似 , props 也是定義了 createupdate 兩個鉤子,統一由 updateProps 處理

這塊邏輯比較簡單 ,直接看代碼吧

function updateProps(oldVnode: VNode, vnode: VNode): void {
    var key: string,
        cur: any,
        old: any,
        elm = vnode.elm,
        oldProps = (oldVnode.data as VNodeData).props,
        props = (vnode.data as VNodeData).props;

    if (!oldProps && !props) return;
    if (oldProps === props) return;

    oldProps = oldProps || {};
    props = props || {};

    // 刪除多餘的屬性
    for (key in oldProps) {
        if (!props[key]) {
            delete (elm as any)[key];
        }
    }

    // 添加新增的屬性
    for (key in props) {
        cur = props[key];
        old = oldProps[key];
        // key爲value的情況,再判斷是否value有變化
        // key不爲value的情況,直接更新
        if (old !== cur && (key !== 'value' || (elm as any)[key] !== cur)) {
            (elm as any)[key] = cur;
        }
    }
}

eventlisteners 模塊

eventlisteners 這一塊內容稍微多一點,故將其獨立出來一個章節。 傳送門 : 事件

style 模塊

待續。。。

hero 模塊

待續。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章