持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
前言
上一篇我们找到了入口文件,并分析到了 createApp 方法,在createApp 中,首先运行了ensureRenderer方法,然后再调用ensureRenderer里面的createApp方法,来生成 app,
然后再为 app重构了 mount 方法。
createRenderer
ensureRenderer 只做了一件事,就是调用了createRenderer生成了渲染器,并赋值给renderer。
// packages/runtime-dom/src/index.ts
let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
ensureRenderer 位于 /packages/runtime-core/src/renderer.ts 中。
// packages/runtime-core/src/renderer.ts
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
里面只有一行代码,返回了baseCreateRenderer的结果,我们来看看baseCreateRenderer的实现
function baseCreateRenderer(options, createHydrationFns) {
// 代码过长省略N行 ...
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
container._vnode = vnode
}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
这个方法的代码非常长,足有2000行,我们先跳过,直接看返回值,结果返回了一个render方法、hydrate属性,这里还返回了真正的createApp方法,由
createAppAPI 生成,并返回到了ensureRenderer进行执行,生成 app,也就是我们之前分析过的这句话 const app = ensureRenderer().createApp(...args)。
// packages/runtime-core/src/apiCreateApp.ts
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
use(plugin: Plugin, ...options: any[]) {},
mixin(mixin: ComponentOptions) {},
component(name: string, component?: Component): any {},
directive(name: string, directive?: Directive) { },
mount(): any {},
unmount() { /** 忽略 */ },
provide(key, value) { /** 忽略 */ }
})
return app
}
}
由这个方法我们就能很清楚的知道所创建的 app 的所有属性和方法。
mount
我们回到 packages/runtime-dom/src/index.ts 里面的 createApp 方法里。我们之前说过,在这里面对 mount 进行了重构
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
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) {
component.template = container.innerHTML
}
// 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
}
})
首先他把原本 app 里面的 mount 方法抽出来。然后重新为 app.mount 赋值一个方法,这个方法做了这几件事情。
- 调用 normalizeContainer 获取元素容器;
- 判断template,获取需要渲染的模板;
- 把容器的innerHTML置空;
- 调用旧的
mount方法,并返回 proxy - 删除v-cloak属性,添加data-v-app属性;
- 返回 proxy
我们再回去 createAppAPI 方法里,看看 mount是怎么实现的。
// packages/runtime-core/src/apiCreateApp.ts
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
// 是否初始化挂载,只执行一次
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
render(vnode, rootContainer, isSVG)
isMounted = true
app._container = rootContainer
return getExposeProxy(vnode.component!) || vnode.component!.proxy
},
}
}
}
- 首先判断是否挂载过,说明这个挂载方法只在初识化时执行一次。
- 通过
createVNode创建根节点的 vnode - 然后在执行
render方法进行渲染,render 方法是通过参数传进来的。
小结
这篇主要讲我们顺着createApp 这条线下去找,找到了 app 的属性,然后知道了在 mount 里面创建了根节点的 vnode,最后调用了render方法,对这个vnode 进行了渲染。
下一篇我们主要会围绕这个 render 方法,了解是怎么渲染到页面上的。下面是这部分的一个简单流程图。