应用程序示例创建过程 createApp()
如何创建实例?
在 todomvc 中调用 createApp 方法,并且把相应的配置信息传进去。
const { createApp, ... } = Vue
createApp({
setup () {
...
return { ... }
},
directives: {
'todo-focus': (el, { value }) => {
if (value) {
el.focus()
}
}
}
}).mount('#app')
runtime-dom/src/index.ts 文件中定义了 createApp,createApp 方法通过 ensureRenderer().createApp() 创建实例。
export const createApp = ((...args) => {
//调用渲染器创建 app
const app = ensureRenderer().createApp(...args)
...
const { mount } = app
//覆盖 app 的 mount 方法(分支问题)
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
...
}
return app
}) as CreateAppFunction<Element>
同样在 runtime-dom/src/index.ts 文件中定义了 ensureRenderer(),这个方法返回一个渲染器。ensureRenderer 方法又会调用 createRenderer 创建渲染器。
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
createRenderer 再调用 baseCreateRenderer,该方法在 runtime-core/src/renderer.ts 中,最终返回渲染器,这也是一个超级长的方法。返回一个对象,对象包含了 createApp 方法等。createApp 由一个工厂方法 createAppAPI 创建。
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
实例长什么样子?
createAppAPI 工厂方法位于 runtime-core/src/apiCreateApp.ts 文件中,返回 createApp 方法,该方法返回 app 对象,app 中有我们熟悉的 use、mixin、mount 等等。
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
...
const app: App = (context.app = {
...
version,
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
use(plugin: Plugin, ...options: any[]) {
...
return app
},
mixin(mixin: ComponentOptions) {
...
return app
},
component(name: string, component?: Component): any {
...
return app
},
directive(name: string, directive?: Directive) {
...
return app
},
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
...
},
unmount() {
...
},
provide(key, value) {
...
return app
}
})
...
return app
}
}
挂载过程 app.mount()
挂载过程是怎么样的?
- 创建根节点的 vnode;
- 执行渲染,把第一步创建的 vnode 传给 patch 转化成真实 dom,再追加到宿主元素中,宿主元素为 app.mount() 传入的元素;
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
...
const app: App = (context.app = {
...
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
//保证只挂载一次
if (!isMounted) {
//创建虚拟节点
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
...
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
//这里的 render 是通过参数传进来的
render(vnode, rootContainer, isSVG)
}
isMounted = true
...
} else if (__DEV__) {
...
}
},
...
})
...
return app
}
}
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
...
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
//将虚拟节点转换成真实节点,并追加到 container 上
//这里的 container 是 app.mount('#app') 中的 #app
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
...
}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
挂载的调用堆栈
Vue3 初始化流程
初始化流程调用栈
初始化流程图
setupRenderEffect():安装渲染函数副作用,除了首次 patch之外,还会定义更新机制,以后如果有数据变化会继续更新。