「螳螂是一種交配完母方就會把公方吃掉的動物,所以螳螂寶寶意指死了爹的孤兒。」——自尤雨溪望
轉眼間鐵人賽就過去了1/3,我們終於來到了視圖相關的第一章節。
既然要將dom元素放進內存中,我們需要先定義一個能描述dom元素訊息的資料格式。先建立/src/runtime-core/vnode.ts並在裡面寫一個interface吧:
export const Text = Symbol('Text')
export interface VNode {
type: string | symbol,
props: Record<string, any>,
children: Array<VNode | string> | string,
el: HTMLElement | Text,
key: string,
__v_isVnode: true,
shapeFlag: number;
}
const Text = Symbol('Text')的部分是去年在看vue3源碼的影片時看到的,但今天我去翻真正的源碼時沒找到這個東西,不知道是比較舊的版本的代碼,還是當時看的影片的講師為了方便理解而改寫的。不過Text代表這個虛擬dom是純文字,也挺語意化的,就接著用了。
VNode的type代表這個節點屬於哪種標籤或純文字,例如type可以是h1、div、p等等;props代表這個節點的屬性,它的key可以包含class、id、href等等或任何客製化屬性;children代表這個節點的子節點;el用於存儲這個節點的dom元素(非虛擬dom);key就是v-for的那個key,之後在比對新舊節點時會解釋;__is_Vnode代表這個節點是虛擬dom。
舉例來說,如果在template中有這樣一組標籤:
<div style="color: blueviolet;">
不曾遇見劉備的<strong>臥龍</strong>,也不過就是諸葛孔明。
</div>
先不考慮最下面的shapeFlag,將其翻譯成虛擬dom,就會是這樣:
{
type: 'div',
props: {
style: 'color: blueviolet;'
},
children: [
'不曾遇見劉備的',
{
type: 'strong',
props: {},
children: '臥龍',
__is_vNode: true
},
',也不過就是諸葛孔明。'
],
__v_isVnode: true
};
至於上面沒提到的shapeFlag,用到了一個叫位運算的算法。
簡單來說,當一個對象具備零至多種特徵,例如有一個人既是男人,還是個工程師,我們可以用['男人','工程師']來描述他,但這麼做是比較佔用硬盤或內存空間的。當有許多個對象需要記錄特徵時,我們用2的任意次方對應到一項特徵,把男人看作是1(0001),工程師看作16(1000),這個人的特徵就可以記為17(1001),僅以一個十位數字便能取代一個陣列所佔用的空間。
位運算是一種相對好上手且實用的算法,感興趣的小夥伴不妨可以上網查查,或者參考我以前拍的影片,然後順便幫我點一下訂閱。
shapeFlag便是用這種位運算的算法,用最小的內存空間記錄虛擬dom中的每個節點,他們所擁有的特徵。
export const enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEEP_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}
<<運算符是用來描述2的次方,1 << x就是二進位的1後面補x個0,也就是十進位的2的x次方。例如:
1 << 1就是二進位的1右邊補一個0,也就是二進位的10,十進位的2。
1 << 2是二進位的1右邊補2個0,也就是二進位的100,十進位的4。
1 << 3是二進位的1000,十進位的8……依此類推。
|運算符同樣用於二進位,a | b回傳a和b作為二進位至少一邊有值的位數。例如:
2 | 4是010和100至少一邊有的位數,也就是110,十進位的6。
10 | 15是1010和1111至少一邊有的位數,也就是1111,十進位的15。
|和<<都可以打開瀏覽器的console,自己試試看喔。
我們可以看到ELEMENT普通的元素是1(1)、TEXT_CHILDREN純文字是8(1000),ARRAY_CHILDREN是16(10000)……而具備STATEFUL_COMPONENT或FUNCTIONAL_COMPONENT任一種特徵的,就被歸類為COMPONENT。
如此一來我們便定義了虛擬dom節點的資料結構,之後我們將定義一個能將虛擬dom渲染成真實dom元素的函數。
githubmain分支commit「[Day 10] 虛擬dom」