深入淺出MV*框架源碼(六):Moon中的組件

前言

組件化是前端生產(chǎn)力提升的另一大變革,之前jq時(shí)代大家都是一份代碼復(fù)制來復(fù)制去,對(duì)代碼復(fù)用性和可維護(hù)性非常不友好。現(xiàn)在MV*框架全部是支持組件的翼闹,只不過使用上略有區(qū)別。

Component

首先蒋纬,讓我們注冊一個(gè)具有template橄碾、props和自定義事件的組件:

Moon.component("my-component", {
    // options
    template: `<h3>This is a Component {{content}} 計(jì)數(shù)器{{count}}! 
    <button m-on:click='increment'>計(jì)數(shù)器</button></h3>`,
    props: ['content'],
    data: function() {
        return {
            count: 0
        }
    },
    methods: {
        increment: function() {
            this.set("count", this.get("count") + 1);
            this.emit("increment");
        }
    }
});

它是一個(gè)計(jì)數(shù)器組件,擁有content這個(gè)從父組件傳來的prop颠锉,也有increment這個(gè)事件可以發(fā)射到父組件以便相互通信。
讓我們打個(gè)斷點(diǎn)看看里面發(fā)生了什么:


component.jpg

可以看出史汗,我們傳了name和options兩個(gè)參數(shù)用于注冊組件琼掠。首先,初始化了組件的options停撞,然后讓MoonComponent繼承Moon瓷蛙,并重寫了init方法(主要是為了處理props數(shù)據(jù))。
之后把MoonComponent和options掛載到全局的components[name]上戈毒,返回MoonComponent構(gòu)造函數(shù)艰猬。

html->code過程與組件

組件既然是繼承自Moon實(shí)例,自然也少不了html->tokens->ast->code->vnode->node->html的過程埋市。拿我們這個(gè)例子來說也是一樣:

<my-component content="'父組件內(nèi)容'" m-on:increment="incrementTotal"></my-component>

這段html自然也會(huì)先被轉(zhuǎn)成code冠桃。只不過組件對(duì)于編譯過程中來說暫時(shí)還沒有什么特別的。

code->html與組件

組件html被稀里糊涂轉(zhuǎn)成code了道宅,之后解析code的過程就變得麻煩許多食听。

第一個(gè)與之相遇的就是m函數(shù):


m-component.jpg

可見它會(huì)先判斷此組件是否是函數(shù)式組件胸蛛,是的話就按函數(shù)式的方法返回一段函數(shù)式組件vnode,不是的話先掛載到meta上去再創(chuàng)建vnode樱报。

函數(shù)式組件

函數(shù)式組件沒有任何狀態(tài)葬项,也沒有生命周期方法。它只是一個(gè)接收參數(shù)的函數(shù)迹蛤。創(chuàng)建函數(shù)式組件的代碼其實(shí)很好懂:

var createFunctionalComponent = function(props, children, functionalComponent) {
    var options = functionalComponent.options;
    var attrs = props.attrs;
    var data = options.data;

    if (data === undefined) {
        data = {};
    }

    // Merge data with provided props
    var propNames = options.props;
    if (propNames === undefined) {
        data = attrs;
    } else {
        for (var i = 0; i < propNames.length; i++) {
            var prop = propNames[i];
            data[prop] = attrs[prop];
        }
    }

    // Call render function
    return functionalComponent.options.render(m, {
        data: data,
        slots: getSlots(children)
    });
}

它其實(shí)就是將一個(gè)擁有attrs(props會(huì)被合并進(jìn)去)和data的組件直接render民珍,至于slot,我們稍后再講盗飒。

createComponentFromVNode

它區(qū)別于createNodeFromVNode嚷量,專門用于產(chǎn)生組件vnode的node:


createComponentFromVNode.jpg

可以看出,它分四步走:

  1. 合并prop到data屬性上去
  2. 擴(kuò)展vnode的事件監(jiān)聽到組件實(shí)例上去
  3. 獲取slot和實(shí)際掛載元素并build產(chǎn)生node
  4. 混入vnode和node然后返回實(shí)際node

appendChild/removeChild/replaceChild

在這期間它可能還會(huì)遇見一些vnode層面上的增/刪/替操作:

// appendChild Check for Component
var component = null;
if ((component = vnode.meta.component) !== undefined) {
    createComponentFromVNode(node, vnode, component);
}

// removeChild Check for Component
var componentInstance = null;
if ((componentInstance = node.__moon__) !== undefined) {
    // Component was unmounted, destroy it here
    componentInstance.destroy();
}

// Check for Component
var componentInstance = null;
if ((componentInstance = oldNode.__moon__) !== undefined) {
    // Component was unmounted, destroy it here
    componentInstance.destroy();
}
// Replace It
parent.replaceChild(newNode, oldNode);
// replaceChild Check for Component
var component = null;
if ((component = vnode.meta.component) !== undefined) {
    createComponentFromVNode(newNode, vnode, component);
}

增的情況最簡單箩兽,如果有組件就創(chuàng)建一個(gè)組件的node津肛,刪的情況則是有組件就銷毀掉。替的話則是結(jié)合了增和刪的情況汗贫,先刪后增身坐。

hydrate

在build->patch的最后幾步,還會(huì)遇到hydrate里處理組件的代碼:

// Check for Component
if (vnode.meta.component !== undefined) {
    // Diff the Component
    diffComponent(node, vnode);

    // Skip diffing any children
    return node;
}

也就是diff組件

diffComponent

diff組件做的事情也很簡單:


diffComponent.jpg

若當(dāng)前node沒有被掛載落包,直接創(chuàng)建一個(gè)node部蛇。否則獲取當(dāng)前組件實(shí)例,對(duì)node的props和vnode的attrs進(jìn)行diff咐蝇,diff成功就標(biāo)記componentChanged標(biāo)志位為真并在最后build一次這個(gè)實(shí)例涯鲁。
還有一件事情要處理:如果vnode還有子元素,就要給組件實(shí)例加上slot有序。

slot與getSlots

slot是組件內(nèi)嵌的元素抹腿,思想來源于shadow dom
Moon中關(guān)于slot的關(guān)鍵函數(shù)式getSlots:

var getSlots = function(children) {
    var slots = {};

    // Setup default slots
    var defaultSlotName = "default";
    slots[defaultSlotName] = [];

    // No Children Means No Slots
    if (children.length === 0) {
        return slots;
    }

    // Get rest of the slots
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        var childProps = child.props.attrs;
        var slotName = "";
        var slotValue = null;

        if ((slotName = childProps.slot) !== undefined) {
            slotValue = slots[slotName];
            if (slotValue === undefined) {
                slots[slotName] = [child];
            } else {
                slotValue.push(child);
            }
            delete childProps.slot;
        } else {
            slots[defaultSlotName].push(child);
        }
    }

    return slots;
}

可以發(fā)現(xiàn)它傳入的是children參數(shù),先會(huì)建立一個(gè)默認(rèn)的slots旭寿,然后從children里取每個(gè)child警绩,如果slot是不是具名slot就放進(jìn)默認(rèn)slot里。

getSlots使用處

  1. createFunctionalComponent
return functionalComponent.options.render(m, {
    data: data,
    slots: getSlots(children)
});
創(chuàng)建函數(shù)式組件的時(shí)候slot會(huì)被提取出來放到創(chuàng)建組件的options里去盅称。
  1. createComponentFromVNode
componentInstance.$slots = getSlots(vnode.children);

類似創(chuàng)建函數(shù)式組件肩祥,創(chuàng)建實(shí)例組件也是把slot提取出來掛載到實(shí)例的$slots屬性上。

  1. diffComponent
// If it has children, resolve any new slots
if (vnode.children.length !== 0) {
    componentInstance.$slots = getSlots(vnode.children);
    componentChanged = true;
}

diff的過程中也會(huì)修改組件實(shí)例缩膝,可以發(fā)現(xiàn)與createComponentFromVNode類似混狠,是把slot提取出來掛載到實(shí)例的$slots屬性上。

  1. generateNode
else if (node.type === "slot") {
    parent.meta.shouldRender = true;
    parent.deep = true;

    var slotName = node.props.name;
    return ("instance.$slots[\"" + (slotName === undefined ? "default" : slotName.value) + "\"]");
} 

generate的過程中也會(huì)處理slot疾层,處理方式自然是把它變成code了将饺。

總結(jié)

組件化對(duì)我們前端開發(fā)工程化、提高代碼復(fù)用性和可維護(hù)性起到了革命式的作用,從此各種UI組件庫如雨后春筍似的冒了出來俯逾,對(duì)我們前端解放生產(chǎn)力來說是非常大的一個(gè)進(jìn)步贸桶。相信有一天,HTML5會(huì)支持原生定義組件和Slot桌肴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皇筛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坠七,更是在濱河造成了極大的恐慌水醋,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彪置,死亡現(xiàn)場離奇詭異拄踪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拳魁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門惶桐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人潘懊,你說我怎么就攤上這事姚糊。” “怎么了授舟?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵救恨,是天一觀的道長。 經(jīng)常有香客問我释树,道長肠槽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任奢啥,我火速辦了婚禮秸仙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桩盲。我一直安慰自己筋栋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布正驻。 她就那樣靜靜地躺著,像睡著了一般抢腐。 火紅的嫁衣襯著肌膚如雪姑曙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天迈倍,我揣著相機(jī)與錄音伤靠,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宴合,可吹牛的內(nèi)容都是我干的焕梅。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼卦洽,長吁一口氣:“原來是場噩夢啊……” “哼贞言!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起阀蒂,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤该窗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蚤霞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酗失,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年昧绣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了规肴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夜畴,死狀恐怖拖刃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斩启,我是刑警寧澤序调,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站兔簇,受9級(jí)特大地震影響发绢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垄琐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一边酒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狸窘,春花似錦墩朦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陋气,卻和暖如春劳吠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巩趁。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工痒玩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓蠢古,卻偏偏與公主長得像奴曙,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子草讶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容