主要是记录,复习一下近期的源码学习
目录
1.createApp
在我们编写vue的时候,基本都是从下面的代码开始,既然想了解vue的源码,那么肯定要从入口开始
createApp(App).mount(rootContainer);
1.1 createApp做了什么?
这里主要干了三件事,第一创建renderer,第二创建app实例,第三重写mount函数(检测组件是否为函数)。 首先通过ensureRenderer方法创建renderer渲染器,然后再通过createApp创建app实例,最后重写app.mount方法,返回proxy代理组件实例
其中最主要的就是 const app = ensureRenderer().createApp(...args) 创建renderer后创建app实例,此时的createApp才是真正的createApp
// 项目开始地方---入口方法,通过ensureRenderer确保一个renderer调用真实的createApp
// runtime-dom/src/index.ts
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
if (__DEV__) {
injectNativeTagCheck(app)
injectCompilerOptionsCheck(app)
}
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) {
// __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.
component.template = container.innerHTML
// 2.x compat check
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 = ''
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>
1.2 创建renderer
renderer是由ensureRenderer创建的,通过源码可以得知,他是惰性创建渲染器renderer,如果renderer已经创建那么直接返回已经创建好的renderer,否则通过createRenderer创建新的渲染器renderer
// render有值 就不进行create
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
createRenderer是通过baseCteateRender并将收到的options传到baseCreateRender
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
baseCreateRender是源码中最重要的函数了,他的内部包含了许多函数,因为 const app = ensureRenderer().createApp(...args) ,通过baseCreateRender进行createApp,所以我们先从他的return做起
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
1.3真正的createApp
由这个return可以得知真实调用的其实是createAppAPI函数,所以真正的createApp(app)实际上是在使用createAppAPI返回的createApp方法,该方法最主要的作用就是通过createAppContext创建 返回一个包含app、config、mixins、components、directives和provides基础参数对象, 然后再创建context.app对象,也就是vue的实例对象
回归一下本章的讨论重点 createApp(App).mount(rootContainer) 很明显我们可以知道就是调用这个createApp创建的app对象中的mount方法,mount方法的核心就是通过createVnode创建虚拟节点,并且基于vnode调用render函数
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: {}
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap()
}
}
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
//接收rootComponent,rootProps
return function createApp(rootComponent, rootProps = null) {
//如果rootComponent不是函数 转为对象
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)
}
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
//创建context
const context = createAppContext()
// TODO remove in 3.4
if (__DEV__) {
Object.defineProperty(context.config, 'unwrapInjectedRef', {
get() {
return true
},
set() {
warn(
`app.config.unwrapInjectedRef has been deprecated. ` +
`3.3 now always unwraps injected refs in Options API.`
)
}
})
}
const installedPlugins = new Set()
let isMounted = false
// 创建了 app 对象基本数据结构,并赋值context.app和app
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
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
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
},
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
},
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
},
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
},
// 挂载
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
// #5571
if (__DEV__ && (rootContainer as any).__vue_app__) {
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling \`app.unmount()\` first.`
)
}
//基于根组件创建 vnode--虚拟节点
const vnode = createVNode(rootComponent, rootProps)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
// HMR root reload
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
}
}
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// 调用render函数,基于vnode 进行开箱
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
; (rootContainer as any).__vue_app__ = app
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}
return getExposeProxy(vnode.component!) || vnode.component!.proxy
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}
},
unmount() {
if (isMounted) {
render(null, app._container)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = null
devtoolsUnmountApp(app)
}
delete app._container.__vue_app__
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
},
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.`
)
}
context.provides[key as string | symbol] = value
return app
},
runWithContext(fn) {
currentApp = app
try {
return fn()
} finally {
currentApp = null
}
}
})
if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}
return app
}
}