Svelte筆記三:runtime源碼解讀

svelte的源碼很簡(jiǎn)單是由兩大部分組成已艰,compiler和runtime。

compiler就是一個(gè)編譯器將svelte模版語(yǔ)法轉(zhuǎn)換為瀏覽器能夠識(shí)別的代碼哩掺。而runtime則是在瀏覽器中幫助業(yè)務(wù)代碼運(yùn)作的運(yùn)行時(shí)函數(shù)。所以說(shuō)runtime是svelte框架最核心的部分嚼吞,它也解釋了svelte是如何在沒(méi)有virtual dom的情況下也照樣運(yùn)行的盒件。今天我們r(jià)eview一下runtime代碼。

svelte的runtime主要由fragment和component組成舱禽,而component是包含了fragment。它們有著獨(dú)立的生命周期誊稚,將邏輯層和渲染層分離。

Fragment

Svelte官方example提供了compile出來(lái)的Js output片吊。這些output就是運(yùn)行在瀏覽器的源碼,根據(jù)內(nèi)容知道svelte的基本運(yùn)作协屡,讓開發(fā)者很清除它內(nèi)部的每一步運(yùn)作俏脊。下面這個(gè)栗子很簡(jiǎn)單,就是對(duì)hello的一個(gè)字符串插值爷贫。而name是一個(gè)變量。

<script>
    let name = 'world';
</script>

<h1>Hello {name}!</h1>

編譯出來(lái)的結(jié)果:

/* App.svelte generated by Svelte v3.24.0 */
import {
    SvelteComponent,
    detach,
    element,
    init,
    insert,
    noop,
    safe_not_equal
} from "svelte/internal";

function create_fragment(ctx) {
    let h1;

    return {
        c() {
            h1 = element("h1");
            h1.textContent = `Hello ${name}!`;
        },
        m(target, anchor) {
            insert(target, h1, anchor);
        },
        p: noop,
        i: noop,
        o: noop,
        d(detaching) {
            if (detaching) detach(h1);
        }
    };
}

let name = "world";

class App extends SvelteComponent {
    constructor(options) {
        super();
        init(this, options, null, create_fragment, safe_not_equal, {});
    }
}

export default App;

編譯出來(lái)的結(jié)果就是有一個(gè)初始化函數(shù)漫萄,叫create_fragment盈匾,它是用于dom的初始掛載。它使用了element函數(shù)削饵,通過(guò)查閱源碼src/runtime/internal/dom,我們知道它的作用就是用來(lái)創(chuàng)建h1標(biāo)簽實(shí)例窿撬,并且填入可變內(nèi)容。除了element之外劈伴,還有spacetextsvg_element等都是用于生成真實(shí)dom新啼,分別是對(duì)空格,純文本师抄,svg進(jìn)行生成處理教硫。

export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
    return document.createElement<K>(name);
}

export function text(data: string) {
    return document.createTextNode(data);
}

export function space() {
    return text(' ');
}

create_fragment的過(guò)程還包含有c,m,p,i,o,d等特殊名稱的函數(shù)叨吮,這些函數(shù)并非編譯混淆瞬矩,而是Fragment內(nèi)部的生命周期縮寫。Fragment指得是真實(shí)dom的節(jié)點(diǎn)景用,它擁有著獨(dú)立的生命周期和屬性。源碼中src/runtime/internal/Component介紹了它的定義割粮,它是一個(gè)真實(shí)的dom元素集合,它的屬性并非組件屬性(如下方ts類型定義)舀瓢,分別包含了create,claim,hydrate,mount,update,mesure,fix,animate,intro,outro,destory,組件的真實(shí)變化會(huì)影響Fragment的變化耗美,F(xiàn)ragment的變化影響真實(shí)的dom京髓,從上面例子看在create的過(guò)程中它創(chuàng)建了h1標(biāo)簽,在mount的過(guò)程將剛才創(chuàng)建的h1掛載到頁(yè)面中商架,在update的過(guò)程沒(méi)有任何操作堰怨,在detach的過(guò)程銷毀該Fragment蛇摸。

interface Fragment {
    key: string|null;
    first: null;
    /* create  */ c: () => void;
    /* claim   */ l: (nodes: any) => void;
    /* hydrate */ h: () => void;
    /* mount   */ m: (target: HTMLElement, anchor: any) => void;
    /* update  */ p: (ctx: any, dirty: any) => void;
    /* measure */ r: () => void;
    /* fix     */ f: () => void;
    /* animate */ a: () => void;
    /* intro   */ i: (local: any) => void;
    /* outro   */ o: (local: any) => void;
    /* destroy */ d: (detaching: 0|1) => void;
}

Component

SvelteComponent則是包含了svelte組件內(nèi)置的屬性和生命周期,它們與Fragment的屬性和生命周期是息息相關(guān)赶袄,SvelteComponent是依賴于Fragment,組件的變化會(huì)觸發(fā)Fragment的變化绞吁。它是一個(gè)相輔相成的組合。源碼中還有SvelteComponent和SvelteElement的細(xì)分家破,不同點(diǎn)在于Web Component的組件的支持,這里就不再展開汰聋。

Component擁有四個(gè)生命周期,分別是mount玄妈,beforeUpdate, afterUpdate拟蜻,destory枯饿。沒(méi)有create階段是因?yàn)閟velte沒(méi)有virtual dom酝锅。所以在組件層面奢方,它沒(méi)有像vue那么復(fù)雜。

數(shù)據(jù)流

react的單向數(shù)據(jù)流稿蹲,vue的雙向綁定,那么svelte是怎么樣實(shí)現(xiàn)數(shù)據(jù)流的呢苛聘?

下面是我們業(yè)務(wù)中經(jīng)常見到的代碼嫉入,點(diǎn)擊按鈕請(qǐng)求數(shù)據(jù)然后設(shè)置到變量璧尸,觸發(fā)dom內(nèi)容的變化咒林。svelte的寫法形似vue的寫法爷光,但是它的runtime原理并沒(méi)有雙向綁定。編譯后的代碼除了有上面所說(shuō)的create和mount等f(wàn)ragment生命周期屬性外欢瞪,其他代碼更多表現(xiàn)了數(shù)據(jù)流的形式徐裸。

<script>
    let num = 1;

    async function handleClick() {
        const res = await fetch(`tutorial/random-number`);
        const text = await res.text();

        if (res.ok) {
            num = text;
            return text;
        } else {
            throw new Error(text);
        }
    }
</script>

<button on:click={handleClick}>
    generate random number
</button>


<p>The number is {num}</p>

編譯出來(lái)的結(jié)果:

/* App.svelte generated by Svelte v3.24.0 */
import {
    SvelteComponent,
    append,
    detach,
    element,
    init,
    insert,
    listen,
    noop,
    safe_not_equal,
    set_data,
    space,
    text
} from "svelte/internal";

function create_fragment(ctx) {
    let button;
    let t1;
    let p;
    let t2;
    let t3;
    let mounted;
    let dispose;

    return {
        c() {
            button = element("button");
            button.textContent = "generate random number";
            t1 = space();
            p = element("p");
            t2 = text("The number is ");
            t3 = text(/*num*/ ctx[0]);
        },
        m(target, anchor) {
            insert(target, button, anchor);
            insert(target, t1, anchor);
            insert(target, p, anchor);
            append(p, t2);
            append(p, t3);

            if (!mounted) {
                dispose = listen(button, "click", /*handleClick*/ ctx[1]);
                mounted = true;
            }
        },
        p(ctx, [dirty]) {
            if (dirty & /*num*/ 1) set_data(t3, /*num*/ ctx[0]);
        },
        i: noop,
        o: noop,
        d(detaching) {
            if (detaching) detach(button);
            if (detaching) detach(t1);
            if (detaching) detach(p);
            mounted = false;
            dispose();
        }
    };
}

function instance($$self, $$props, $$invalidate) {
    let num = 1;

    async function handleClick() {
        const res = await fetch(`tutorial/random-number`);
        const text = await res.text();

        if (res.ok) {
            $$invalidate(0, num = text);
            return text;
        } else {
            throw new Error(text);
        }
    }

    return [num, handleClick];
}

class App extends SvelteComponent {
    constructor(options) {
        super();
        init(this, options, instance, create_fragment, safe_not_equal, {});
    }
}

export default App;

代碼中重贺,handleClick函數(shù)被封裝在一個(gè)名為instance的方法當(dāng)中骑祟,而它的入?yún)?dāng)中有個(gè)$$invalidate的回調(diào)函數(shù),用于變量的設(shè)置怯晕,把接口異步獲取的數(shù)據(jù)設(shè)置回調(diào)函數(shù)當(dāng)中缸棵。而它在組件的調(diào)用如下舟茶,重點(diǎn)在于回調(diào)函數(shù)當(dāng)中堵第,instance只會(huì)在初始化的時(shí)候調(diào)用,但是回調(diào)函數(shù)$$invalidate可以在各種異步情況調(diào)用客燕。它會(huì)觸發(fā)make_dirty的方法,而它觸發(fā)了schedule_update也搓,在一個(gè)微任務(wù)當(dāng)中涵紊,觸發(fā)flush將一段時(shí)間內(nèi)的變量操作都執(zhí)行掉傍妒。實(shí)現(xiàn)變量的處理摸柄,flush函數(shù)的具體實(shí)現(xiàn)請(qǐng)查看源碼(src/runtime/internal/Component.ts)。flush的過(guò)程中會(huì)觸發(fā)Fragment的update以及Component的update嗦玖。

    $$.ctx = instance
        ? instance(component, prop_values, (i, ret, ...rest) => {
            const value = rest.length ? rest[0] : ret;
            if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
                if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
                if (ready) make_dirty(component, i);
            }
            return ret;
        })
        : [];

由此可見跃脊,svelte是單向數(shù)據(jù)流宇挫,很多數(shù)據(jù)工作已經(jīng)在compile的過(guò)程當(dāng)中已經(jīng)完成酪术。runtime更多是服務(wù)于瀏覽器層面的數(shù)據(jù)流轉(zhuǎn)化。

題外話

shopee橡疼,又稱蝦皮,是一家騰訊投資的跨境電商平臺(tái)欣除。這里加班少挪略,技術(shù)氛圍好历帚。如果想和我并肩作戰(zhàn)一起學(xué)習(xí),可以找我內(nèi)推澈蟆。郵箱weiping.xiang@shopee.com,非誠(chéng)勿擾趴俘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奏赘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子磨淌,更是在濱河造成了極大的恐慌,老刑警劉巖缚柳,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搪锣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡构舟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門弹澎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人苦蒿,你說(shuō)我怎么就攤上這事麦撵」舫Γ” “怎么了免胃?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵惫撰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我扼雏,道長(zhǎng)坚嗜,這世上最難降的妖魔是什么诗充? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮碟绑,結(jié)果婚禮上茎匠,老公的妹妹穿的比我還像新娘格仲。我一直安慰自己诵冒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布侮东。 她就那樣靜靜地躺著豹芯,像睡著了一般苗桂。 火紅的嫁衣襯著肌膚如雪告组。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天便锨,我揣著相機(jī)與錄音,去河邊找鬼放案。 笑死矫俺,一個(gè)胖子當(dāng)著我的面吹牛吱殉,可吹牛的內(nèi)容都是我干的厘托。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼押赊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了流礁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤神帅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后找御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凹联,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年哆档,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓜浸。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡插佛,死狀恐怖杠巡,靈堂內(nèi)的尸體忽然破棺而出雇寇,到底是詐尸還是另有隱情,我是刑警寧澤锨侯,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站囚痴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏奕谭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一血柳、第九天 我趴在偏房一處隱蔽的房頂上張望蹬昌。 院中可真熱鬧攀隔,春花似錦皂贩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)愚争。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背燕少。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诚撵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓寿烟,卻偏偏與公主長(zhǎng)得像辛燥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挎塌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345