整体流程
完整的创建与渲染流程可以分成这些阶段:
- 创建 App 实例
- 创建根组件实例
- 设置响应式状态
- 创建渲染器(Renderer)
- 挂载 Mount
- vnode -> DOM 渲染
- 数据变更触发更新
- 重新渲染 / diff / patch
流程图大致如下:
createApp() ───> app.mount('#app') │ │ ▼ ▼ createRootComponent createRenderer │ │ ▼ ▼ setup() / render() render(vnode) -> patch │ │ ▼ ▼ effect(fn) ────> scheduler -> patch updates1、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