vue实例化过程
createApp
- 调用
runtime-dom/index.ts
中的createApp
方法 - 在
createApp
中调用renderer
实例的createApp
方法,并将调用参数传递给它
export const createApp = (...args) => {
return ensureRenderer().createApp(...args);
};
- 通过
ensureRenderer
方法调用createRenderer
返回以单例模式返回renderer
实例
let renderer;
function ensureRenderer() {
// 如果 renderer有值的话,那么以后都不会初始化了
return (
renderer ||
(renderer = createRenderer({
createElement,
createText,
setText,
setElementText,
patchProp,
insert,
remove,
}))
);
}
createRenderer
构造函中调用baseCreateRenderer
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
baseCreateRenderer
函数包括了虚拟dom的path、diff等操作,另外创建一个render函数,以闭包的形式调用path方法,并将其传递createAppAPI
, 最终返回一个包含creatApp
方法的对象
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
...
... //省略n行
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
container._vnode = vnode
}
...
... //省略n行
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
createAppAPI
接收两个参数,第一个参数未render
函数,返回一个createApp
方法,该方法返回vue
实例
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
...
... // 省略
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = (context.app = {
_uid: uid++,
...
use(){...},
mixin(){...},
component(){...},
mount(){...},
unmount(){..},
... // 等等方法
})
return app
}
}
执行mount
挂载过程
mount
- 调用 app 实例的
mount
方法,在mount
方法中,先调用createVNode
方法根据根组件(app.vue)
生成虚拟dom结构, 再执行render
方法,传入生成虚拟dom,和容器rootContainer (#app)
...
... // 省略
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
debugger
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
...
... // 省略
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app
...
...
return getExposeProxy(vnode.component!) || vnode.component!.proxy
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}
},
... // 省略
...
render
render
方法中调用patch
方法,进行dom更新
// packages\runtime-core\src\renderer.ts
// 2332行
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
container._vnode = vnode
}
patch
const patch: PatchFn = (
n1, // 老节点
n2, // 新节点
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
// 新老节点一样不做处理
if (n1 === n2) {
return
}
// patching & not same type, unmount old tree
// 老节点存在,并且新老节点类型不一样时,卸载旧的节点
// 节点类型一样:n1.type===n2.type&&n1.key===n2.key
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
const { type, ref, shapeFlag } = n2
// 根据节点类型调用对应的更新方法
switch (type) {
// 文本节点
case Text:
processText(n1, n2, container, anchor)
break
// 注释节点
case Comment:
processCommentNode(n1, n2, container, anchor)
break
// 静态节点(无状态节点)
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
// 文档碎片
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
break
default:
// html节点 | type为string类型
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
// 组件节点(函数组件|有状态组件)| type 为对象或者函数
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
// 使用指令移动得节点(比如弹框,会移动到body下)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
}
....
.... // 省略
- 调用
mount
方法穿入的跟组件app.vue
是一个对象,所以调用processComponent
方法 processComponent
方法中根据老节点(n1)是否存在,判断是要执行更新还是创建操作,如果组件没有被缓存,调用mountComponent
挂载组件
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
n2.slotScopeIds = slotScopeIds
// 创建
if (n1 == null) {
// 使用了keep_alive
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
// 更新
updateComponent(n1, n2, optimized)
}
}
mountComponent
中调用先调用createComponentInstance
创建组件实例,然后调用setupComponent
方法确初始化组件的props
和slots
->initProps initSlots
,并且确定render
函数-
首先处理
setup
,如果setup
存在并且返回了一个function,则用它作为组件的render
函数,如果返回了一个对象,并且是虚拟dom,或者返回了一个promise
并且父组件不是Suspense
则抛出警告 -
setup
不存在,或者返回的不是一个function时,则使用type(组件)的render作为组件实例(instance)的render函数 -
处理
setup
时会根据setup
参数个数决定是否调用createSetupContext
创建包含attrs
、slots
、emit
、expose
属性的对象,即setp
上下文, 参数个数时才会创建
渲染函数
render
的优先级:setup中return的function
>组件中render函数选项
>template
-
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 2.x compat may pre-create the component instance before actually
// mounting
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
// 创建组件实例
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode, // 虚拟dom
parentComponent, // 父组件
parentSuspense
))
....
.... // 省略
// resolve props and slots for setup context
if (!(__COMPAT__ && compatMountInstance)) {
if (__DEV__) {
startMeasure(instance, `init`)
}
/**
* 1. 初始化props/slots
* 2. 如果是有状态组件,执行setupStatefulComponent
* - 如果是开发环境 校验组件命名|校验指令命名是否合法
* - 处理setup, 如果setup得参数个数大于1则创setup建执行上下文(context包括:attrs、emits、slots、expose)
* 2.给instance实例添加render方法
* 3.触发beforeCreate/created钩子(兼容2.x)
*/
setupComponent(instance)
....
.... // 省略不重要的东西
}
// setup() is async. This component relies on async logic to be resolved
// before proceeding
// setup返回了一个promise时,要做异步逻辑解析处理
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
// Give it a placeholder if this is not hydration
// TODO handle self-defined fallback
// 有异步依赖的组件, el不存在,先创建一个注释占位
if (!initialVNode.el) {
const placeholder = (instance.subTree = createVNode(Comment))
processCommentNode(null, placeholder, container!, anchor)
}
return
}
// 开始组件渲染
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
.... // 省略无用
....
}
setupComponent
,对组件进行初始化,并调用beforeCreate/beforeCreated
钩子
-
先初始化
props/slots
, 如果是有状态组件调用setupStatefulComponent
,setup存在, 创建setup执行上下文,并拿到setup函数返回值,不存在直接调用finishComponentSetup
-
调用
handleSetupResult
处理返回值,如果返回一个函数,则将其作为render函数,如果是虚拟dom对象或者undefined
则抛出警告 -
最后调用
finishComponentSetup
,如果组件的没有render
函数,则编辑template模板,作为render
函数 -
然后带调用
applyOptions
处理组件选项 -
合并
mixins
和全局(跟组件)属性方法->触发beforeCreate
->初始化methods
,用bind改变this
指向/初始化data/computed/watcher/provide->调用created
钩子
setupRenderEffect
- 创建组件更新函数
componentUpdateFn
,
- 创建组件更新函数
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// 组件更新函数
const componentUpdateFn = () => {
if (!instance.isMounted) { // 组件未挂载
....
....
} else {
....
....
}
}
// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope // track it in component's effect scope
))
const update = (instance.update = effect.run.bind(effect) as SchedulerJob)
update.id = instance.uid
....
....// 省略
update()
}
componentUpdateFn
组件更新函数
const componentUpdateFn = () => {
if (!instance.isMounted) { // 组件未挂载
let vnodeHook: VNodeHook | null | undefined
...
...
// beforeMount hook
// 执行beforeMount钩子,bm为一个数组,钩子函数可以有多个
if (bm) {
invokeArrayFns(bm为一个数组,钩子函数可以有多个)
}
// onVnodeBeforeMount
// 执行beforeMount指令
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)
) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
// 触发 hook:beforeMount
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeMount')
}
....
.... // 省略
// 执行instance 的 render,渲染组件(el的子组件)
const subTree = (instance.subTree = renderComponentRoot(instance))
....
....
// 递归调用patch方法,以此进行所有子节点(组件)dom创建/更新
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
// mounted hook
// 组件挂载完成,执行mounted钩子
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
) {
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:mounted'),
parentSuspense
)
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
// 使用了keep-alive,触发activated钩子
if (initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
instance.a && queuePostRenderEffect(instance.a, parentSuspense)
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:activated'),
parentSuspense
)
}
}
instance.isMounted = true
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentAdded(instance)
}
// #2458: deference mount-only object parameters to prevent memleaks
initialVNode = container = anchor = null as any
} else {
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
}
// Disallow component effect recursion during pre-lifecycle hooks.
toggleRecurse(instance, false)
if (next) {
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// beforeUpdate hook
// 触发beforeUpdate钩子
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
instance.emit('hook:beforeUpdate')
}
....
....// 省略
// 更新子节点
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
....
....
// updated hook
// 触发updated钩子
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
if (
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
) {
queuePostRenderEffect(
() => instance.emit('hook:updated'),
parentSuspense
)
}
....
}
}
实例化并挂载过程总结
graph TD
createApp --> 返回Vue实例 --> 执行mount --> 执行render方法 --> patch -->
判断节点类型type/ShapeFlags --> 节点类型为函数或者有状态组件 --> processComponent --> mountComponent:调用createComponentInstance创建组件实例 --> setupComponent设置/校验组件状态,执行setup,确定组件的render函数,并触发beforeCreate钩子--> 对组件data/method/computed等属性做代理或添加响应式等操作--> 触发created钩子,并注册之后得声明周期钩子 --> setupRenderEffect --> componentUpdateFn:组件更新函数,先触发beforeMount --> 调用组件实例render方法获取子节点subTree,调用patch方法,然后触发mounted钩子,再触发activited钩子 --> patch
判断节点类型type/ShapeFlags --> 节点类型为element,即type为string --> 根据type创建el/或者更新 --> 如果子节点为数组--> mountChildren:遍历调用patch --> patch
shapeFlag
(节点类型判断)
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0