createApp是vue3的启动函数,返回一个应用实例
对于createApp函数执行过程,我们从一个简单的例子开始
一个最简单的例子
<div class="" id="app">
{{obj.name}}
</div>
<script>
const {
createApp
} = Vue
const app = createApp({
data(){
return{
obj:{name:'zhangsan'}
}
}
}).mount('#app')
</script>
先看下执行时候的调用栈,从下往上执行,知道了调用过程
| createApp() | .mount('#app') | |
|---|---|---|
| 功能说明 | 得到app实例,提供了应用api,各api能实现链式操作 | 数据响应式,获得vNode,patch成DOM(diff算法) |
| 调用栈 |
本文先看看createApp()的执行,初步看看里面每个函数都干了些什么
createApp
createApp返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。你可以在
createApp之后链式调用其它方法,这些方法可以在应用 API 中找到
实现链式调用的方法很巧妙,从源码中看createApp只是用来包装app实例的,一旦执行createApp内部的app实例就被创建并返回
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
...
return app
})
createApp是整个vue3开始的入口,该方法完成两个功能:
- 得到app应用实例:执行
createApp方法,返回app应用实例,app则是通过ensureRenderer的createApp方法得到 - 扩展
mount方法
想要知道初始化都干了什么,接着看ensureRenderer
//** runtime-dom/src/index.ts */
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// clear content before mounting
container.innerHTML = ''
const proxy = mount(container)
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
return app
}) as CreateAppFunction<Element>
ensureRenderer
此函数为单例模式,有renderer方法则直接返回,没有则通过createRenderer创建
//** runtime-dom/src/index.ts */
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)
// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer: Renderer<Element> | HydrationRenderer
let enabledHydration = false
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
对于renderer方法,他又干了些什么,可以从其类型声明接口找到答案
export interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement>
createApp: CreateAppFunction<HostElement>
}
在初始化的时候,很显然renderer方法是不存在的,那么将会执行createRenderer方法
createRenderer(自定义渲染函数)
createRenderer函数接受两个泛型参数:HostNode和HostElement,对应于宿主环境中的 Node 和 Element 类型。在runtime-dom中,HostNode 将是 DOM
Node接口,HostElement 将是 DOMElement接口。
createRenderer 函数,从源码的注释上可以知道,这是一个可以提供给用户自定义渲染器的方法。
createRenderer 函数的返回值对象必须要有render和createApp方法,而这两个方法是由baseCreateRenderer提供的
//** runtime-core/src/renderer.ts */
/**
* The createRenderer function accepts two generic arguments:
* HostNode and HostElement, corresponding to Node and Element types in the
* host environment. For example, for runtime-dom, HostNode would be the DOM
* `Node` interface and HostElement would be the DOM `Element` interface.
*
* Custom renderers can pass in the platform specific types like this:
* 自定义渲染器可以像这样传入特定平台的类型:
*
* ``` js
* const { render, createApp } = createRenderer<Node, Element>({
* patchProp,
* ...nodeOps
* })
* ```
*/
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
createRenderer 函数接收一个参数,该参数定义了宿主环境中的 Node 和 Element 类型的渲染方式
下图为vue3中createRenderer 函数rendererOptions参数默认方法
rendererOptions类型声明接口
//** runtime-dom/src/index.ts */
//vue3默认的的渲染方式
import { nodeOps } from './nodeOps'
import { patchProp, forcePatchProp } from './patchProp'
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)
//** runtime-core/src/renderer.ts */
export interface RendererOptions<
HostNode = RendererNode,
HostElement = RendererElement
> {
patchProp(
el: HostElement,
key: string,
prevValue: any,
nextValue: any,
isSVG?: boolean,
prevChildren?: VNode<HostNode, HostElement>[],
parentComponent?: ComponentInternalInstance | null,
parentSuspense?: SuspenseBoundary | null,
unmountChildren?: UnmountChildrenFn
): void
forcePatchProp?(el: HostElement, key: string): boolean
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
remove(el: HostNode): void
createElement(
type: string,
isSVG?: boolean,
isCustomizedBuiltIn?: string
): HostElement
createText(text: string): HostNode
createComment(text: string): HostNode
setText(node: HostNode, text: string): void
setElementText(node: HostElement, text: string): void
parentNode(node: HostNode): HostElement | null
nextSibling(node: HostNode): HostNode | null
querySelector?(selector: string): HostElement | null
setScopeId?(el: HostElement, id: string): void
cloneNode?(node: HostNode): HostNode
insertStaticContent?(
content: string,
parent: HostElement,
anchor: HostNode | null,
isSVG: boolean
): HostElement[]
}
baseCreateRenderer
baseCreateRenderer方法定义了vue整个渲染过程,最终返回 render hydrate createApp 3个函数,
render方法:vnode转变为真实DOM
createApp方法:app实例创建的工厂函数,接收render方法,hydrate 为可选参数,ssr 的场景下会用到
//** runtime-core/src/renderer.ts */
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
forcePatchProp: hostForcePatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
//...中间省略1800多行代码,内容为关于渲染器用到的函数声明
const render: RootRenderFunction = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
//初始化走这里
//最终转换方法还是patch
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode
}
//...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
createAppAPI
createAppAPI方法是返回的是真正的createApp
当createApp被调用时,返回app实例,定义了实例方法,每个实例方法返回app实现链式操作
得到app实例后执行mount方法,调用render,通过patch将vnode转变为DOM
//** runtime-core/src/apiCreateApp.ts */
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
// 创建默认APP配置
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
// 定义app实例,相当于vue2的new Vue()
const app: App = {
_component: rootComponent as Component,
_props: rootProps,
_container: null,
_context: context,
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
use() {},
mixin() {},
component() {},
directive() {},
mount(rootContainer: HostElement, isHydrate?: boolean): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer)
}
isMounted = true
app._container = rootContainer
return vnode.component!.proxy
} else if (__DEV__) {
...
}
},
unmount() {},
// ...
}
return app
}
}