Appearance
setup和render
演进一: vue组件模拟
有了reactivity后就可以初步使用进行vue组件模拟了
js
import { effect } from "./reactivity/index.js";
const App = {
// template => render
render(context) {
effect(() => {
// reset
document.body.innerText = "";
const div = document.createElement("div");
div.innerText = context.state.count;
// mount root
document.body.append(div);
});
},
setup() {
const state = reactive({ count: 0 });
return { state };
}
};
App.render(App.setup())
import { effect } from "./reactivity/index.js";
const App = {
// template => render
render(context) {
effect(() => {
// reset
document.body.innerText = "";
const div = document.createElement("div");
div.innerText = context.state.count;
// mount root
document.body.append(div);
});
},
setup() {
const state = reactive({ count: 0 });
return { state };
}
};
App.render(App.setup())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这里有很多缺点,render中的视图每次都需要重新创建,另外render和setup的执行细节也不需要让用户知道
演进二: 简化使用
简化使用,让api更合理
js
import { reactive } from "./reactivity/index.js";
export default {
// template => render
render(context) {
const div = document.createElement("div");
div.innerText = context.state.count;
return div;
},
setup() {
const state = reactive({ count: 0 });
return { state };
}
};
import { reactive } from "./reactivity/index.js";
export default {
// template => render
render(context) {
const div = document.createElement("div");
div.innerText = context.state.count;
return div;
},
setup() {
const state = reactive({ count: 0 });
return { state };
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
js
import { createApp } from "./runtime-core/index.js";
import App from "./app.js";
createApp(App).mount(document.querySelector("#app"));
import { createApp } from "./runtime-core/index.js";
import App from "./app.js";
createApp(App).mount(document.querySelector("#app"));
1
2
3
4
2
3
4
html
<body>
<div id="app"></div>
<script src="./index.js" type="module"></script>
</body>
<body>
<div id="app"></div>
<script src="./index.js" type="module"></script>
</body>
1
2
3
4
2
3
4
这里的dom都是直接在render中创建的,一旦需求稍微复杂,这实际使用时就不方便了,所以需要引入虚拟dom(vdom,中间层,分解聚合的思想),相当于图纸或模型;同时这在后面能引入diff算法提高程序性能打下基础。
演进三: 虚拟dom
引入虚拟dom,完善render函数
js
import { reactive } from "./reactivity/index.js";
import { h } from "./runtime-core/index.js";
export default {
// template => render
render(context) {
return h("div", { id: "app-id", class: "app-class" }, `${context.state.count}`);
},
setup() {
const state = reactive({ count: 0 });
window.state = state;
return { state };
}
};
import { reactive } from "./reactivity/index.js";
import { h } from "./runtime-core/index.js";
export default {
// template => render
render(context) {
return h("div", { id: "app-id", class: "app-class" }, `${context.state.count}`);
},
setup() {
const state = reactive({ count: 0 });
window.state = state;
return { state };
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
js
import { createApp } from "./runtime-core/index.js";
import App from "./app.js";
createApp(App).mount(document.querySelector("#app"));
import { createApp } from "./runtime-core/index.js";
import App from "./app.js";
createApp(App).mount(document.querySelector("#app"));
1
2
3
4
2
3
4
js
export function h(tag, props, children) {
return { tag, props, children };
}
export function h(tag, props, children) {
return { tag, props, children };
}
1
2
3
2
3
js
import { effect } from "./../reactivity/index.js";
import { mountElement } from "./renderer.js";
export function createApp(rootComponet) {
return {
mount(rootContainer) {
const context = rootComponet.setup();
effect(() => {
// reset
rootContainer.innerText = "";
// vdom
const subTree = rootComponet.render(context);
// vdom => mount
mountElement(subTree, rootContainer);
});
}
};
}
import { effect } from "./../reactivity/index.js";
import { mountElement } from "./renderer.js";
export function createApp(rootComponet) {
return {
mount(rootContainer) {
const context = rootComponet.setup();
effect(() => {
// reset
rootContainer.innerText = "";
// vdom
const subTree = rootComponet.render(context);
// vdom => mount
mountElement(subTree, rootContainer);
});
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
js
export function mountElement(vnode, container) {
const { tag, props, children } = vnode;
// 1. element
const el = document.createElement(tag);
// 2. props
if (props) {
for (let [key, value] of Object.entries(props)) {
el.setAttribute(key, value);
}
}
// 3. children
if (typeof children === "string") {
el.append(document.createTextNode(children));
} else if (Array.isArray(children)) {
children.forEach(v => mountElement(v, el));
}
// 4. 插入
container.append(el);
}
export function mountElement(vnode, container) {
const { tag, props, children } = vnode;
// 1. element
const el = document.createElement(tag);
// 2. props
if (props) {
for (let [key, value] of Object.entries(props)) {
el.setAttribute(key, value);
}
}
// 3. children
if (typeof children === "string") {
el.append(document.createTextNode(children));
} else if (Array.isArray(children)) {
children.forEach(v => mountElement(v, el));
}
// 4. 插入
container.append(el);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这种方式在当响应式数据有变化时,会导致整个document树重新create,非常消耗性能,事实上我们只需要修改变动的部分,即diff算法
演进四: diff算法
js
import { reactive } from "./reactivity/index.js";
import { h } from "./runtime-core/index.js";
// 综合例子
// a b (c d e z) f g
// a b (d c y e) f
const prevChildren = [
h("p", { key: "A" }, "A"),
h("p", { key: "B" }, "B"),
h("p", { key: "C" }, "C"),
h("p", { key: "D" }, "D"),
h("p", { key: "E" }, "E"),
h("p", { key: "Z" }, "Z"),
h("p", { key: "F" }, "F"),
h("p", { key: "G" }, "G")
];
const nextChildren = [
h("p", { key: "A" }, "A"),
h("p", { key: "B" }, "B"),
h("p", { key: "D" }, "D"),
h("p", { key: "C" }, "C"),
h("p", { key: "Y" }, "Y"),
h("p", { key: "E" }, "E"),
h("p", { key: "F" }, "F")
];
export default {
// template => render
render(context) {
return h("div", { id: `app-${context.state.count}`, class: "app-class" }, [
h("p", {}, `${context.state.count}`),
h("p", {}, `${context.state.isChange}`),
h(
"div",
{ class: "content" },
context.state.isChange === true ? nextChildren : prevChildren
)
]);
},
setup() {
const state = reactive({ count: 0, isChange: false });
window.state = state;
return { state };
}
};
import { reactive } from "./reactivity/index.js";
import { h } from "./runtime-core/index.js";
// 综合例子
// a b (c d e z) f g
// a b (d c y e) f
const prevChildren = [
h("p", { key: "A" }, "A"),
h("p", { key: "B" }, "B"),
h("p", { key: "C" }, "C"),
h("p", { key: "D" }, "D"),
h("p", { key: "E" }, "E"),
h("p", { key: "Z" }, "Z"),
h("p", { key: "F" }, "F"),
h("p", { key: "G" }, "G")
];
const nextChildren = [
h("p", { key: "A" }, "A"),
h("p", { key: "B" }, "B"),
h("p", { key: "D" }, "D"),
h("p", { key: "C" }, "C"),
h("p", { key: "Y" }, "Y"),
h("p", { key: "E" }, "E"),
h("p", { key: "F" }, "F")
];
export default {
// template => render
render(context) {
return h("div", { id: `app-${context.state.count}`, class: "app-class" }, [
h("p", {}, `${context.state.count}`),
h("p", {}, `${context.state.isChange}`),
h(
"div",
{ class: "content" },
context.state.isChange === true ? nextChildren : prevChildren
)
]);
},
setup() {
const state = reactive({ count: 0, isChange: false });
window.state = state;
return { state };
}
};
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
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
js
import { createApp } from "./runtime-core/index.js";
import App from "./app.js";
createApp(App).mount(document.querySelector("#app"));
import { createApp } from "./runtime-core/index.js";
import App from "./app.js";
createApp(App).mount(document.querySelector("#app"));
1
2
3
4
2
3
4
js
import { effect } from "./../reactivity/index.js";
import { mountElement, diff } from "./renderer.js";
export function createApp(rootComponet) {
return {
mount(rootContainer) {
const context = rootComponet.setup();
let isMounted = false;
let prevSubTree;
effect(() => {
// vdom
const subTree = rootComponet.render(context);
if (!isMounted) {
isMounted = true;
// reset
rootContainer.innerText = "";
// vdom => mount
mountElement(subTree, rootContainer);
prevSubTree = subTree;
} else {
diff(prevSubTree, subTree);
prevSubTree = subTree;
}
});
}
};
}
import { effect } from "./../reactivity/index.js";
import { mountElement, diff } from "./renderer.js";
export function createApp(rootComponet) {
return {
mount(rootContainer) {
const context = rootComponet.setup();
let isMounted = false;
let prevSubTree;
effect(() => {
// vdom
const subTree = rootComponet.render(context);
if (!isMounted) {
isMounted = true;
// reset
rootContainer.innerText = "";
// vdom => mount
mountElement(subTree, rootContainer);
prevSubTree = subTree;
} else {
diff(prevSubTree, subTree);
prevSubTree = subTree;
}
});
}
};
}
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
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
js
export function mountElement(vnode, container) {
const { tag, props, children } = vnode;
// 1. element
const el = (vnode.el = document.createElement(tag));
// 2. props
if (props) {
for (let [key, value] of Object.entries(props)) {
el.setAttribute(key, value);
}
}
// 3. children
if (typeof children === "string") {
el.append(document.createTextNode(children));
} else if (Array.isArray(children)) {
children.forEach(v => mountElement(v, el));
}
// 4. 插入
container.append(el);
}
/**
*
* @param {Vnode} n1 oldVnode 老节点
* @param {Vnode} n2 newVnode 新节点
*/
export function diff(n1, n2) {
const { tag: oldTag, props: oldProps, el, children: oldChildren } = n1;
const { tag: newTag, props: newProps, children: newChildren } = n2;
n2.el = el;
// 1. tag
if (oldTag !== newTag) {
el.replaceWith(document.createElement(n2.tag));
} else {
// 2. props
// 2.1 新增和修改
if (newProps && oldProps) {
for (const [key, newVal] of Object.entries(newProps)) {
if (newVal !== oldProps[key]) {
el.setAttribute(key, newVal);
}
}
}
// 2.2 删除
if (oldProps) {
for (const key of Object.keys(oldProps)) {
if (!newProps[key]) {
el.removeAttribute(key);
}
}
}
// 3. children -> 暴力解法
// 3.1 newChildren -> string (oldChildren: string, oldChildren: array)
// 3.2 newChildren -> array (oldChildren: string, oldChildren: array)
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
el.textContent = newChildren;
}
} else if (Array.isArray(oldChildren)) {
el.textContent = newChildren;
}
} else if (Array.isArray(newChildren)) {
if (typeof oldChildren === "string") {
el.innerText = "";
mountElement(n2, el);
} else if (Array.isArray(oldChildren)) {
// new {a, b, c}
// old {a, d, c, e}
const oldChildrenLen = oldChildren.length;
const newChildrenLen = newChildren.length;
const minLen = Math.min(oldChildrenLen, newChildrenLen);
// 处理公共的vnode
for (let index = 0; index < minLen; index++) {
diff(oldChildren[index], newChildren[index]);
}
if (newChildrenLen > minLen) {
// 新增
for (let index = minLen; index < newChildrenLen; index++) {
mountElement(newChildren[index], el);
}
}
if (oldChildrenLen > minLen) {
// 删除
for (let index = minLen; index < oldChildrenLen; index++) {
const oldVnodeEl = oldChildren[index].el;
oldVnodeEl.parentNode.removeChild(oldVnodeEl);
}
}
}
}
}
}
export function mountElement(vnode, container) {
const { tag, props, children } = vnode;
// 1. element
const el = (vnode.el = document.createElement(tag));
// 2. props
if (props) {
for (let [key, value] of Object.entries(props)) {
el.setAttribute(key, value);
}
}
// 3. children
if (typeof children === "string") {
el.append(document.createTextNode(children));
} else if (Array.isArray(children)) {
children.forEach(v => mountElement(v, el));
}
// 4. 插入
container.append(el);
}
/**
*
* @param {Vnode} n1 oldVnode 老节点
* @param {Vnode} n2 newVnode 新节点
*/
export function diff(n1, n2) {
const { tag: oldTag, props: oldProps, el, children: oldChildren } = n1;
const { tag: newTag, props: newProps, children: newChildren } = n2;
n2.el = el;
// 1. tag
if (oldTag !== newTag) {
el.replaceWith(document.createElement(n2.tag));
} else {
// 2. props
// 2.1 新增和修改
if (newProps && oldProps) {
for (const [key, newVal] of Object.entries(newProps)) {
if (newVal !== oldProps[key]) {
el.setAttribute(key, newVal);
}
}
}
// 2.2 删除
if (oldProps) {
for (const key of Object.keys(oldProps)) {
if (!newProps[key]) {
el.removeAttribute(key);
}
}
}
// 3. children -> 暴力解法
// 3.1 newChildren -> string (oldChildren: string, oldChildren: array)
// 3.2 newChildren -> array (oldChildren: string, oldChildren: array)
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
el.textContent = newChildren;
}
} else if (Array.isArray(oldChildren)) {
el.textContent = newChildren;
}
} else if (Array.isArray(newChildren)) {
if (typeof oldChildren === "string") {
el.innerText = "";
mountElement(n2, el);
} else if (Array.isArray(oldChildren)) {
// new {a, b, c}
// old {a, d, c, e}
const oldChildrenLen = oldChildren.length;
const newChildrenLen = newChildren.length;
const minLen = Math.min(oldChildrenLen, newChildrenLen);
// 处理公共的vnode
for (let index = 0; index < minLen; index++) {
diff(oldChildren[index], newChildren[index]);
}
if (newChildrenLen > minLen) {
// 新增
for (let index = minLen; index < newChildrenLen; index++) {
mountElement(newChildren[index], el);
}
}
if (oldChildrenLen > minLen) {
// 删除
for (let index = minLen; index < oldChildrenLen; index++) {
const oldVnodeEl = oldChildren[index].el;
oldVnodeEl.parentNode.removeChild(oldVnodeEl);
}
}
}
}
}
}
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94