main.ts
打开到项目的主文件main.ts,可以看到
js
import App from './App.vue'
console.log(App);
/*
打印App组件,可以看到组件实例有两个方法,render渲染函数 和 setup(暂不深究)
{__name: 'App', __hmrId: '7a7a37b1', __file: '/Users/kirk/Desktop/project/vue_project/core/vue3/debug/vue-deug/src/App.vue', setup: ƒ, render: ƒ}
render: ƒ _sfc_render(_ctx, _cache, $props, $setup, $data, $options)
setup: setup(__props, { expose: __expose }) { __expose(); const count = ref(0); const add = () => {…}
__file: "/Users/kirk/Desktop/project/vue_project/core/vue3/debug/vue-deug/src/App.vue"
__hmrId: "7a7a37b1"
__name: "App"
[[Prototype]]: Object
*/
createApp(App).mount('#app')
- 进入源码,按住键盘command/control ,点击createApp,可以进入定义 createApp 的源文件 /packages/runtime-dom/src/index.ts
ts
export const createApp = ((...args) => {
// ensureRenderer函数 返回一个renderer 渲染器
const app = ensureRenderer().createApp(...args)
if (__DEV__) {
if (__DEV__) {
// 检查是否原生标签
injectNativeTagCheck(app)
// 检查是否自定义标签
injectCompilerOptionsCheck(app)
}
}
// 缓存原始mount方法
const { mount } = app
// 重写mount方法
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// 获取mount所挂载的dom
const container = normalizeContainer(containerOrSelector)
if (!container) return
// 存储的App根组件
const component = app._component
// 检查根组件是否存在template,否则就使用 所挂载dom的 innerHTML
if (!isFunction(component) && !component.render && !component.template) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's
// rendered by the server, the template should not contain any user data.
/*
__UNSAFE__
/原因:DOM模板中可能执行JS表达式。用户必须确保DOM中的模板是可信的。如果由服务器呈现,则模板不应包含任何用户数据。
*/
component.template = container.innerHTML
// 2.x compat check
/* __COMPAT__ 是启动的时候通过rollup去注入进去的
用来判断是否向下兼容 */
if (__COMPAT__ && __DEV__) {
for (let i = 0; i < container.attributes.length; i++) {
const attr = container.attributes[i]
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
compatUtils.warnDeprecation(
DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
null,
)
break
}
}
}
}
// clear content before mounting
container.innerHTML = ''
// 使用原始mount方法挂载
const proxy = mount(container, false, resolveRootNamespace(container))
if (container instanceof Element) {
// vue2的话,会给#app设置一个v-cloak属性,在render的时候清空掉
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
// 返回app实例
return app
}) as CreateAppFunction<Element>
- ensureRenderer 经过一系列参数处理 指向了 baseCreateRenderer方法
js
baseCreateRenderer(){
// ...
// 返回渲染器的属性
return {
render, // 渲染h函数
hydrate, //SSR相关
createApp: createAppAPI(render, hydrate), // 返回app对象并携带上下文
}
}
- createAppAPI, // 返回app对象并携带上下文
ts
export interface App<HostElement = any> {
version: string
config: AppConfig
use<Options extends unknown[]>(
plugin: Plugin<Options>,
...options: Options
): this
use<Options>(plugin: Plugin<Options>, options: Options): this
mixin(mixin: ComponentOptions): this
component(name: string): Component | undefined
component(name: string, component: Component | DefineComponent): this
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
mount(
rootContainer: HostElement | string,
isHydrate?: boolean,
namespace?: boolean | ElementNamespace,
): ComponentPublicInstance
unmount(): void
provide<T>(key: InjectionKey<T> | string, value: T): this
/**
* Runs a function with the app as active instance. This allows using of `inject()` within the function to get access
* to variables provided via `app.provide()`.
*
* @param fn - function to run with the app as active instance
*/
runWithContext<T>(fn: () => T): T
// internal, but we need to expose these for the server-renderer and devtools
_uid: number
_component: ConcreteComponent
_props: Data | null
_container: HostElement | null
_context: AppContext
_instance: ComponentInternalInstance | null
/**
* v2 compat only
*/
filter?(name: string): Function | undefined
filter?(name: string, filter: Function): this
/**
* @internal v3 compat only
*/
_createRoot?(options: ComponentOptions): ComponentPublicInstance
}
- 总结 vue3 的 createApp 方法首先创建了一个renderer渲染器,并调用渲染器的 createApp方法创建了app对象,app对象携带了上下文context和一些属性方法 之后缓存了mount方法, 然后又重写了app.mount,并调用normalizeContainer校验挂载元素,临时保存了需要渲染的内容。接下来并对vue2的写法做了兼容处理,最终挂载App,完成第一次渲染