各位親~愛的讀者們!聽我說!聽我炫耀一下!我!我我我!就!在!昨!天!就在寫這篇草稿的時間點的前一天!我升官加薪啦~~~~~
嘿嘿~加薪的感覺真~好。
好我炫耀完了,謝謝你們聽我炫耀,雖然寫這篇草稿的時間點的我根本不知道這篇草稿被發出去的時候我還有沒有讀者,反正也已經炫耀完了,就讓我們進入正題。
我們先假設我們有能力解析開發者寫在template裡的html,如何實現先擱著,總之就假設我們能把每個標籤的標籤名、屬性、綁定的事件以及內容都區分出來,現在我們要實現一個函數,只要傳入標籤名、屬性、事件及內容,就能回傳虛擬dom,這個函數就叫做h函數。
為什麼叫h函數呢?我也不知道,想知道去問尤雨溪本人,我是自尤雨溪望,不是尤雨溪030
題外話,雖然我這輩子沒用過,不過聽說在vue的專案,你可以直接import { h } from 'vue'
,用這個h函數創建虛擬dom。今天我們學了怎麼使用h函數,或許哪天開發時能用到也說不定。
總之我們先新建/src/runtime-core/h.ts這個文件吧。
import { createVNode, isVnode, VNode } from "./vnode";
type Props = Record<string, any>;
type ChildNode = VNode | string;
export const h: {
(type: string, h: VNode): VNode;
(type: string, props: Props): VNode;
(type: string, children: ChildNode | ChildNode[]): VNode;
(type: string, props: Props, children?: ChildNode | ChildNode[]): VNode;
(type: string, props: Props, ...args: ChildNode[]): VNode;
} = (...args) => {
};
h: 後面那個大括號內的東西是ts的函數重載,可以描述這個h函數可接受的參數及其返回值。
由於一個函數能接收的參數可能不只一種類型,而不同的類型可能可以組合出不同的效果,有時我們會有需要能夠描述函數能接收的參數有哪些組合的情境。
函數重載就是用來處理這種情境的做法。
先從參數看起吧。從函數重載的內容,我們可以看到h函數能接收以下幾種組合的參數:
h('ul', h('li', /* ... */)) // <ul><li></li></ul>
h('div', { class: 'nmsl' }) // <div class="nmsl"></div>
h('ul', [h('li', /* ... */), h('li', /* ... */)]) // <ul><li></li><li></li></ul>
h('ul', { class: 'nmsl' }, [h('li', /* ... */), 'NMSL', h('li', /* ... */)]) // <ul class="nmsl"><li></li>NMSL<li></li></ul>
h('ul', {}, h('li', /* ... */), h('li', /* ... */), h('li', /* ... */)) // <ul><li></li><li></li><li></li></ul>
由於h函數返回VNode,因此我們可以直接把函數重載中的VNode看成是一個h函數的調用。
因此我們需要先判斷接收到的參數有幾個、分別是甚麼型別,來決定要回傳怎樣的VNode。
const l = args.length;
const type = args[0];
if (l === 2) {
} else {
}
如果只有兩個參數,我們僅需判斷第二個參數是字串、h函數、屬性、還是裝著h函數的陣列。因此我們先針對上述不同的情況去寫if (l === 2)的情況:
const propsOrChildren = args[1]
if (
propsOrChildren &&
typeof propsOrChildren === 'object' &&
!Array.isArray(propsOrChildren)
) {
if (isVnode(propsOrChildren)) {
// 元素需要循環創建,故包成陣列:h('tag',h(any)) => h('tag',[h(any)])
return createVNode(type, null, [<VNode>propsOrChildren])
}
// h('tag',{})
return createVNode(type, propsOrChildren)
} else {
// h('tag',[h(any),...]) or h('tag','text')
return createVNode(type, null, <ChildNode[]>propsOrChildren)
}
接著考慮參數超過兩個的情況。
參數超過兩個,有可能是3個,也有可能是超過3個,例如是4、5、6……甚至以上。
const props = <Props>args[1]
let children = args[2]
if (l > 3) {
// h('tag',{},'text',h(any),'text')
children = Array.from(args).slice(2)
} else if(l === 3 && isVnode(children)) {
// h('tag',{},h(any))
children = [<VNode>children]
}
// h('tag',{},'text') or h('tag',{},[h(any),...])
return createVNode(type, props, <string | Array<VNode> | Array<string>>children)
超過3個,從args第二項開始包成陣列就是目標的子節點,所以children = Array.from(args).slice(2)
。
如果剛好3個,就把children變成要馬是子節點,要馬是字串的形式,以配合createVNode可接收的參數。
之後再調用createVNode,即可將我們之後解析好的template變成虛擬dom,接下來我們剩下的就只有解析template了。
githubmain分支commit「[Day 21]h函數」