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
之外劈伴,還有space
,text
,svg_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)勿擾趴俘。