Skip to content

讓我們啟用 HTML 元素的渲染

什麼是 h 函式?

Kawaiko mascot - question
「h」是什麼的縮寫?

hhyperscript 的縮寫。這個函式用 JavaScript 表示 HTML(Hyper Text Markup Language),因此得名!

到目前為止,我們已經讓以下原始碼工作:

ts
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 函式。

ts
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 函式的結果。

ts
const result = h('div', { class: 'container' }, ['hello'])

result 中應該儲存什麼樣的結果?(我們應該如何格式化結果以及如何渲染它?)

讓我們假設以下物件儲存在 result 中:

ts
const result = {
  type: 'div',
  props: { class: 'container' },
  children: ['hello'],
}

換句話說,我們將從渲染函式接收類似於上面的物件,並使用它來執行 DOM 操作並渲染它。
圖像是這樣的(在 createAppmount 內部):

ts
const app: App = {
  mount(rootContainer: HostElement) {
    const node = rootComponent.render!()
    render(node, rootContainer)
  },
}

嗯,唯一改變的是我們將 message 字串更改為 node 物件。 我們現在要做的就是在渲染函式中基於物件執行 DOM 操作。

實際上,這個物件有一個名字,「虛擬 DOM」。 我們將在虛擬 DOM 章節中更多地解釋虛擬 DOM,所以現在只需記住這個名字。\

Kawaiko mascot - funny
虛擬 DOM 的本質

「虛擬 DOM」聽起來可能很複雜,但它其實只是一個 JavaScript 物件!它用簡單的 { type, props, children } 結構來表示 DOM。

實作 h 函式

首先,建立必要的檔案。

sh
pwd # ~
touch packages/runtime-core/vnode.ts
touch packages/runtime-core/h.ts

在 vnode.ts 中定義型別。這就是我們在 vnode.ts 中要做的全部。

ts
export interface VNode {
  type: string
  props: VNodeProps
  children: (VNode | string)[]
}

export interface VNodeProps {
  [key: string]: any
}

接下來,在 h.ts 中實作函式體。

ts
export function h(
  type: string,
  props: VNodeProps,
  children: (VNode | string)[],
) {
  return { type, props, children }
}

現在,讓我們嘗試在遊樂場中使用 h 函式。

ts
import { createApp, h } from 'chibivue'

const app = createApp({
  render() {
    return h('div', {}, ['Hello world.'])
  },
})

app.mount('#app')

螢幕上的顯示被破壞了,但如果您在 apiCreateApp 中加入紀錄,您可以看到它按預期工作。

ts
mount(rootContainer: HostElement) {
  const vnode = rootComponent.render!();
  console.log(vnode); // 檢查紀錄
  render(vnode, rootContainer);
},

現在,讓我們實作渲染函式。
在 RendererOptions 中實作 createElementcreateTextinsert

ts
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

ts
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 操作。

ts
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)
  },
}

嗯,此時,您應該能夠在螢幕上渲染元素。
嘗試在遊樂場中編寫和測試各種東西!

ts
import { createApp, h } from 'chibivue'

const app = createApp({
  render() {
    return h('div', {}, [
      h('p', {}, ['Hello world.']),
      h('button', {}, ['click me!']),
    ])
  },
})

app.mount('#app')

耶!現在我們可以使用 h 函式來渲染各種標籤!

VNode log from a simple h function

Kawaiko mascot - surprise
h 函式完成!

現在你已經可以用 JavaScript 表示 HTML 了!透過巢狀結構,還能建立任意複雜的 UI。

到此為止的原始碼: chibivue (GitHub)

基於 MIT 許可證發布。