万字长文详细解析Vue 3 源码:从 createApp 到组件渲染
- Vue 3 源码解析:从 createApp 到组件渲染
- 1. Vue 3 应用初始化
- 2. 响应式系统原理
- 3. 虚拟 DOM 与渲染器
- 4. 组件渲染全流程
- 5. 模板编译过程
- 6. 总结与性能优化
Vue 3 源码解析:从 createApp 到组件渲染
1. Vue 3 应用初始化
1.1 createApp
入口函数
1.1.1 入口函数的作用
createApp
是 Vue 3 应用的起点,它的核心任务是:
- 创建应用实例:基于根组件和根属性生成一个 Vue 应用实例。
- 初始化平台相关渲染器:确保渲染逻辑与当前环境(浏览器、小程序等)适配。
- 重写
mount
方法:标准化挂载流程,处理容器元素和初始渲染。
1.1.2 源码实现解析
源码位于 runtime-dom/src/index.ts
(浏览器环境):
export const createApp = ((...args) => {
// 1. 获取渲染器(Renderer)
const renderer = ensureRenderer();
// 2. 调用渲染器的 createApp 方法创建应用实例
const app = renderer.createApp(...args);
// 3. 保存原始 mount 方法
const {
mount } = app;
// 4. 重写 mount 方法,处理容器标准化
app.mount = (containerOrSelector: Element | string): any => {
// 4.1 标准化容器
const container = normalizeContainer(containerOrSelector);
if (!container) return;
// 4.2 清空容器内容(安全措施)
container.innerHTML = '';
// 4.3 调用原始 mount 方法进行挂载
const proxy = mount(container, false, container instanceof SVGElement);
return proxy;
};
return app;
}) as CreateAppFunction<Element>;
关键代码分析:
-
ensureRenderer()
创建或复用浏览器环境的渲染器,内部调用createRenderer
:let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer; function ensureRenderer() { return renderer || (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions)); }
rendererOptions
包含浏览器特有的 DOM 操作方法(如insert
、remove
)。
-
renderer.createApp
调用runtime-core
中的createAppAPI
生成应用实例:// runtime-core/src/apiCreateApp.ts export function createAppAPI<HostElement>( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> { return function createApp(rootComponent, rootProps = null) { // 创建应用上下文和插件集合 const context = createAppContext(); const installedPlugins = new Set(); // 应用实例对象 const app: App = { _component: rootComponent, _props: rootProps, _container: null, use(plugin: Plugin, ...options: any[]) { /* 插件安装逻辑 */ }, component(name: string, component?: Component): any { /* 组件注册 */ }, mount(rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any { // 核心挂载逻辑 }, // 其他方法(directive, unmount, provide 等) }; return app; }; }
-
app.mount
的重写
原始mount
方法由createAppAPI
定义,但平台可能需要额外处理容器。重写后的逻辑包括:- 标准化容器:支持传入选择器字符串或 DOM 元素。
- 清空容器:避免遗留内容影响渲染。
- 调用原始
mount
:触发组件渲染。
1.2 应用实例的创建
1.2.1 应用实例的核心属性
应用实例(app
)是 Vue 3 全局配置的载体,包含以下关键属性:
_component
: 根组件对象(用户传入的App.vue
)。_props
: 根组件接收的初始属性。_context
: 应用上下文,提供全局配置(如组件、指令、混入等)。_container
: 挂载的 DOM 容器。
1.2.2 应用上下文的创建
createAppContext
定义应用全局状态:
// runtime-core/src/apiCreateApp.ts
export function createAppContext(): AppContext {
return {
app: null as any, // 由应用实例自身填充
config: {
isNativeTag: NO, // 默认非原生标签
performance: false,
globalProperties: {
},
optionMergeStrategies: {
},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: {
}
},
mixins: [],
components: {
},
directives: {
},
provides: Object.create(null)
};
}
config
: 全局配置,如自定义元素识别、性能监控开关。mixins
: 全局混入的选项。components
: 全局注册的组件(如app.component('MyComp', ...)
)。provides
: 依赖注入的全局数据。
1.2.3 应用实例的方法
1. use()
:插件安装
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`);
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin);
plugin.install(app, ...options);
} else if (isFunction(plugin)) {
installedPlugins.add(plugin);
plugin(app, ...options);
}
return app;
}
- 插件机制:通过
install
方法扩展应用功能(如 Vue Router、Vuex)。
2. component()
:全局组件注册
component(name: string, component?: Component): any {
if (__DEV__) validateComponentName(name, context.config);
if (!component) {
return context.components[name];
}
context.components[name] = component;
return app;
}
- 注册逻辑:将组件存入
context.components
,供模板解析使用。
3. mount()
:挂载根组件
原始 mount
方法的核心逻辑:
mount(rootContainer: HostElement, isHydrate?: boolean, isSVG?: boolean): any {
// 1. 创建根组件的虚拟节点(VNode)
const vnode = createVNode(rootComponent, rootProps);
// 2. 存储应用上下文
vnode.appContext = context;
// 3. 调用渲染器的 render 方法
render(vnode, rootContainer, isSVG);
// 4. 标记容器为已挂载
app._container = rootContainer;
return vnode.component!.proxy;
}
1.3 全局 API 的实现
1.3.1 全局 API 的设计变化
Vue 3 废弃了 Vue.extend
、Vue.mixin
等全局 API,改为通过应用实例配置,避免全局污染。
对比 Vue 2 的全局配置:
// Vue 2 的全局行为(影响所有实例)
Vue.component('my-component', {
/* ... */ });
Vue.mixin({
/* ... */ });
Vue 3 改为:
const app = createApp(App);
app.component('MyComponent', {
/* ... */ });
app.mixin({
/* ... */ });
1.3.2 全局属性注入
通过 app.config.globalProperties
添加全局属性:
// 用户代码
app.config.globalProperties.$http = axios;
// 组件内访问
this.$http.get('/api');
实现原理:在组件实例化时,将 globalProperties
合并到组件实例的上下文。
1.3.3 自定义元素与白名单
通过 app.config.isNativeTag
配置自定义元素:
// 告诉 Vue 将 <my-element> 视为原生元素
app.config.isNativeTag = (tag) => tag === 'my-element';
源码中的处理:
在模板编译阶段,检查元素是否为原生标签或已注册组件。
1.4 挂载流程的完整解析
1.4.1 mount
方法的执行步骤
-
创建根组件的 VNode
const vnode = createVNode(rootComponent, rootProps);
createVNode
是虚拟 DOM 的构造函数,标记节点类型、属子级等。
-
关联应用上下文
vnode.appContext = context;
- 所有子组件可通过
vnode.appContext
访问全局配置。
- 所有子组件可通过
-
调用渲染器进行渲染
render(vnode, rootContainer, isSVG);
render
函数位于runtime-core/src/renderer.ts
,核心逻辑为patch
算法。
-
返回组件代理对象
return vnode.component!.proxy;
proxy
是组件实例的公共接口,暴露属性方法供用户访问。
1.4.2 虚拟 DOM 的创建
createVNode
的简化实现:
// runtime-core/src/vnode.ts
export function createVNode(
type: VNodeTypes,
props: {
[key: string]: any } | null = null,
children: unknown = null
): VNode {
const vnode: VNode = {
__v_isVNode: true,
type,
props,
key: props?.key,
ref: props?.ref,
// 其他属性(el, component, shapeFlag 等)
};
normalizeChildren(vnode, children); // 标准化子节点
return vnode;
}
shapeFlag
:标记节点类型(如元素、组件、插槽),优化后续patch
过程。
1.4.3 渲染器的工作流程
渲染器的核心方法是 patch
,负责将 VNode 转换为真实 DOM:
// runtime-core/src/renderer.ts
const patch = (
n1: VNode | null, // 旧节点
n2: VNode, // 新节点
container: HostElement,
anchor: HostNode | null = null
) => {
if (n1 === n2) return;
const {
type, shapeFlag } = n2;
// 根据节点类型调用不同的处理函数
switch (type) {
case Text:
processText(n1, n2, container, anchor);
break;
case Comment:
processCommentNode(n1, n2, container, anchor);
break;
case Static:
// 处理静态节点...
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2