createApp 介绍
# 概述
createApp
是使用 vue3 调用的第一个也是必须的入口函数,通过它可以创建一个应用实例,然后我们可以通过返回的实例对象挂载到真实的 DOM 元素上,还可以安装插件等等,一般使用如下
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
const app = createApp(App); // App为根组件
app.use(createPinia()); // 安装Pinia插件
app.mount("#app"); // 挂载
2
3
4
5
6
7
8
# 深入了解 createApp
# runtime-dom
中的包装
我们引用的createApp
函数定义如下所示,其在node_modules\@vue\runtime-dom
const createApp = (...args) => {
const app = ensureRenderer().createApp(...args);
const { mount } = app;
app.mount = (containerOrSelector) => {
const container = normalizeContainer(containerOrSelector); // 通过`document.querySelector`查找并返回 dom 元素
if (!container) return;
const component = app._component;
if (
!shared.isFunction(component) &&
!component.render &&
!component.template
) {
component.template = container.innerHTML;
}
container.innerHTML = ""; //清空
const proxy = mount(container, false, resolveRootNamespace(container)); // resolveRootNamespace 判断container是属于svg还是MathML命名空间
if (container instanceof Element) {
container.removeAttribute("v-cloak");
container.setAttribute("data-v-app", "");
}
return proxy;
};
return app;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
很显然这里只是给app
包装了一层,定义了一个mount
函数,具体的实现是第一句ensureRenderer().createApp(...args)
,实际上,这句返回的才是我们所熟知的实例app
。通过观察发现,它是调用了runtimeCore
的createRenderer
方法,参数为rendererOptions
,包含patchProp
方法和nodeOps
# patchProp
方法
patchProp
主要作用是根据虚拟 DOM 的变化来更新实际的 DOM 元素的属性,它负责处理添加、删除和更新 DOM 元素的属性,实现 Vue 组件的属性动态更新。而patchProp
的操作依据就是参数key
的值,这里不过多赘述,详见 key 的更新机制
//伪代码
const patchProp = (
el,
key,
prevValue,
nextValue,
namespace,
prevChildren,
parentComponent,
parentSuspense,
unmountChildren
) => {
if(key==='class'){
patchClass()
}else if(key==='style'){
patchStyle()
}else if(isOn(key)){ //key是否是onClick等等
patchEvent()
}else if(...){ //主要用于处理设置元素的 CSS 类、排除不应设置为属性的情况,以及正常设置属性的情况
patchDOMProp()
}else{
patchAttr()
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# nodeOps
nodeOps
就是基于 DOM 定义了一些 DOM 节点的操作方法,这些方法将在 vue3 运行 DOM 时被使用,主要如下
insert
: 将一个子节点插入到父节点中,可以指定插入位置。
remove
: 从父节点中移除一个子节点。
createElement
: 创建一个新的 DOM 元素节点。
createText
: 创建一个包含指定文本的文本节点。
createComment
: 创建一个注释节点。
setText
: 设置文本节点的文本内容。
setElementText
: 设置元素节点的文本内容。
parentNode
: 获取节点的父节点。
nextSibling
: 获取节点的下一个兄弟节点。
querySelector
: 在文档中查找匹配指定选择器的第一个元素。
setScopeId
: 设置元素节点的作用域 ID。
insertStaticContent
: 插入静态内容到指定位置。
# runtime-core
中的核心实现
runtime-core\src\renderer.ts
中runtime-core
调用createRenderer
函数,会返回一个重载函数baseCreateRenderer
,该函数里面定义了许多操作组件的方法,返回如下
return {
render,
hydrate, //ssr 渲染不用管
createApp: createAppAPI(render, hydrate),
};
2
3
4
5
# render
方法
render
顾名思义就是用来渲染节点,这也放在其它文章里重点讲解组件的渲染
const render = (vnode, container, namespace) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
} else {
patch(
container._vnode || null,
vnode,
container,
null,
null,
null,
namespace
);
}
if (!isFlushing) {
isFlushing = true;
flushPreFlushCbs();
flushPostFlushCbs();
isFlushing = false;
}
container._vnode = vnode;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# createAppAPI
方法
createAppAPI
是在runtime-core\src\apiCreateApp.ts
中实现的,其伪代码如下。
export function createAppAPI(render) {
return function createApp(rootComponent, rootProps=null) {
const context = createAppContext()
const installedPlugins = new WeakSet()
const app = {
_uid: uid++,
_component: rootComponent, // 即最上面的 App 根组件
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
get config(){},
set config(){},
use(plugin,...options){},
mixin(){},
component(name,component){},
mount(){},
unmount(){},
provide(){},
runWithContext(fn){}
};
return app;
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
首先调用createAppContext
定义了上下文环境context
,主要用来存放cache
数据, 然后声明一个WeakSet
集合存储插件,最后定义一个 app 对象并返回。
当我们通过app
实例进行挂载mount
时,本质上就是调用的上述 app 中的mount
方法, 创建完虚拟节点vnode
后,会调用render
方法是在baseCreateRenderer
中实现的,如上。
mount(rootContainer, isHydrate, namespace) {
if (!isMounted) {
const vnode = createVNode(rootComponent, rootProps); //根据rootComponent 创建虚拟DOM
vnode.appContext = context; //绑定context
if (namespace === true) {
namespace = "svg";
} else if (namespace === false) {
namespace = void 0;
}
if (isHydrate && hydrate) {
hydrate(vnode, rootContainer); // ssr 不用管
} else {
render(vnode, rootContainer, namespace);
}
isMounted = true; // 重置挂载标志
app._container = rootContainer;
rootContainer.__vue_app__ = app;
return getExposeProxy(vnode.component) || vnode.component.proxy;
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20