我们一般创建一个vue3项目会从createApp或createSSRApp开始,createSSRApp是服务端渲染的入口,createApp是客户端渲染的入口。
1.createApp 、 createSSRApp 初始化App实例
createApp
export const createApp = ((...args) => {
// 创建基础应用实例(来自 runtime-core)
const app = ensureRenderer().createApp(...args)
// 开发环境注入校验逻辑
if (__DEV__) {
injectNativeTagCheck(app) // ← 校验原生 HTML/SVG 标签
injectCompilerOptionsCheck(app) // ← 检查编译器选项有效性
}
// 重写 mount 方法(DOM 平台特有逻辑)
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// 标准化容器(支持选择器字符串)
const container = normalizeContainer(containerOrSelector)
if (!container) return
// 处理无 render/template 的组件
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
// 使用容器内 HTML 作为模板(安全警告)
component.template = container.innerHTML // ← __UNSAFE__ 标记
// Vue 2.x 兼容性检查(v- 指令在容器上的使用)
if (__COMPAT__ && __DEV__) {
// 遍历容器属性检查 Vue 指令
for (let i = 0; i < container.attributes.length; i++) {
const attr = container.attributes[i]
if (/^(v-|:|@)/.test(attr.name)) {
compatUtils.warnDeprecation(...) // ← 兼容性警告
}
}
}
}
// 清空容器内容
container.innerHTML = ''
// 调用原始 mount(来自 runtime-core)
const proxy = mount(container, false, resolveRootNamespace(container))
// DOM 特定标记处理
if (container instanceof Element) {
container.removeAttribute('v-cloak') // ← 移除加载占位符
container.setAttribute('data-v-app', '') // ← 应用挂载标识
}
return proxy
}
return app
}) as CreateAppFunction<Element>
createSSRApp 与普通createApp的区别 :
| 功能模块 | createApp | createSSRApp |
|---|---|---|
| 渲染器类型 | 客户端渲染 | hydration渲染器 |
| mount参数 | (container, false) | (container, true) |
| 初始HTML处理 | 清空容器内容 | 保留服务端渲染的HTML |
| 指令初始化 | 普通指令 | SSR专用指令初始化 |
export const createSSRApp = ((...args) => {
// 使用hydration渲染器创建应用实例
const app = ensureHydrationRenderer().createApp(...args)
// 开发环境校验逻辑
if (__DEV__) {
injectNativeTagCheck(app) // ← 校验原生标签命名规范
injectCompilerOptionsCheck(app) // ← 检查编译器配置有效性
}
// 重写mount方法(SSR专用逻辑)
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// 标准化容器处理
const container = normalizeContainer(containerOrSelector)
if (container) {
// 调用核心mount方法,第二个参数true表示hydrate模式
return mount(container, true, resolveRootNamespace(container))
}
}
return app
}) as CreateAppFunction<Element>
1.ensureRenderer
其中ensureRenderer集成的是baseCreateRenderer方法。baseCreateRenderer 函数作为Vue的渲染核心,处理虚拟DOM到真实DOM的转换和更新,支持多种节点类型和优化策略
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions,
): any {
...
//从options中解构出平台相关的DOM操作方法,这使得渲染器可以跨平台使用。
// 具体方法定义在nodeOps文件中
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
// ...其他方法
} = options
//这是虚拟DOM对比的核心算法,处理不同类型的节点更新逻辑。
const patch: PatchFn = (n1, n2, container, anchor = null, ...) => {
// 对比算法入口
if (n1 && !isSameVNodeType(n1, n2)) {
// 卸载旧节点
unmount(n1, ...)
}
switch(n2.type) {
case Text:
processText(...)
case Comment:
processCommentNode(...)
// 其他节点类型处理
}
}
...
//完整的元素挂载和更新逻辑,包含DOM操作和属性处理
const processElement = (...) => {
if (n1 == null) {
mountElement(...) // 首次挂载
} else {
patchElement(...) // 更新
}
}
const mountElement = (...) => {
// 创建DOM元素
el = hostCreateElement(...)
// 处理children
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(...)
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(...) // 递归处理子节点
}
// 处理props/指令/事件等
if (props) {
for (const key in props) {
hostPatchProp(el, key, ...)
}
}
}
//处理组件的挂载和更新,包含组件实例的创建、生命周期调用等
const processComponent = (...) => {
if (n1 == null) {
mountComponent(...) // 挂载新组件
} else {
updateComponent(...) // 更新组件
}
}
...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate),
}
}
2.createAppAPI
createAppAPI是一个高阶函数,接收render和可选的hydrate函数作为参数,返回一个createApp函数
这个函数作为Vue应用构造的核心,实现了应用实例的创建、全局配置管理、插件系统、组件挂载等基础能力。
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction,
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
// 参数验证逻辑
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)
}
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
// 创建应用上下文
const context = createAppContext()
const installedPlugins = new WeakSet()
let isMounted = false
// 核心app实例对象
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
// 配置访问器
get config() {
return context.config
},
set config(v) {
__DEV__ && warn(`app.config cannot be replaced.`)
},
// 插件安装方法
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied.`)
} 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
},
// 全局混入
mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
context.mixins.includes(mixin) || context.mixins.push(mixin)
}
return app
},
// 组件注册
component(name: string, component?: Component): any {
if (__DEV__) validateComponentName(name, context.config)
if (!component) return context.components[name]
context.components[name] = component
return app
},
// 指令注册
directive(name: string, directive?: Directive) {
if (__DEV__) validateDirectiveName(name)
if (!directive) return context.directives[name]
context.directives[name] = directive
return app
},
// 挂载核心逻辑
mount(
rootContainer: HostElement,
isHydrate?: boolean,
namespace?: boolean | ElementNamespace,
): any {
if (!isMounted) {
const vnode = createVNode(rootComponent, rootProps)
vnode.appContext = context
// 开发环境HMR支持
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, namespace)
}
}
// 执行实际渲染或hydration
if (isHydrate && hydrate) {
hydrate(vnode, rootContainer)
} else {
render(vnode, rootContainer, namespace)
}
isMounted = true
app._container = rootContainer
return vnode.component!.proxy
}
},
// 卸载方法
unmount() {
if (isMounted) {
render(null, app._container)
delete app._container.__vue_app__
}
},
// 依赖注入
provide(key, value) {
context.provides[key] = value
return app
},
// 上下文保持
runWithContext(fn) {
const lastApp = currentApp
currentApp = app
try {
return fn()
} finally {
currentApp = lastApp
}
}
})
return app
}
}
3.流程图
graph TD
subgraph createApp[createApp 客户端渲染]
A1[初始化普通渲染器] --> B1[创建应用实例]
B1 --> C1[开发环境校验]
C1 --> D1[重写mount方法]
D1 --> E1[清空容器内容]
E1 --> F1[执行DOM渲染]
end
subgraph createSSRApp[createSSRApp 服务端渲染激活]
A2[初始化hydration渲染器] --> B2[创建应用实例]
B2 --> C2[开发环境校验]
C2 --> D2[重写mount方法]
D2 --> E2[保留SSR生成的HTML]
E2 --> F2[执行hydration激活]
end
style createApp fill:#f9f9f9,stroke:#4a90e2
style createSSRApp fill:#f9f9f9,stroke:#50b83c
2.mount
一般createApp完成后,我们会紧接着app.mount('#app')
const app = createApp(App);
// 全局方法挂载
app.config.globalProperties.useDict = useDict;
app.use(router);
app.use(store);
app.use(i18n);
app.use(plugins);
// 自定义指令
directive(app);
app.mount('#app');
在createApp中我们知道,会重写 mount 方法,我们就深入看下,mount里到底实现了什么
mount(
rootContainer: HostElement,
isHydrate?: boolean,
namespace?: boolean | ElementNamespace,
): any {
// 挂载状态检查(单例模式)
if (!isMounted) {
// 开发环境容器冲突检查(#5571问题修复)
if (__DEV__ && (rootContainer as any).__vue_app__) {
warn(`已有应用实例挂载到该容器`)
}
// 创建根组件虚拟节点
const vnode = createVNode(rootComponent, rootProps)
vnode.appContext = context // 注入应用上下文
// 命名空间处理(SVG/MathML支持)
if (namespace === true) namespace = 'svg'
else if (namespace === false) namespace = undefined
// 开发环境HMR支持
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, namespace)
}
}
// 核心渲染逻辑
if (isHydrate && hydrate) {
//SSR渲染
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
//普通渲染
render(vnode, rootContainer, namespace)
}
// 更新挂载状态
isMounted = true
app._container = rootContainer
// 容器标记(用于后续检测)
;(rootContainer as any).__vue_app__ = app
// 开发工具集成
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}
// 返回组件代理对象
return getExposeProxy(vnode.component!) || vnode.component!.proxy
} else if (__DEV__) {
warn(`应用已挂载,需创建新实例`)
}
}
可以看出该方法实现了Vue应用实例挂载的核心流程,整合了虚拟DOM创建、渲染模式选择、开发环境支持等关键功能,是连接组件系统与渲染器的核心桥梁。
createVNode创建完成后会根据是否服务端渲染来进入render还是hydrate函数
下面我们着重分析各主要函数
1.createVNode
_createVNode是Vue中用于创建虚拟节点(VNode)的核心函数
主要负责包括参数处理、类型检查、兼容性处理、属性和样式的规范化,以及最终的通过调用createBaseVNode进行VNode创建过程
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false,
): VNode {
// 核心处理流程分为5个阶段:
// 1. 参数校验与默认值处理
if (!type || type === NULL_DYNAMIC_COMPONENT) {
if (__DEV__ && !type) {
warn(`无效的vnode类型: ${type}`)
}
type = Comment // 降级为注释节点
}
// 2. 克隆现有VNode逻辑(处理<component :is="vnode"/>场景)
if (isVNode(type)) {
const cloned = cloneVNode(type, props, true) // 合并refs
// ...子节点处理与块追踪逻辑...
return cloned
}
// 3. 兼容性处理(Vue 2.x特性支持)
if (isClassComponent(type)) {
type = type.__vccOpts // 类组件选项标准化
}
// 当启用Vue 2兼容模式时
if (__COMPAT__) {
type = convertLegacyComponent(type, currentRenderingInstance) // 旧版组件转换
}
// 4. 属性与样式规范化
if (props) {
// 处理响应式属性副本
props = guardReactiveProps(props)!
// class标准化(支持数组/对象语法)
if (props.class && !isString(props.class)) {
props.class = normalizeClass(props.class)
}
// style标准化(处理响应式样式对象)
if (isObject(props.style)) {
props.style = normalizeStyle(props.style)
}
}
// 5. 类型标志计算(使用位运算优化性能)
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
// 开发环境响应式组件警告
if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
warn(`组件被意外响应式化,应使用markRaw标记`)
}
// 最终创建基础VNode
return createBaseVNode(type, props, children, patchFlag, dynamicProps, shapeFlag, isBlockNode, true)
}
VNode多类型支持,使用 shapeFlag 位掩码快速判断节点类型
type VNodeTypes =
| string // HTML标签
| Component // 组件对象
| typeof Text // 文本节点
| typeof Fragment // Fragment片段
| typeof Teleport // Teleport组件
| typeof Suspense // Suspense组件
1.cloneVNode
这个函数的主要职责是克隆VNode,合并额外的属性,处理ref和过渡效果
-
ref合并机制 通过数组结构合并多个ref引用,解决 < component :is > 场景下多个ref指向同一节点的问题。当 mergeRef=true 时会将新旧ref合并为数组
-
patchFlag优化策略 克隆时自动添加 FULL_PROPS 标志(Fragment除外),告知diff算法需要完整检查props变更。原始 HOISTED 节点升级为全属性检查
-
作用域ID保留 scopeId 和 slotScopeIds 继承原始节点,确保克隆后的样式隔离和插槽作用域与原始节点一致
-
动态内容处理 对 dynamicChildren 直接继承而非克隆,保持与原始block tree的关联,确保动态节点追踪的准确性
我们可以通过克隆函数去了解Vnode节点个属性的含义
export function cloneVNode<T, U>(
vnode: VNode<T, U>,
extraProps?: (Data & VNodeProps) | null,
mergeRef = false,
cloneTransition = false,
): VNode<T, U> {
const { props, ref, patchFlag, children, transition } = vnode
// 核心克隆逻辑分为5个阶段:
// 1. 属性合并:安全合并原有props和额外props
const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
const cloned: VNode<T, U> = {
__v_isVNode: true, // 虚拟节点标识,用于快速判断对象类型
__v_skip: true, // 跳过响应式代理标记(VNode不需要响应式转换)
type: vnode.type, // 原始节点类型(组件/HTML标签/Symbol类型)
props: mergedProps, // 合并后的属性对象(包含原始props和额外添加的props)
key: mergedProps && normalizeKey(mergedProps), // 标准化的key值(用于diff算法优化)
// 引用处理,处理嵌套ref合并场景
ref: extraProps && extraProps.ref
? mergeRef && ref
? isArray(ref)
? ref.concat(normalizeRef(extraProps)!)
: [ref, normalizeRef(extraProps)!]
: normalizeRef(extraProps)
: ref,
scopeId: vnode.scopeId, // 作用域ID(用于SFC样式隔离)
slotScopeIds: vnode.slotScopeIds, // 插槽作用域ID(支持scoped slots)
// 子节点克隆逻辑(开发环境深度克隆HOISTED节点)
children: __DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
? (children as VNode[]).map(deepCloneVNode)
: children,
target: vnode.target, // Teleport目标容器(仅Teleport类型节点有效)
targetAnchor: vnode.targetAnchor, // Teleport锚点位置
staticCount: vnode.staticCount, // 静态节点数量(优化静态内容渲染)
shapeFlag: vnode.shapeFlag, // 节点形状标志位(用位掩码表示节点类型)
// 差异化更新标志(优化patch过程)
patchFlag: extraProps && vnode.type !== Fragment
? patchFlag === PatchFlags.HOISTED // hoisted node
? PatchFlags.FULL_PROPS
: patchFlag | PatchFlags.FULL_PROPS
: patchFlag,
dynamicProps: vnode.dynamicProps, // 动态属性列表(优化props更新)
dynamicChildren: vnode.dynamicChildren, // 动态子节点(block tree优化)
appContext: vnode.appContext, // 应用上下文(全局配置/组件等)
dirs: vnode.dirs, // 指令绑定信息(包含生命周期钩子)
transition: vnode.transition, // 过渡动画钩子(支持CSS/JS过渡)
component: vnode.component, // 组件实例(keep-alive缓存用)
suspense: vnode.suspense, // Suspense边界状态(异步组件用)
ssContent: /* 服务端渲染内容 */, // SSR激活内容
ssFallback: /* 降级内容 */, // SSR降级备用内容
el: vnode.el, // 对应真实DOM元素(渲染后赋值)
anchor: vnode.anchor, // Fragment锚点位置(DOM操作定位用)
ctx: vnode.ctx, // 渲染上下文(当前组件实例)
ce: vnode.ce // 自定义元素钩子(Web Components支持)
}
// 保持过渡效果与克隆节点的关联
if (cloneTransition) {
cloned.transition = vnode.transition?.clone(cloned)
}
__COMPAT__ && defineLegacyVNodeProperties(cloned)
return cloned
}
patchFlag更新规则:
原始flag | 额外props | 新flag
-------------------------------------------------
HOISTED | 存在 | FULL_PROPS
其他 | 存在 | 原flag | FULL_PROPS
任意 | 不存在 | 保持原flag
兼容模式在该指令下有效 vue build --target lib --compat
2.createBaseVNode
createBaseVNode是创建虚拟节点的基础函数,参数包括类型、属性、子节点等。函数内部构造了一个vnode对象,并进行了多项处理,比如规范化子节点、验证key、跟踪block树(动态节点树)等
该函数是Vue虚拟DOM系统的基石,通过结构化数据封装和优化策略,为高效的组件渲染和更新机制提供核心支持。
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false,
) {
// 核心构造流程分为三个阶段:
const vnode = {
__v_isVNode: true, // 虚拟节点身份标识(与普通对象区分)
__v_skip: true, // 跳过响应式代理标记(优化性能)
// 基础属性
type, // 节点类型(组件/HTML标签/内置组件符号)
props, // 合并后的属性对象
key: props && normalizeKey(props), // 标准化后的key值
ref: props && normalizeRef(props), // 处理后的ref引用
// 作用域标识
scopeId: currentScopeId, // 当前组件作用域ID(SFC样式隔离)
slotScopeIds: null, // 插槽作用域ID(动态插槽内容)
// 子节点结构
children, // 原始子节点(后续进行规范化处理)
// 组件相关
component: null, // 组件实例引用(挂载后赋值)
suspense: null, // Suspense边界状态(异步组件用)
ssContent: null, // SSR激活内容
ssFallback: null, // SSR降级内容
// DOM关联
el: null, // 对应的真实DOM元素
anchor: null, // Fragment锚点位置
target: null, // Teleport目标容器
targetAnchor: null, // Teleport目标锚点
// 优化标识
staticCount: 0, // 静态节点数量
shapeFlag, // 节点类型位掩码(快速类型判断)
patchFlag, // 差异化更新标志(优化diff算法)
dynamicProps, // 动态属性列表(优化props更新)
dynamicChildren: null, // 动态子节点(block tree优化)
// 上下文信息
appContext: null, // 应用级配置(全局组件/指令等)
ctx: currentRenderingInstance // 当前渲染实例上下文
} as VNode
// 阶段一:子节点规范化
if (needFullChildrenNormalization) {
normalizeChildren(vnode, children) // 深度规范化所有子节点
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).normalize(vnode) // Suspense特殊处理
}
} else if (children) {
vnode.shapeFlag |= isString(children) // 快速设置文本/数组子节点标志
? ShapeFlags.TEXT_CHILDREN
: ShapeFlags.ARRAY_CHILDREN
}
// 阶段二:开发环境校验
if (__DEV__) {
if (vnode.key !== vnode.key) { // 检测NaN类型的无效key
warn(`创建了无效key的VNode,节点类型:`, vnode.type)
}
}
// 阶段三:Block树追踪
// isBlockTreeEnabled 控制Block树追踪层级的全局开关 > 0 : 启用动态节点收集(默认)
//isBlockNode 标识当前VNode是否为Block节点
// currentBlock 当前活跃的Block上下文
if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
const shouldTrack = (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
vnode.patchFlag !== PatchFlags.NEED_HYDRATION
if (shouldTrack) { // 满足条件的动态节点加入当前block
currentBlock.push(vnode)
}
}
// 向后兼容处理
if (__COMPAT__) {
convertLegacyVModelProps(vnode) // Vue2的v-model属性转换
defineLegacyVNodeProperties(vnode) // 添加Vue2风格的虚拟节点属性
}
return vnode
}
// 形状标志(32位掩码)
enum ShapeFlags {
ELEMENT = 1,
COMPONENT = 1 << 2,
SUSPENSE = 1 << 10
}
// 补丁标志(16位掩码)
enum PatchFlags {
TEXT = 1, // 动态文本
CLASS = 1 << 1, // 动态class
PROPS = 1 << 3 // 动态props(不含class/style)
}
3.blockTree与patchFlags
这两者相互作用来提升diff算法的效率
传统diff是需要一层一层的遍历,其中包括各种动态节点和静态内容,实际上静态内容无需比较
vue3引入了PatchFlag用来标识该vnode是动态节点,dynamicChildren属性来存放动态子节点,这种节点就是block节点,这样我们就可以知道那些节点是动态节点来进行靶向更新了。
具体的创建blockTree后续章节会讲到