Vue3源码解读之runtime(上)
前言
上一篇文章,我们提到packages
中核心的源码主要分为三部分,接下来我们就开始阅读runtime
部分的代码
createApp(App).mount('#app')
接下来我们就以入口文件中的这行代码开始来一步步深入
初始化
上一篇文章中我们提到vue主入口文件中,引入导出了runtime-dom
和compiler
,而createApp
就是来自runtime-dom
// packages/runtime-dom/src/index.ts
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
if (__DEV__) {
injectNativeTagCheck(app) // 在dev环境下,注册一个方法isNativeTag,挂载到app.config下面
}
const {
mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// ...
}
return app
}) as CreateAppFunction<Element>
在该函数内部中通过调用ensureRenderer()
和createApp(...args)
创建了app
实例并把实例返回出去,因此我们可以在app实例中安装插件,设置全局指令等等。这其中又是怎么实现的呢?
创建renderer
ensureRenderer()
函数的用途是什么呢?
// packages/runtime-dom/src/index.ts
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
我们可以看到调用该函数后返回一个renderer
,若没有renderer
则调用createRenderer
来进行创建。
而这边的createRenderer
则是来自runtime-core
// packages/runtime-core/src/index.ts
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
该函数接收一个RendererOptions
作为参数,其实际是调用了baseCreateRenderer
并将options
传入
// packages/runtime-core/src/renderer.ts
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
forcePatchProp: hostForcePatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
// 声明了许多操作函数,约2000行
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
在调用完baseCreateRenderer
后主要返回了三个函数:render
,hydrate
,createApp
。
此时renderer
便创建完成了。
这其中有一处需要留心,即传入的RendererOptions
是什么?为什么在runtime-dom
传入,又在runtime-core
拆解。
// packages/runtime-dom/src/index.ts
const rendererOptions = extend({
patchProp, forcePatchProp }, nodeOps)
// packages/runtime-dom/src/nodeOps.ts
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null)
},
remove: child => {
const parent = child.parentNode
if (parent) {
parent.removeChild(child)
}
},
// ...
}
是不是很熟悉,其实就是对于dom
操作的封装。那为什么要在runtime-dom
中传入,runtime-core
拆解?
其实是因为在Vue3中runtime-core
和runtime-dom
的拆分,runtime-core
不应该关心实际的操作,这样当新平台要接入时(比如weex)就可以只实现属于自己平台的nodeOps
。
总结:创建renderer
的函数调用顺序为
ensureRenderer()
createRenderer()
baseCreateRenderer()
创建app
当创建完renderer
后返回了3个函数,我们可以看到其中createApp
实际上是引用了createAppAPI(render, hydrate)
,所以其实const app = ensureRenderer().createApp(...args)
创建app实例时,调用的是createAppAPI
的返回值(运用柯里化,返回的是一个函数)
// packages/runtime-core/src/apiCreateApp.ts
export function createAppContext(): AppContext {