[vue3] vue3 setup函數
從語法上看,Composition API 提供了一個 setup 啟動函數作為邏輯組織的入口,提供了響應式 API,提供了生命周期函數以及依賴注入的接口,通過調用函數來聲明一個組件。
Options API
- 選項式 API 在 props、data、methods、computed 等選項中定義變量;
- 在組件初始化階段,Vue.js 內部處理這些 options,把定義的變量添加到組件實例上;
- 等模板編譯成 render 函數的時候,內部通過
with(this){}的語法去訪問在組件實例中的變量。
在 Vue3 中,這兩種 API 能夠同時使用,但執行的優先級不同,建議只使用其中一種。
- Options API 適合小型簡單的組件;
- Composition API 適合大型復雜、需要拆分邏輯的組件。
組件初始化
在 Vue3 中,render函數可以訪問到 setup 函數返回的數據,這是怎么實現的呢?
組件的渲染流程是:創建vnode、渲染vnode、生成DOM。
其中渲染vnode就是在掛載(或更新)組件,通過 patch 函數對不同類型的 vnode 進行掛載或更新。
setup 函數只在首次掛載的流程中調用,因此這里主要研究掛載的流程。
通過 patch 函數內部的調用鏈通過 mountComponent 函數進行組件掛載。
mountComponent
const mountComponent = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
namespace: ElementNamespace,
optimized,
) => {
// 創建組件實例
const instance: ComponentInternalInstance =
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense,
))
// 設置組件實例:props, slots ...
setupComponent(instance)
// 設置并運行帶副作用的渲染函數
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
namespace,
optimized,
)
}
mountComponent 函數內部主要執行了三個函數:
- createComponentInstance:【工廠模式】內部通過對象字面量創建一個組件實例對象并返回,對象包含許多屬性,例如:
- effect,update,job:與副作用和更新邏輯相關;
- components、directives:局部組件與局部指令;
- ctx、data、props:state相關;
- bc、c、bm、m、bu:聲明周期相關,源代碼中用首字母命名,bc 是 beforeCreate;
- setupComponent:
- 判斷組件是否有狀態;
- 初始化props
- 初始化slots
- 如果有狀態,則調用 setupStatefulComponent 進行組件實例設置,內部調用了 setup 函數。
- setupRenderEffect:創建一個與更新相關的副作用,再包裝成 Job 對象。這個副作用內部會調用生命周期 hook 。
這篇文章主要介紹setup,下面主要內容是執行了setup函數的 setupStatefulComponent 函數。
setupStatefulComponent
創建代理
這個函數創建了渲染上下文代理,創建代理的目的是讓訪問數據更加簡便。
例如在 Vue2 中 props 的數據實際存儲在 this._props 上,而 data 的數據則存儲在 this._data 上,在組件方法中可以通過 this.msg 訪問到 this._data.msg ,就是因為使用了代理。
而在 Vue3 中,不同的狀態被存儲在了組件實例的 setupState、ctx、data、props中。通過創建一個代理,渲染函數可以在一個對象上進行讀寫操作,再由代理將讀寫操作分發給不同的狀態對象。
// 0. create render proxy property access cache
instance.accessCache = Object.create(null)
// 1. create public instance / render proxy
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
函數內部還創建了一個 accessCache 對象,它是一個 key 到 state類型 的映射,避免每次讀取一個 key 都要去判斷這個 key 屬于哪一種狀態。
PublicInstanceProxyHandlers 內部關于 accessCache 的快速命中代碼:
const n = accessCache![key]
if (n !== undefined) {
switch (n) {
case AccessTypes.SETUP:
return setupState[key]
case AccessTypes.DATA:
return data[key]
case AccessTypes.CONTEXT:
return ctx[key]
case AccessTypes.PROPS:
return props![key]
// default: just fallthrough
}
}
如果沒能命中緩存,內部只能通過 hasOwn 方法一個一個去判斷狀態對象上是否存在查詢的 key。hasOwn 是這一過程中的開銷大頭。
判斷的順序很重要,當不同的狀態對象有著相同名稱的屬性,那么優先應用那個先判斷的類型。
get攔截(在沒有命中緩存的情況下)的判斷順序是 setup、data、props、context。
這意味著當我們混用 setup函數 和 選項式 API 時,同名響應式變量會命中 setup 中聲明的變量。
下面的代碼會在界面上顯示“from setup”。
<template>
<p>{{ msg }}</p>
</template>
<script>
import { ref } from 'vue';
export default{
data() {
return {
msg: 'from data'
}
},
setup(){
const msg = ref('from setup');
return {
msg
}
}
}
</script>
上面說的都是對get的攔截,對于set的攔截簡要介紹如下:
- key 的判斷順序和 get 一樣;
- 在開發環境中對props的修改操作進行警告。
調用setup
setupStatefulComponent 函數在創建了這個上下文代理之后,就調用了 setup 函數。
大致流程如下:
-
判斷是否有 setup 函數;
-
如果setup函數參數列表長度大于1,則調用 createSetupContext 函數創建 setup 上下文對象;
setup函數的參數如下,第二個參數是一個上下文對象。如果用戶(前端程序員)編寫setup的時候使用了第二個參數,那么Vue在執行setup函數之前就要把這個上下文對象準備好。
setup(props, { attrs, slots, emit, expose }) { ... } -
執行 setup 函數獲取返回的結果;
執行 setup 函數是通過 callWithErrorHandling 間接調用的。其實內部不復雜,有參數則攜帶參數執行,用 try/catch 捕獲錯誤。
export function callWithErrorHandling( fn: Function, instance: ComponentInternalInstance | null | undefined, type: ErrorTypes, args?: unknown[], ): any { try { return args ? fn(...args) : fn() } catch (err) { handleError(err, instance, type) } } -
處理 setup 返回的結果。
setup 函數的返回值有兩種類型:數據對象 和 渲染函數。
-
如果是渲染函數,則綁定到 instance.render 上;
instance.render = setupResult as InternalRenderFunction -
如果是數據對象,則先進行響應式包裝,再綁定到 instance.setupState 上。
instance.setupState = proxyRefs(setupResult)
-
至此,setup函數執行完成了,相關的數據也都綁定到了組件實例上。

浙公網安備 33010602011771號