createApp创建实例解析
# 概览
在vue3项目中,通常是通过const app = createApp(根组件)
来创建一个vue实例,app
即为vue实例。本文主要介绍createApp
这个API背后究竟
是做了那些工作。
# 源码解析
createApp
入口是在packages\runtime-dom\src\index.ts
中实现的,createApp
需要结合vue3的渲染器使用,而渲染器是在packages\runtime-core\src\renderer.ts
中实现的,本文主要介绍入口部分,关于渲染器需要几篇文章才能分析完。
# createApp
createApp
的内部就是将参数传给渲染器的createApp
方法,返回一个app
实例,然后重写了mount
方法,用于挂载DOM根节点。
其源码实现如下:
const createApp = (...args) => {
// 通过渲染器返回app实例
const app = ensureRenderer().createApp(...args);
const { mount } = app;
// 重写app实例的mount方法,用于挂载
app.mount = (containerOrSelector) => {
// containerOrSelector是一个css选择器,normalizeContainer是一个内部方法,会返回DOM容器
const container = normalizeContainer(containerOrSelector);
// 若容器不存在,就返回
if (!container) {
return;
}
// 根节点,实际上就是参数args
const component = app._component;
// 判断若根节点不是一个参数,并且根节点上也不存在render和template,则将容器的子元素作为根节点的template
if (!shared.isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML;
}
// 若容器是一个元素节点,则将其内容清空
if (container.nodeType === 1) {
container.textContent = "";
}
// 调用渲染器的mount方法挂载,mount方法会返回根组件实例的代理对象
const proxy = mount(container, false, resolveRootNamespace(container));
// 判断若容器是DOM元素,则移除属性v-cloak,添加属性data-v-app
if (container instanceof Element) {
container.removeAttribute("v-cloak");
container.setAttribute("data-v-app", "");
}
// 返回代理对象proxy
return proxy;
}
// 最后返回应用实例app
return app;
}
1
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
29
30
31
32
33
34
35
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
29
30
31
32
33
34
35
# ensureRenderer
ensureRenderer
就是调用runtimeCore
的createRenderer
创建一个渲染器,保存在renderer
中,构造器的参数rendererOptions
包括patchProp
和nodeOps
。
let renderer;
const rendererOptions = shared.extend({ patchProp }, nodeOps);
function ensureRenderer() {
return renderer || (renderer = runtimeCore.createRenderer(rendererOptions));
}
1
2
3
4
5
2
3
4
5
# patchProp
patchProp
主要作用是根据虚拟 DOM 的变化来更新实际的 DOM 元素的属性,它负责处理添加、删除和更新 DOM 元素的属性,实现 Vue 组件的属性动态更新。
patchProp
的源码实现如下:
const patchProp = (el, key, prevValue, nextValue, namespace, parentComponent) => {
// el:目标DOM元素 key:属性名 preValue:旧值 nextValue:新值 namespace:命名空间 parentComponent:父组件实例
// isSVG表示是否是SVG元素
const isSVG = namespace === 'svg';
if (key === 'class') {
// 处理class属性
patchClass(el, nextValue, isSVG);
} else if (key === "style") {
// 处理style样式属性
patchStyle(key, prevValue, nextValue);
} else if (shared.isOn(key)) {
// 处理事件监听器,isOn判断属性名即事件名称是否以on开头
if (!shared.isModeListener(key)) {
// isModeListener过滤掉onUpdate特殊事件
patchEvent(el, key, prevValue, nextValue, parentComponent);
}
} else if (key[0] === '.' ? (key = key.slice(1), true) :
key[0] === "^" ? (key = key.slice(1), false) :
shouldSetAsProp(el, key, nextValue, isSVG)) {
// 处理DOM属性
patchDOMProp(el, key, nextValue);
if (!el.tagName.includes("-") && (key === "value" || key === "checked" || key === "selected")) {
// 处理特殊属性 value/checked/selected
patchAttr(el, key, nextValue, isSVG, parentComponent, key !== "value")
}
} else if (el._isVueCE && (/[A-Z]/.test(key) || !shared.isstring(nextValue))) {
// 处理Web Components属性
patchDOMProp(el, shared, camelize(key), nextValue, parentComponent, key)
} else {
// 处理其他属性
if (key === 'true-value') {
el._trueValue = nextValue;
} else if (key === 'false-value') {
el._falseValue = nextValue;
}
patchAttr(el, key, nextValue, isSVG)
}
}
1
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
29
30
31
32
33
34
35
36
37
38
39
40
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
29
30
31
32
33
34
35
36
37
38
39
40
# nodeOps
nodeOps
就是一个对象,该对象包含一些操作Node节点的方法。
const svgNS = "http://www.w3.org/2000/svg";
const mathmlNS = "http://www.w3.org/1998/Math/MathML";
const doc = typeof document !== "undefined" ? document : null;
const templateContainer = doc && /* @__PURE__ */ doc.createElement("template");
const nodeOps = {
// 将子节点插入到父节点的指定位置
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null);
},
// 从父节点中移除子节点
remove: (child) => {
const parent = child.parentNode;
if (parent) {
parent.removeChild(child)
}
},
// 创建一个新的 DOM 元素节点
createElement: (tag, namespace, is, props) => {
const el = namespace === 'svg' ? doc.createElementNS(svgNS, tag) : namespace === 'mathml' ? doc.createElementNS(mathmlNS, tag) : is ? doc.createElement(tag, { is }) : doc.createElement(tag);
if (tag === "select" && props && props.multiple != null) {
el.setAttribute("multiple", props.multiple);
}
return el;
},
// 创建一个包含指定文本的文本节点
createText: (text) => doc.createTextNode(text),
// 创建一个注释节点
createComment: () => doc.createComment(text),
// 设置文本节点的文本内容
setText: (node, text) => {
node.nodeValue = text;
},
// 设置元素节点的文本内容
setElementText: (el, text) => { el.textContent = text },
// 获取节点的父节点
parentNode: (node) => node.parentNode,
// 获取节点的下一个兄弟节点
nextSibling: (node) => node.nextSibling,
// 查找匹配选择器的第一个元素
querySelector: (selector) => doc.querySelector(selector),
// 设置元素节点的id属性
setScopedId: (el, id) => {
el.setAttribute(id, "")
},
// 将静态内容插入到指定位置
insertStaticContent(content, parent, anchor, namespace, start, end) {
const before = anchor ? anchor.previousSibling : parent.lastChild;
if (start && (start === end || start.nextSibling)) {
while (true) {
parent.insertBefore(start.cloneNode(true), anchor);
if (start === end || !(start = start.nextSibling)) {
break;
}
}
} else {
templateContainer.innerHTML = unsafeToTrustedHTML(namespace === 'svg' ? `<svg>${content}</svg>` : namespace === "mathml" ? `<math>${content}</math>` : content
);
const template = templateContainer.content;
if (namespace === 'svg' || namespace === 'mathml') {
const wrapper = template.firstChild;
while (wrapper.firstChild) {
template.appendChild(wrapper.firstChild)
}
template.removeChild(wrapper)
}
parent.insertBefore(template, anchor);
}
return [before ? before.nextSibling : parent.firstChild, anchor ? anchor.previousSibling : parent.lastChild]
}
}
1
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
编辑 (opens new window)
上次更新: 2025/09/16, 07:26:19