共计 6674 个字符,预计需要花费 17 分钟才能阅读完成。
当咱们像上面这样应用 createApp 创立 vue app 实例过程中产生了什么?
const {createApp} = Vue
createApp({setup() {return {}
}
})
.mount('#app')
咱们一起来看看。
首先,进入的是 vue 导出的 createApp 函数,它将所有参数都合并为了 args, 并调用了 ensureRenderer 函数并调用了其返回数据上的 createApp,而后将 args 打散传入。
这里能够看出 ensureRenderer 调用的是 runtime-core 内导出的 createRenderer 办法,createRenderer 接管了 rendererOptions,
这样做的目标是,一是应用 renderer 做了单例缓存,防止创立多个 renderer, 二是 vue3 思考到更好的反对多端的渲染,没有强耦合浏览器的 dom 操作,而是把一些操作的具体实现裸露给开发者本人去实现,这里传入的 rendererOptions 就是 浏览器 dom 环境的具体实现
import {createRenderer} from '@vue/runtime-core'
const rendererOptions = extend({patchProp}, nodeOps) // 渲染相干的一些配置,比方更新属性的办法,操作 DOM 的办法
let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
export const createApp = ((...args) => {const app = ensureRenderer().createApp(...args) //
//...
})
createRenderer 办法外部调用的又是 baseCreateRenderer
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {return baseCreateRenderer<HostNode, HostElement>(options)
}
baseCreateRenderer 外部内容就很多了,这里的 options 就是内部传入的平台相干的操作细节 rendererOptions,而后将 insert、remove 等办法从 options 中取出。在下方实现的渲染相干的一系列办法中调用,因为内容过长,这里将其省略了。再往下,函数的开端呢导出了二个比拟重要的货色,render 办法,createApp 办法,而后 createApp 办法又是通过 createAppAPI 办法创立的的,其承受了 render 和 hydrate。这也是内部能够调用 ensureRenderer().createApp(...args)
的起因
import {createAppAPI} from './apiCreateApp'
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
const patch: PatchFn = () => {/*.... */}
const processText: ProcessTextOrCommentFn = = () => {/*.... */}
const processCommentNode: ProcessTextOrCommentFn = = () => {/*.... */}
const mountStaticNode = = () => {/*.... */}
const patchStaticNode = () => {/*.... */}
const moveStaticNode = () => {/*.... */}
const removeStaticNode = () => {/*.... */}
const processElement = () => {/*.... */}
const mountElement = () => {/*.... */}
const setScopeId = () => {/*.... */}
const mountChildren: MountChildrenFn = () => {/*.... */}
const patchElement = () => {/*.... */}
// The fast path for blocks.
const patchBlockChildren: PatchBlockChildrenFn = () => {/*.... */}
const patchProps = () => {/*.... */}
const processFragment = () => {/*.... */}
const processComponent = () => {/*.... */}
const mountComponent: MountComponentFn = () => {/*.... */}
const updateComponent = () => {/*.... */}
const setupRenderEffect: SetupRenderEffectFn = () => {/*.... */}
const updateComponentPreRender = () => {/*.... */}
const patchUnkeyedChildren = () => {/*.... */}
const patchKeyedChildren = () => {/*.... */}
const move: MoveFn = () => {/*.... */}
const unmount: UnmountFn = () => {/*.... */}
const remove: RemoveFn = () => {/*.... */}
const removeFragment = () => {/*.... */}
const unmountComponent = () => {/*.... */}
const unmountChildren: UnmountChildrenFn = () => {/*.... */}
const getNextHostNode: NextFn = () => {/*.... */}
const render: RootRenderFunction = () => {/*.... */}
const internals: RendererInternals = () => {/*.... */}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
createAppAPI 外部返回了 createApp 函数。这个函数外部次要是创立了 app 对象根本数据结构,其上有 uuid, use(注册插件),component(注册子组件),mount 等等属性和办法。这里要留神的是,mount 办法外部调用了 createAppAPI 接管的 render 渲染器,此时以闭包模式存在,最初返回了 app 对象
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++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
get config() {return context.config},
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
},
mixin(mixin: ComponentOptions) {if (__FEATURE_OPTIONS_API__) {if (!context.mixins.includes(mixin)) {context.mixins.push(mixin)
}
}
return app
},
// 注册组件
component(name: string, component?: Component): any {if (!component) {return context.components[name]
}
context.components[name] = component
return app
},
directive(name: string, directive?: Directive) {if (!directive) {return context.directives[name] as any
}
context.directives[name] = directive
return app
},
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
render(vnode, rootContainer, isSVG)
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app // app 挂到 dom __vue_app__ 属性上
return vnode.component!.proxy
}
},
unmount() {if (isMounted) {render(null, app._container)
delete app._container.__vue_app__
}
},
provide(key, value) {context.provides[key as string] = value
return app
}
})
return app
}
}
返回 app 后,这时就回到了一开始的 createApp 函数,将返回的 app 实例存储在了 app 变量内,并取出 mount 办法,并从新定义了一个 app.mount 办法,对本来的 mount 办法进行了一次包装,包装的目标呢和 renderer 是一样的,将平台的具体实现抽离,由开发者自行实现,
这里 app.mount 办法所接管的 containerOrSelector 参数就是咱们内部传入的 #app
选择器 , 而后应用 normalizeContainer 对这个选择器进行标准化,返回的是理论的 dom,如果没找到理论的 dom 的话就间接 return 了,如果发现组件没有提供 template、render,那就会取 dom 的 innerHTML 作为 template , 并在调用 mount 办法挂载前清空理论 dom 的 innerHTML,挂载完后移除了 v -cloak 等属性,并将 app 返回
export const createApp = ((...args) => {const app = ensureRenderer().createApp(...args)
const {mount} = app // 取出本来的 mount
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}
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
return app
}) as CreateAppFunction<Element>
当内部调用 mout 办法时,app.mount 天然就被调用了
createApp().mount('#app')
这就又进入到了在 createApp 中定义的最后的 mount , 外部创立了 vNode , 并调用了 render 办法进行渲染,并将 isMounted 批改
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
render(vnode, rootContainer, isSVG)
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
}
}
好了,这样整个流程就走完了
整顿一下,整个调用过程如下:
createApp -> ensureRenderer -> baseCreateRenderer -> createAppAPI (接管 render 办法) -> createApp 创立并返回 app 对象 -> 回到 createApp,包装 app.mount -> 内部调用 .mount('#app')
-> 触发 app.mount -> mount -> 调用 render 办法渲染