vue3源码学习1-应用初始化
创建一个vue项目,我们从入口代码main.ts文件中可以看到:
// 从vue导入一个createApp方法,使用该方法创建一个app对象,并且重写app.mount方法
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
从官网(https://github.com/vuejs/core)下载一份源码
在packages/runtime-dom/src/index.ts 中可以找到createApp()方法
// 使用ensureRenderer().createApp创建app对象
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
...
return app
})
// 延时创建渲染器,当用户只依赖响应式包的时候,可以通过 tree-shaking 移除核心渲染逻辑相关的代码
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
在packages/runtime-core/src/renderer.ts中找到createRenderer方法
//可以发现调用的是baseCreateRenderer方法
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
// 创建一个渲染器,最后执行的是createAppAPI返回的函数。
function baseCreateRenderer(options){
...
const render: RootRenderFunction = (vnode, container, isSVG) => {
// 组件渲染的核心逻辑
}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
在packages/runtime-core/src/apiCreateApp.ts中找到createAppAPI方法
// 上一步中使用的函数,也就是createApp方法,接收rootComponent和rootProps两个参数, 我们在main.ts执行createApp(App)时,
// 会把App组件作为根组件传递给rootComponent,这样createApp内部就创建了一个app对象,提供了mount方法
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const app: App = (context.app = {
_component: rootComponent as ConcreteComponent,
_props: rootProps,
...
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
// 创建根组件的vnode
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// 渲染vnode
render(vnode, rootContainer, isSVG)
app._container = rootContainer
return getExposeProxy(vnode.component!) || vnode.component!.proxy
}
...
return app
}
}
app对象创建完成,接下来重写app.mount(为什么要重写,上一步中的app对象中不是已经有mount方法了吗?因为vue跨平台,之前的mount方法是可以跨平台的通用渲染流程),重写代码在packages/runtime-dom/src/index.ts的createApp方法中:
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// 标准化容器,如果传入的是字符串,通过document.querySelector()转为DOM
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
// 如果组件对象没有定义render函数和template,则取容器的innerHTML作为组件模板内容
component.template = container.innerHTML
}
// 挂载前清除容器
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
}