渲染函數 & JSX
在絕大多數情況下,Vue 推薦使用模板語法來創建應用。然而在某些使用場景下,我們真的需要用到 JavaScript 完全的編程能力。這時 渲染函數 就派上用場了。
如果你還不熟悉虛擬 DOM 和渲染函數的概念的話,請確保先閱讀 渲染機制 章節。
基本用法
創建 Vnodes
Vue 提供了一個
h()
函數用於創建 vnodes:
h()
是
hyperscript
的簡稱——意思是“能生成 HTML (超文本標記語言) 的 JavaScript”。這個名字來源於許多虛擬 DOM 實現默認形成的約定。一個更準確的名稱應該是
createVnode()
,但當你需要多次使用渲染函數時,一個簡短的名字會更省力。
h()
函數的使用方式非常的靈活:
得到的 vnode 為如下形式:
注意事項
完整的
VNode
接口包含其他內部屬性,但是強烈建議避免使用這些沒有在這裡列舉出的屬性。這樣能夠避免因內部屬性變更而導致的不兼容性問題。
聲明渲染函數
當組合式 API 與模板一起使用時,
setup()
鉤子的返回值是用於暴露數據給模板。然而當我們使用渲染函數時,可以直接把渲染函數返回:
在
setup()
內部聲明的渲染函數天生能夠訪問在同一範圍內聲明的 props 和許多響應式狀態。
除了返回一個 vnode,你還可以返回字符串或數組:
TIP
請確保返回的是一個函數而不是一個值!
setup()
函數在每個組件中只會被調用一次,而返回的渲染函數將會被調用多次。
如果一個渲染函數組件不需要任何實例狀態,為了簡潔起見,它們也可以直接被聲明為一個函數:
沒錯,這就是一個合法的 Vue 組件!參閱 函數式組件 來了解更多語法細節。
Vnodes 必須唯一
組件樹中的 vnodes 必須是唯一的。下面是錯誤示範:
如果你真的非常想在頁面上渲染多個重複的元素或者組件,你可以使用一個工廠函數來做這件事。例如下面的這個渲染函數就可以完美渲染出 20 個相同的段落:
JSX / TSX
JSX 是 JavaScript 的一個類似 XML 的擴展,有了它,我們可以用以下的方式來書寫代碼:
在 JSX 表達式中,使用大括號來嵌入動態值:
create-vue
和 Vue CLI 都有預置的 JSX 語法支持。如果你想手動配置 JSX,請參閱
@vue/babel-plugin-jsx
文檔獲取更多細節。
雖然最早是由 React 引入,但實際上 JSX 語法並沒有定義運行時語義,並且能被編譯成各種不同的輸出形式。如果你之前使用過 JSX 語法,那麼請注意 Vue 的 JSX 轉換方式與 React 中 JSX 的轉換方式不同 ,因此你不能在 Vue 應用中使用 React 的 JSX 轉換。與 React JSX 語法的一些明顯區別包括:
-
可以使用 HTML attributes 例如
class
和for
作為 props - 不需要使用className
或htmlFor
。 - 傳遞子元素給組件 (例如 slots) 的 方式不同 。
Vue 的類型定義也提供了 TSX 語法的類型推導支持。當使用 TSX 語法時,確保在
tsconfig.json
中配置了
"jsx": "preserve"
,這樣的 TypeScript 就能保證 Vue JSX 語法轉換過程中的完整性。
JSX 類型推斷
與轉換類似,Vue 的 JSX 也需要不同的類型定義。
從 Vue 3.4 開始,Vue 不再隱式註冊全局
JSX
命名空間。要指示 TypeScript 使用 Vue 的 JSX 類型定義,請確保在你的
tsconfig.json
中包含以下內容:
你也可以通過在文件的頂部加入
/* @jsxImportSource vue */
註釋來選擇性地開啟。
如果仍有代碼依賴於全局存在的
JSX
命名空間,你可以在項目中通過顯式導入或引用
vue/jsx
來保留 3.4 之前的全局行為,它註冊了全局
JSX
命名空間。
渲染函數案例
下面我們提供了幾個常見的用等價的渲染函數 / JSX 語法,實現模板功能的案例:
v-if
模板:
等價於使用如下渲染函數 / JSX 語法:
v-for
模板:
等價於使用如下渲染函數 / JSX 語法:
v-on
以
on
開頭,並跟著大寫字母的 props 會被當作事件監聽器。例如,
onClick
與模板中的
@click
等價。
事件修飾符
對於
.passive
、
.capture
和
.once
事件修飾符,可以使用駝峰命名法將他們拼接在事件名後面:
實例:
對於事件和按鍵修飾符,可以使用
withModifiers
函數:
組件
在給組件創建 vnode 時,傳遞給
h()
函數的第一個參數應是組件的定義。這意味著使用渲染函數時不再需要註冊組件了 —— 可以直接使用導入的組件:
不管是什麼類型的文件,只要從中導入的是有效的 Vue 組件,
h
就能正常運作。
動態組件在渲染函數中也可直接使用:
如果一個組件是用名字註冊的,不能直接導入 (例如,由一個庫全局註冊),可以使用
resolveComponent()
來解決這個問題。
渲染插槽
在渲染函數中,插槽可以通過
setup()
的上下文來訪問。每個
slots
對象中的插槽都是一個
返回 vnodes 數組的函數
:
等價 JSX 語法:
傳遞插槽
向組件傳遞子元素的方式與向元素傳遞子元素的方式有些許不同。我們需要傳遞一個插槽函數或者是一個包含插槽函數的對象而非是數組,插槽函數的返回值同一個正常的渲染函數的返回值一樣——並且在子組件中被訪問時總是會被轉化為一個 vnodes 數組。
等價 JSX 語法:
插槽以函數的形式傳遞使得它們可以被子組件懶調用。這能確保它被註冊為子組件的依賴關係,而不是父組件。這使得更新更加準確及有效。
作用域插槽
為了在父組件中渲染作用域插槽,需要給子組件傳遞一個插槽。注意該插槽現在擁有一個
text
參數。該插槽將在子組件中被調用,同時子組件中的數據將向上傳遞給父組件。
記得傳遞
null
以避免插槽被誤認為 prop:
等同於 JSX:
內置組件
諸如
<KeepAlive>
、
<Transition>
、
<TransitionGroup>
、
<Teleport>
和
<Suspense>
等
內置組件
在渲染函數中必須導入才能使用:
v-model
v-model
指令擴展為
modelValue
和
onUpdate:modelValue
在模板編譯過程中,我們必須自己提供這些 props:
自定義指令
可以使用
withDirectives
將自定義指令應用於 vnode:
當一個指令是以名稱註冊並且不能被直接導入時,可以使用
resolveDirective
函數來解決這個問題。
模板引用
在組合式 API 中,模板引用通過將
ref()
本身作為一個屬性傳遞給 vnode 來創建:
函數式組件
函數式組件是一種定義自身沒有任何狀態的組件的方式。它們很像純函數:接收 props,返回 vnodes。函數式組件在渲染過程中不會創建組件實例 (也就是說,沒有
this
),也不會觸發常規的組件生命週期鉤子。
我們用一個普通的函數而不是一個選項對象來創建函數式組件。該函數實際上就是該組件的渲染函數。
函數式組件的簽名與
setup()
鉤子相同:
大多數常規組件的配置選項在函數式組件中都不可用,除了
props
和
emits
。我們可以給函數式組件添加對應的屬性來聲明它們:
如果這個
props
選項沒有被定義,那麼被傳入函數的
props
對象就會像
attrs
一樣會包含所有屬性。除非指定了
props
選項,否則每個 prop 的名字將不會基於駝峰命名法被一般化處理。
對於有明確
props
的函數式組件,
屬性透傳
的原理與普通組件基本相同。然而,對於沒有明確指定
props
的函數式組件,只有
class
、
style
和
onXxx
事件監聽器將默認從
attrs
中繼承。在這兩種情況下,可以將
inheritAttrs
設置為
false
來禁用屬性繼承:
函數式組件可以像普通組件一樣被註冊和使用。如果你將一個函數作為第一個參數傳入
h
,它將會被當作一個函數式組件來對待。
為函數式組件標註類型
函數式組件可以根據它們是否有命名來標註類型。在單文件組件模板中, Vue - Official 擴展 還支持對正確類型化的函數式組件進行類型檢查。
具名函數式組件
匿名函數式組件