
阶段 1:创建应用实例
js
// 用户入口代码
const app = createApp(App);
app.mount('#app');
- createApp调用
- 调用createApp创建应用实例,内部调用
ensureRenderer().createApp(...)
。 - ensureRenderer :创建或复用全局渲染器(包含核心的
patch
、nodeOps
等方法)。 - createAppAPI :生成应用实例对象,暴露
mount
、use
等方法。
- 调用createApp创建应用实例,内部调用
阶段 2:应用实例初始化
js
// 内部执行 mount('#app')
mount(rootContainer) {
// 步骤 1: 创建根组件 VNode
const vnode = createVNode(rootComponent, rootProps);
// 步骤 2: 调用核心渲染逻辑
render(vnode, rootContainer);
}
创建根组件 VNode:
- createVNode(rootComponent, rootProps)生成根组件的虚拟节点(VNode)。
- VNode 包含组件配置(
type
)、props、children 等信息。
阶段 3:核心渲染逻辑(render
函数)
js
// renderer.ts 中的 render 函数
function render(vnode, container) {
if (vnode == null) {
// 卸载逻辑 if (container._vnode) unmount(container._vnode);
} else {
// 挂载或更新逻辑
patch(container._vnode || null, vnode, container);
}
// 缓存当前
VNode container._vnode = vnode;
}
- 首次挂载 :
container._vnode
为null
,直接调用patch(null, vnode, container)
。 - patch 函数 :根据新旧 VNode 的差异进行 DOM 更新(首次挂载时旧 VNode 为
null
)。
阶段 4:patch
过程(首次挂载)
js
// patch 函数的分发逻辑
const patch: PatchFn = (n1, n2, container, ...args) => {
const { type } = n2;
if (type === Fragment) {
// 处理 Fragment
} else if (type === Text) {
// 处理文本节点
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 处理组件
processComponent(n1, n2, container, ...args);
} else if (shapeFlag & ShapeFlags.ELEMENT) {
// 处理普通元素
processElement(n1, n2, container, ...args);
}
};
4.1 处理根组件(processComponent
)
js
const processComponent = (n1, n2, container) => {
if (!n1) {
// 首次挂载 mountComponent(n2, container);
} else {
// 更新逻辑 updateComponent(n1, n2);
}
};
阶段 5:挂载组件(mountComponent
)
js
// mountComponent 核心逻辑
function mountComponent(initialVNode, container) {
// 步骤 1: 创建组件实例
const instance = createComponentInstance(initialVNode);
// 步骤 2: 初始化组件实例
setupComponent(instance);
// 步骤 3: 设置渲染副作用(响应式依赖收集)
setupRenderEffect(instance, initialVNode, container);
}
5.1 创建组件实例(createComponentInstance
)
-
生成组件实例对象,包含以下关键属性:
jsconst instance = { type: vnode.type, // 组件配置对象 vnode: initialVNode, // 当前 VNode props: {}, // 初始化 props attrs: {}, // 非 props 的 attributes slots: {}, // 插槽 setupState: {}, // setup() 返回的状态 ctx: {}, // 渲染上下文(代理访问 setupState、props 等) render: null, // 渲染函数 isMounted: false, // 是否已挂载 subTree: null, // 组件渲染的子树(VNode 树) };
5.2 初始化组件实例(setupComponent
)
js
// setupComponent 核心逻辑
export function setupComponent(instance) {
// 步骤 1: 初始化 props 和 slots
initProps(instance, instance.vnode.props);
initSlots(instance, instance.vnode.children);
// 步骤 2: 执行有状态的 setup 函数
const setupResult = instance.type.setup?.(
instance.props,
{ attrs: instance.attrs, slots: instance.slots, emit: instance.emit }
);
// 步骤 3: 处理 setup 返回值
if (isFunction(setupResult)) {
// 返回渲染函数
instance.render = setupResult;
} else if (isObject(setupResult)) {
// 将状态转换为响应式并挂载到实例
instance.setupState = proxyRefs(setupResult);
}
// 步骤 4: 确定最终的 render 函数
if (!instance.render) {
instance.render = instance.type.render || NOOP;
}
}
5.3 设置渲染副作用(setupRenderEffect
)
js
// setupRenderEffect 核心逻辑
function setupRenderEffect(instance, initialVNode, container) {
// 创建响应式副作用(effect)
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 首次挂载
// 步骤 1: 调用 beforeMount 生命周期钩子
if (instance.type.beforeMount) {
instance.type.beforeMount.call(instance.proxy);
}
// 步骤 2: 生成子树(subTree)
const subTree = (instance.subTree = renderComponentRoot(instance));
// 步骤 3: 递归 patch 子树到 DOM
patch(null, subTree, container);
// 步骤 4: 标记已挂载
initialVNode.el = subTree.el;
instance.isMounted = true;
// 步骤 5: 调用 mounted 生命周期钩子
if (instance.type.mounted) {
instance.type.mounted.call(instance.proxy);
}
} else {
// 更新逻辑(略)
}
}, { scheduler: queueJob });
}
阶段 6:递归处理子树(renderComponentRoot
)
js
// 执行组件的 render 函数生成 subTree
function renderComponentRoot(instance) {
const { render, proxy } = instance;
let result;
try {
// 调用 render 函数,生成 VNode 树
result = normalizeVNode(render.call(proxy));
} catch (err) {
// 错误处理
}
return result;
}
- render函数执行 :
- 若组件使用模板,
render
函数由编译器生成(包含_createBlock
等优化)。 - 若组件手写
render
函数,直接返回 VNode。
- 若组件使用模板,
阶段 7:处理元素(processElement
)
js
// 处理普通元素(如 div、span)
const processElement = (n1, n2, container) => {
if (!n1) {
// 首次挂载元素
mountElement(n2, container);
} else {
// 更新逻辑(略)
}
};
// mountElement 核心逻辑
function mountElement(vnode, container) {
// 步骤 1: 创建 DOM 元素
const el = (vnode.el = hostCreateElement(vnode.type));
// 步骤 2: 处理子节点(递归或文本)
if (vnode.children) {
if (typeof vnode.children === 'string') {
// 文本子节点
hostSetElementText(el, vnode.children);
} else {
// 子节点为 VNode 数组,递归 patch
vnode.children.forEach(child => {
patch(null, child, el);
});
}
}
// 步骤 3: 处理 props(包括事件、属性、类、样式等)
if (vnode.props) {
for (const key in vnode.props) {
hostPatchProp(el, key, null, vnode.props[key]);
}
}
// 步骤 4: 插入到父容器
hostInsert(el, container);
}
阶段 8:处理动态内容(Block Tree 优化)
-
createBlock函数:
- 在编译阶段,模板中的动态节点(如
v-if
、v-for
)会被包裹在createBlock
中。 Block
节点会通过dynamicChildren
数组收集所有动态子节点。
lesstypescriptCopy Code // 编译后的 render 函数示例 function render() { return (_openBlock(), _createBlock("div", null, [ _createVNode("span", null, "Static"), _createVNode("span", { class: _ctx.dynamicClass }, _toDisplayString(_ctx.dynamicText), 1 /* TEXT */) ])) }
- 在编译阶段,模板中的动态节点(如
-
靶向更新:
- 在
patch
过程中,Block
节点的dynamicChildren
会被直接遍历,仅处理动态节点。 - 通过
patchFlags
标记动态类型(如TEXT
、CLASS
),跳过无关的 Diff 逻辑。
- 在
阶段 9:DOM 插入与完成挂载
- hostInsert方法 :
- 将创建好的 DOM 元素插入到父容器(如
#app
)。 - 最终,根组件的所有子节点递归挂载完毕,页面完成渲染。
- 将创建好的 DOM 元素插入到父容器(如