前言
距离上一篇文章源码解读预热过去了两周多的时间了,度过了国庆和中秋节,迟到的中秋国庆快乐送给大家,祝大家no bug,线上不报警,天天早下班。
Vue3系列文章将会围绕文件夹来进行讲解,主线流程走通后,再进行部分的讲解,会按照这个原则进行Vue3源码的逐一阅读和分析。
正文
正文从这里开始,Vue3源码分析之路从这里开始,
createApp
Vue3中创建应用的方式是通过createApp来进行的,较之前new Vue的方式在使用上基本无差别。
@file packages/runtime-dom/src/index.ts
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
if (__DEV__) {
injectNativeTagCheck(app)
}
const { mount } = app
app.mount = (containerOrSelector: Element | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
container.innerHTML = ''
const proxy = mount(container)
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
return proxy
}
return app
}) as CreateAppFunction<Element>
// Js中文网 -前端Vue3源码解析 https://www.javascriptc.com/
上面代码createApp就是咱们在创建应用时调用的方法
- ensureRenderer是一个单例模式的函数,会返回一个renderer,如果无renderer则会调用createRenderer进行获取renderer,获得了一个app实例;
- dev环境下注册一个方法:isNativeTag,挂载到app.config下面;
- 获取到实例的mount方法,并保存下来;
- 重写实例的mount方法;
-
- 调用normalizeContainer获取根元素容器;
-
- 判断template,获取需要渲染的模板;
-
- 把容器的innerHTML置空;
-
- 调用上面实例的mount方法;
-
- 删除v-cloak属性,添加data-v-app属性;
-
- 返回mount后的代理;
- 返回实例;
// 上面代码中所使用到的两个简单的函数源码
// @file packages/runtime-dom/src/index.ts
// 获取renderer
let renderer: Renderer<Element> | HydrationRenderer
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
// normalizeContainer,获取元素
function normalizeContainer(container: Element | string): Element | null {
if (isString(container)) {
return document.querySelector(container)
}
return container
}
// Js中文网 -前端Vue3源码解析 https://www.javascriptc.com/
至此,createAPP方法讲解完成。其中的核心包括两部分,第一部分就是调用createRenderer的实现;第二部分就是实例的mount方法的实现。
createRenderer
上面说到createAPP里面调用此方法获取renderer对象,来看下这个的实现。createRenderer接受一个对象参数rendererOptions, const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)
nodeOps和Vue2的时候的node-ops是差不多的功能,不过在Vue3中进行了优化,把patch和clone增加到了nodeOps里面,去掉了部分功能;
// @file packages/runtime-core/src/renderer.ts
export function createRenderer<HostNode = RendererNode,
HostElement = RendererElement>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
// Js中文网 -前端Vue3源码解析 https://www.javascriptc.com/
baseCreateRenderer的实现如下:
// @file packages/runtime-core/src/renderer.ts
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
// balabala,一堆函数的声明
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
// Js中文网 -前端Vue3源码解析 https://www.javascriptc.com/
可以看到上面的代码,最后返回的是render函数,hydrate函数和createApp函数,createApp函数是通过调用createAppAPI来获取到的。
// @file packages/runtime-core/src/apiCreateApp.ts
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
isCustomElement: NO,
errorHandler: undefined,
warnHandler: undefined
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null)
}
}
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
version,
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
use() {},
mixin() {},
component() {},
directive() {},
mount() {},
unmount() {},
provide() {}
})
return app
}
}
如上,就是createAppAPI的源码,直接返回了一个函数createApp。createApp:
- 对传递进来的第二个参数,也就是root props进行校验;
- 调用createAppContext创建appContext对象,赋值给context;
- 创建变量installedPlugins,Set类型,存储已经安装过的插件;
- isMounted设为false;
- 创建app,挂载属性和函数;
- 返回app;
此时的app属于Vue的一个准备阶段,为后面的mount等操作准备好了所需要使用到的函数。
mount
最核心(繁琐)的操作都在mount里面,这里面包括Vnode,render,patch等等所有的核心功能。咱们先理一下mount的调用流程。
- 第一步:调用runtime-dom下面index.ts里面的mount函数;
- 第二步:调用runtime-core下面apiCreateApp里面的mount函数;
前面这两步骤和Vue2中的调用是相似的,先调用平台相关的mount,再调用核心相关的mount;
- 第三步:直接调用createVNode;与Vue2不同的是,2并不会直接调用,而是建立一个watcher,使用watcher调用get的时候来调用,详情可见Vue2源码解读(七)-mount
- 第四步:调用render,判断是进行卸载还是进行渲染;
- 第五步:调用patch,进行dom diff,渲染页面。
结语
本篇文章算是简单的开了个小头,接下来会对mount部分进行详细的分析和解读。
路漫漫其修远兮,吾将上下而求索
听说最近react出了个recoil的官方状态管理库,更加简单的管理state了,期待Vue后续的跟进。
作者:德莱问
链接:https://juejin.im/post/6881910894473773069
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com