整體流程
完整的創建與渲染流程可以分成這些階段:
- 創建 App 實例
- 創建根組件實例
- 設置響應式狀態
- 創建渲染器(Renderer)
- 掛載 Mount
- vnode -> DOM 渲染
- 數據變更觸發更新
- 重新渲染 / diff / patch
流程圖大致如下:
createApp() ───> app.mount('#app')
│ │
▼ ▼
createRootComponent createRenderer
│ │
▼ ▼
setup() / render() render(vnode) -> patch
│ │
▼ ▼
effect(fn) ────> scheduler -> patch updates
1、createApp 初始化
Vue 應用的入口通常是:
createApp(App).mount('#app')
從源碼看 createApp:
// packages/runtime-core/src/apiCreateApp.ts
export function createAppAPI(render) {
return function createApp(rootComponent, rootProps = null) {
const app = {
_component: rootComponent,
_props: rootProps,
_container: null,
_context: createAppContext()
}
const proxy = (app._instance = {
app
})
// register global APIs
// ...
return {
mount(container) {
const vnode = createVNode(rootComponent, rootProps)
app._container = container
render(vnode, container)
},
unmount() { /* ... */ }
}
}
}
關鍵點:
createAppAPI(render)生成 createApp 函數- app 內保存
_component、上下文_context app.mount調用render(vnode, container)
render 由 平台渲染器 注入(在 web 下是 DOM 渲染器)。
2、createVNode 創建虛擬節點(VNode)
在 mount 前會創建一個虛擬節點:
function createVNode(type, props, children) {
const vnode = {
type,
props,
children,
shapeFlag: getShapeFlag(type),
el: null,
key: props && props.key
}
return vnode
}
vnode 是渲染的基礎單元:
shapeFlag 用來快速判斷 vnode 類型,是內部性能優化。
3、渲染器 Renderer 初始化
Vue3 是平台無關的(runtime-core),真正依賴 DOM 的是在 runtime-dom 中。
創建 Renderer:
export const renderer = createRenderer({
createElement: hostCreateElement,
patchProp: hostPatchProp,
insert: hostInsert,
remove: hostRemove,
setElementText: hostSetElementText
})
createRenderer 返回了我們前面在 createApp 中使用的 render(vnode, container) 函數。
4、render & patch
核心渲染入口:
function render(vnode, container) {
patch(null, vnode, container, null, null)
}
patch 是渲染補丁函數:
function patch(n1, n2, container, parentComponent, anchor) {
const { type, shapeFlag } = n2
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement()
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(...)
}
}
簡化為:
- 如果是 DOM 元素 vnode → 掛載/更新
- 如果是 組件 vnode → 創建組件實例、掛載、渲染子樹
5、組件實例創建
當渲染組件時:
function processComponent(n1, n2, container, parentComponent, anchor) {
mountComponent(n2, container, parentComponent, anchor)
}
function mountComponent(vnode, container, parentComponent, anchor) {
const instance = createComponentInstance(vnode, parentComponent)
setupComponent(instance)
setupRenderEffect(instance, container, anchor)
}
- processComponent 處理組件
- mountComponent 掛載組件
- createComponentInstance 創建組件實例
- setupComponent 創建組件對象
createComponentInstance:
function createComponentInstance(vnode, parent) {
const instance = {
vnode,
parent,
proxy: null,
ctx: {},
props: {},
attrs: {},
slots: {},
setupState: {},
isMounted: false,
subTree: null
}
return instance
}
實例保存基礎信息,還沒運行 setup。
6、 setupComponent(初始化組件)
function setupComponent(instance) {
initProps(instance, vnode.props)
initSlots(instance, vnode.children)
setupStatefulComponent(instance)
}
內部會執行:
const { setup } = Component
if (setup) {
const setupResult = setup(props, ctx)
handleSetupResult(instance, setupResult)
}
setup 返回值:
- 返回對象 → 作為響應式狀態 state
- 返回函數 → render 函數
最終讓組件擁有 instance.render。
7、創建響應式狀態
Vue3 的響應式來自 reactivity 包:
const state = reactive({ count: 0 })
底層是 Proxy 攔截 getter/setter:
- getter:收集依賴
- setter:觸發依賴更新
依賴管理核心是 effect / track / trigger。
8、 setupRenderEffect 與首次渲染
創建渲染器副作用,並調度組件掛載和異步更新:
function setupRenderEffect(instance, container, anchor) {
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
const subTree = (instance.subTree = instance.render.call(proxy))
patch(null, subTree, container, instance, anchor)
instance.isMounted = true
} else {
// 更新更新邏輯
}
}, {
scheduler: queueJob
})
}
這裏:
- 創建一個 響應式 effect
- 第一次執行 render 得到 subTree
- patch 子樹到 DOM
effect+scheduler實現異步更新。
9、vnode-> 真實 DOM(DOM mount)
當 patch 到真正的 DOM 時,走的是 element 分支:
function processElement(...) {
if (!n1) {
mountElement(vnode, container)
} else {
patchElement(n1, n2)
}
}
mountElement
function mountElement(vnode, container) {
const el = (vnode.el = hostCreateElement(vnode.type))
// props
for (key in props) {
hostPatchProp(el, key, null, props[key])
}
// children
if (typeof children === 'string') {
hostSetElementText(el, children)
} else {
children.forEach(c => patch(null, c, el))
}
hostInsert(el, container)
}
10、更新 & Diff 算法
當響應式狀態改變:
state.count++
觸發 setter → trigger:
- 將 effect 放入更新隊列
- 異步執行 scheduler
- 調用 instance.update 再次 patch
更新階段:
patchElement(n1, n2)
核心邏輯:
- props diff
- children diff
- unkeyed/keyed diff 算法(最小化移動)
具體見
patchChildren和patchKeyedChildren。
整體核心對象關係架構
App
└─ vnode(root)
└─ ComponentInstance
├─ props / slots
├─ setupState
└─ render() -> subTree
└─ vnode tree
└─ DOM nodes
響應式依賴結構:
reactive state
├─ effects[]
└─ track -> effect
└─ scheduler -> patch