讓我們啟用 HTML 元素的渲染
什麼是 h 函式?

h 是 hyperscript 的縮寫。這個函式用 JavaScript 表示 HTML(Hyper Text Markup Language),因此得名!
到目前為止,我們已經讓以下原始碼工作:
import { createApp } from 'vue'
const app = createApp({
render() {
return 'Hello world.'
},
})
app.mount('#app')這是一個簡單地在螢幕上渲染「Hello World.」的函式。 由於只有一條訊息有點孤單,讓我們考慮一個也可以渲染 HTML 元素的開發者介面。 這就是 h 函式 的用武之地。這個 h 代表 hyperscript,作為在 JavaScript 中編寫 HTML(超文本標記語言)的函式提供。
h() 是 hyperscript 的縮寫 - 意思是「產生 HTML(超文本標記語言)的 JavaScript」。這個名稱繼承自許多虛擬 DOM 實作共享的約定。一個更具描述性的名稱可能是 createVnode(),但當您必須在渲染函式中多次呼叫此函式時,較短的名稱會有所幫助。
引用:https://vuejs.org/guide/extras/render-function.html#creating-vnodes
讓我們看看 Vue.js 中的 h 函式。
import { createApp, h } from 'vue'
const app = createApp({
render() {
return h('div', {}, [
h('p', {}, ['HelloWorld']),
h('button', {}, ['click me!']),
])
},
})
app.mount('#app')作為 h 函式的基本用法,您將標籤名稱指定為第一個參數,將屬性指定為第二個參數,將子元素陣列指定為第三個參數。 在這裡,我特別提到了「基本用法」,因為 h 函式實際上對其參數有多種語法,您可以省略第二個參數或不對子元素使用陣列。 但是,在這裡我們將以最基本的語法實作它。
我們應該如何實作它?
現在我們了解了開發者介面,讓我們決定如何實作它。 需要注意的重要一點是它如何用作渲染函式的回傳值。 這意味著 h 函式回傳某種物件並在內部使用該結果。
由於複雜的子元素很難理解,讓我們考慮實作簡單 h 函式的結果。
const result = h('div', { class: 'container' }, ['hello'])result 中應該儲存什麼樣的結果?(我們應該如何格式化結果以及如何渲染它?)
讓我們假設以下物件儲存在 result 中:
const result = {
type: 'div',
props: { class: 'container' },
children: ['hello'],
}換句話說,我們將從渲染函式接收類似於上面的物件,並使用它來執行 DOM 操作並渲染它。
圖像是這樣的(在 createApp 的 mount 內部):
const app: App = {
mount(rootContainer: HostElement) {
const node = rootComponent.render!()
render(node, rootContainer)
},
}嗯,唯一改變的是我們將 message 字串更改為 node 物件。 我們現在要做的就是在渲染函式中基於物件執行 DOM 操作。
實際上,這個物件有一個名字,「虛擬 DOM」。 我們將在虛擬 DOM 章節中更多地解釋虛擬 DOM,所以現在只需記住這個名字。\

「虛擬 DOM」聽起來可能很複雜,但它其實只是一個 JavaScript 物件!它用簡單的 { type, props, children } 結構來表示 DOM。
實作 h 函式
首先,建立必要的檔案。
pwd # ~
touch packages/runtime-core/vnode.ts
touch packages/runtime-core/h.ts在 vnode.ts 中定義型別。這就是我們在 vnode.ts 中要做的全部。
export interface VNode {
type: string
props: VNodeProps
children: (VNode | string)[]
}
export interface VNodeProps {
[key: string]: any
}接下來,在 h.ts 中實作函式體。
export function h(
type: string,
props: VNodeProps,
children: (VNode | string)[],
) {
return { type, props, children }
}現在,讓我們嘗試在遊樂場中使用 h 函式。
import { createApp, h } from 'chibivue'
const app = createApp({
render() {
return h('div', {}, ['Hello world.'])
},
})
app.mount('#app')螢幕上的顯示被破壞了,但如果您在 apiCreateApp 中加入紀錄,您可以看到它按預期工作。
mount(rootContainer: HostElement) {
const vnode = rootComponent.render!();
console.log(vnode); // 檢查紀錄
render(vnode, rootContainer);
},現在,讓我們實作渲染函式。
在 RendererOptions 中實作 createElement,createText 和 insert。
export interface RendererOptions<HostNode = RendererNode> {
createElement(type: string): HostNode // 加入
createText(text: string): HostNode // 加入
setElementText(node: HostNode, text: string): void
insert(child: HostNode, parent: HostNode, anchor?: HostNode | null): void // 加入
}在渲染函式中實作 renderVNode 函式。現在,我們忽略 props。
export function createRenderer(options: RendererOptions) {
const {
createElement: hostCreateElement,
createText: hostCreateText,
insert: hostInsert,
} = options
function renderVNode(vnode: VNode | string) {
if (typeof vnode === 'string') return hostCreateText(vnode)
const el = hostCreateElement(vnode.type)
for (const child of vnode.children) {
const childEl = renderVNode(child)
hostInsert(childEl, el)
}
return el
}
const render: RootRenderFunction = (vnode, container) => {
const el = renderVNode(vnode)
hostInsert(el, container)
}
return { render }
}在 runtime-dom 的 nodeOps 中,定義實際的 DOM 操作。
export const nodeOps: RendererOptions<Node> = {
// 加入
createElement: tagName => {
return document.createElement(tagName)
},
// 加入
createText: (text: string) => {
return document.createTextNode(text)
},
setElementText(node, text) {
node.textContent = text
},
// 加入
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null)
},
}嗯,此時,您應該能夠在螢幕上渲染元素。
嘗試在遊樂場中編寫和測試各種東西!
import { createApp, h } from 'chibivue'
const app = createApp({
render() {
return h('div', {}, [
h('p', {}, ['Hello world.']),
h('button', {}, ['click me!']),
])
},
})
app.mount('#app')耶!現在我們可以使用 h 函式來渲染各種標籤!


現在你已經可以用 JavaScript 表示 HTML 了!透過巢狀結構,還能建立任意複雜的 UI。
到此為止的原始碼: chibivue (GitHub)
