createApp
位置🍎:packages/compiler-core/src/apiCreateApp.ts
动机🍎:我们知道vue暴露了createAppAPI【位置packages/compiler-core/src/renderer.ts Line:2256】,源码得知createApp等于createAppAPI(render, hydrate),从而我们先从apiCreateApp.ts出发
import { createApp } from 'vue'
import App from './App.vue'
/* 不推荐这么写 */
/* 因为所有的Api其实都是通过createApp(App)暴露出去的(mount等) */
createApp(App).mount('#app')
/* 后面我们经常需要在全局做操作所以最好的写法 */
const app=createApp(App);
app.mount('#app');
首先先让我们来看一下Vue3新版createApp里面有什么?
ensureRenderer
位置🍎:packages/compiler-dom/src/index.ts
动机:vue3运行的核心render方法
-
经过断点发现
createApp接受的参数通过三点运算符进行了展开处理【说明createApp这边应该可以接受多个参数】/* args多参数 */ const createApp = ((...args) => { const app = ensureRenderer().createApp(...args); //do something.... }ensureRenderer函数执行了返回了createRenderer函数/* extend==Object.assign */ /* rendererOptions对象合并 */ const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps) let renderer; function ensureRenderer() { return renderer || (renderer = createRenderer(rendererOptions)); } -
rendererOptions包含了所有的节点操作packages/compiler-dom/src/nodeOps.ts:-
nodeOps(基础的节点操作)==【insert、remove,createElement、createText、createComment、setText、setElementText、parentNode、nextSibling、querySelector、setScopeId、cloneNode、insertStaticContent】 -
patchProp比较新旧的属性export const patchProp: DOMRendererOptions['patchProp'] = ( ..... ) => { switch (key) { // special case 'class': patchClass(el, nextValue, isSVG) break case 'style': patchStyle(el, prevValue, nextValue) break default: //do something.....patchDOMProp break } }我们知道解析元素的属性,特殊的
class的方法patchClass,特殊的style的方法patchStyle,以及最后的DOM原生的属性patchDOMProp方法我们从patchProp的类型DOMRendererOptions得知----->>>>RendererOptions的类型就是
extend({ patchProp, forcePatchProp }, nodeOps)的合并。#学到语法可以通过
DOMRendererOptions['patchProp']获取到某一串中的patchProp类型 -
forcePatchProp类型boolean,应该是是否强制比对props
-
-
createRenderer又把rendererOptions参数传递给baseCreateRenderer函数我们发现
baseCreateRenderer【位置:packages/compiler-core/src/renderer.ts】重载overload了俩次,实现implementation了一次baseCreateRenderer最后implementation出了vnode的diff算法生成正式DOM的方法,这个方法调用了rendererOptions的所有关于节点的操作通过diff算法,里面还包含了一些热更新的方法。这里暂不细讲
createAppAPI
位置🍎:packages/compiler-core/src/apiCreateApp.ts
ensureRenderer()执行完了,然后就执行createApp
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
//do something....
}
}
createAppAPI返回函数可以接受俩个参数:
rootComponent第一个参数毋庸置疑可以传递App根组件,rootComponent就是传入的参数APP==={name: "App", setup: ƒ}rootProps第二个参数全局属性会传递_props上面
const app=createApp(App, {
msg: '我是全局属性哦',
})
我们打印app的话,可以在其实例上找到{_props:{msg:'我是全局属性哦'}},这里跟源码切合实际。
createAppContext
首先初始化
//new 一下
const context = createAppContext()
//安装的插件
const installedPlugins = new Set()
//是否被加载到正式DOM上
let isMounted = false
context的初始值:
{
app: null as any,
config: {
isNativeTag: NO,//代表false
performance: false,
globalProperties: {},
optionMergeStrategies: {},
isCustomElement: NO,//代表false
errorHandler: undefined,
warnHandler: undefined
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null)
}
use
/* isFunction是否是typeof val === 'function' */
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)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
return app
}
-
当
Set()数据结构has到已经存在当前插件就会Warn【DEV==判断是否在开发模式】Plugin has already been applied to target app.
-
判断插件是否使用
install方法,- 存在:把Vue实例以及options传递给
install方法,提供给开发者使用,所以install方法能获取到俩个参数哦 - 不存在:Vue3可以直接写
function插件,导致不存在install方法,所以function直接能够获取到俩个参数,同install
- 存在:把Vue实例以及options传递给
-
如果以上都不成立且当前环境开发模式就直接warn,生产模式不进行warn报错
mixin
mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
} else if (__DEV__) {
warn(
'Mixin has already been applied to target app' +
(mixin.name ? `: ${mixin.name}` : '')
)
}
} else if (__DEV__) {
warn('Mixins are only available in builds supporting Options API')
}
return app
}
首先我们可以看到__FEATURE_OPTIONS_API__常量,字面意思就是特性选项API,判断了这个常量说明mixin成为了一个可关闭的特性,从warn我们也可以看出,未来有一天,也就是将不支持Mixins这个Options API
context.mixins.push(mixin)看出mixins在全局是一个数组,这说明他还可能遵循着Vue2的mixins规则
mixins: [mixin2, mixin1]会使得mixin1覆盖mixin2
component
component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component
return app
}
- 先检查组件
validateComponentName()的不能存在原生Tag,比如说div、span等 component参数为空的时候等,也会进行全局注册,不会报错context.components[name]匹配到相同name的组件的时候会warn警告- 最后直接全局注册
directive
directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}
if (!directive) {
return context.directives[name] as any
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.directives[name] = directive
return app
}
-
首先Vue3会先检查自定义指令不能够重名内置指令
这是源码定制的内置指令【位置🍎:
packages/compiler-core/src/directives.ts】bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text
-
directive参数为空的时候等,也会进行全局注册,不会报错 -
context.directives[name]匹配到相同name的组件的时候会warn警告 -
最后直接全局注册
mount
Don't say so much【多提一句:变量isMounted会变成true】
unmount
unmount() {
if (isMounted) {
render(null, app._container)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsUnmountApp(app)
}
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
}
- 前面也说,当
isMounted挂载ok了,我们这边才有卸载的能力
provide
provide(key, value) {
if (__DEV__ && (key as string | symbol) in context.provides) {
warn(
`App already provides property with key "${String(key)}". ` +
`It will be overwritten with the new value.`
)
}
// TypeScript doesn't allow symbols as index type
// https://github.com/Microsoft/TypeScript/issues/24587
context.provides[key as string] = value
return app
}
-
首先key可以是
string类型或者说是symbol类型拿来判断,并且如果in类型保护,也就是说context.provides当中有重复,Vue3会进行吧warn警告 -
不能用
symbols类型当作keyTypeScript doesn't allow symbols as index type github.com/Microsoft/T…
以上是createApp暴露出来的所有Api
use---->router
观察到install方法
app.component('RouterLink', RouterLink);
app.component('RouterView', RouterView);
app.config.globalProperties.$router = router;
Object.defineProperty(app.config.globalProperties, '$route', {
get: () => unref(currentRoute),
});
注册了全局组件RouterLink、RouterView
RouterLink
-
to:表示链路的目标路由,当单击时,to prop的值将在内部传递给router.push(),因此它可以是字符串,也可以是route location对象- 必传参数【
required: true】 - 可接收
String或者·Object类型/home{ name: 'home', params: { userId: '123' }}`
- 必传参数【
-
activeClass:标识链接处于被激活状态时应用于渲染的<a>的class -
ariaCurrentValue:当链接完全处于活动状态时传递给属性aria-current的值 -
custom:<router-link>不应该包裹<a>标签元素,当我们使用v-slot去创建个自定义的RouterLink,属性custom是非常有用的<router-link to="/home" v-slot="{ route }"> <span>{{ route.fullPath }}</span> </router-link> //渲染成 <a href="/home"><span>/home</span></a> -
exact-active-class:已经激活状态下的class
/* 这是RouterLink组件 */
defineComponent({
name: 'RouterLink',
props: {
to: {
type: [String, Object] as PropType<RouteLocationRaw>,
required: true,
},
activeClass: String,
// inactiveClass: String,
exactActiveClass: String,
custom: Boolean,
ariaCurrentValue: {
type: String as PropType<RouterLinkProps['ariaCurrentValue']>,
default: 'page',
},
},
setup(props, { slots, attrs }) {
//do something....
},
})
RouterView
当有一个名称时,它将在匹配路由记录的components选项中呈现具有相应名称的组件
defineComponent({
name: 'RouterView',
props: {
name: {
type: String,
default: 'default',
},
route: Object,
},
setup(props, { attrs, slots }) {
//do something....
},
});
然后把挂载$router与$route
app.mount
const { mount } = app
app.mount = (containerOrSelector: Element | string): any => {
const container = normalizeContainer(containerOrSelector)
if (container) {
return mount(container, true)
}
}
首先前面return app这边我们就可以mount解构出来,然后载真实DOM
-
normalizeContainer方法会document.querySelector方法去HTML找是否存在该元素function normalizeContainer(container: Element | string): Element | null { if (isString(container)) { const res = document.querySelector(container) if (__DEV__ && !res) { warn(`Failed to mount app: mount target selector returned null.`) } return res } return container }当不存在元素的时候(且在生产模式下),warn===
Failed to mount app: mount target selector returned null存在的时候会返回
HTMLElement元素if (!isFunction(component) && !component.render && !component.template) { component.template = container.innerHTML }还需要判断
App组件是不是Function或者有没有template或者有没有render,当都不存在的时候,会把真实DOM元素的内容变成template最后还会清除
container元素的内容外加v-cloak属性,新增data-v-app属性最后解析组件【这里不多叙述】,然后挂载页面