├── .gitignore
├── .vscode
└── launch.json
├── README.md
├── babel.config.js
├── examples
├── apiInject
│ ├── App.js
│ └── index.html
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── helloworld
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── index.html
│ └── main.js
├── lib
├── guide-mini-vue.cjs.js
└── guide-mini-vue.esm.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── index.ts
├── reactivity
│ ├── baseHandler.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── renderer.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── ShapeFlags.ts
│ └── index.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "name": "vscode-jest-tests",
10 | "request": "launch",
11 | "args": [
12 | "--runInBand",
13 | "--watchAll=false"
14 | ],
15 | "cwd": "${workspaceFolder}",
16 | "console": "integratedTerminal",
17 | "internalConsoleOptions": "neverOpen",
18 | "disableOptimisticBPs": true,
19 | "program": "${workspaceFolder}/node_modules/.bin/jest",
20 | "windows": {
21 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
22 | }
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 响应式原理
2 |
3 | 不管是 vue2 还是 vue3,都还存在着响应式原理。虽然功能还是这个功能,但是其实现的方式却是不一样了。
4 |
5 | ### 什么是响应式原理
6 |
7 | 响应式原理本质就是观察者模式(定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新)的实现。对应到 vue 中,被观察的对象就是响应式对象,观察者就是(渲染 watcher,用户 watcher,computed watcher)。
8 |
9 | ### 实现响应式原理的思路
10 |
11 | 将我们的响应式对象做层代理,当响应式对象进行读操作时,对当前活动的观察者(很重要)进行依赖收集。当响应式对象进行写操作时,会对收集的观察者进行派发更新
12 |
13 | ### 本项目中 reactivity 模块
14 |
15 | - src/reactivity/\*(功能实现)
16 | - src/reactivity/tests(测试用例)
17 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript",
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/examples/apiInject/App.js:
--------------------------------------------------------------------------------
1 | // 组件 provide 和 inject 功能
2 | import { h, provide, inject } from "../../lib/guide-mini-vue.esm.js";
3 |
4 | const Provider = {
5 | name: "Provider",
6 | setup() {
7 | provide("foo", "fooVal");
8 | provide("bar", "barVal");
9 | },
10 | render() {
11 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]);
12 | },
13 | };
14 |
15 | const ProviderTwo = {
16 | name: "ProviderTwo",
17 | setup() {
18 | provide("foo", "fooTwo");
19 | const foo = inject("foo");
20 |
21 | return {
22 | foo,
23 | };
24 | },
25 | render() {
26 | return h("div", {}, [
27 | h("p", {}, `ProviderTwo foo:${this.foo}`),
28 | h(Consumer),
29 | ]);
30 | },
31 | };
32 |
33 | const Consumer = {
34 | name: "Consumer",
35 | setup() {
36 | const foo = inject("foo");
37 | const bar = inject("bar");
38 | // const baz = inject("baz", "bazDefault");
39 | const baz = inject("baz", () => "bazDefault");
40 |
41 | return {
42 | foo,
43 | bar,
44 | baz,
45 | };
46 | },
47 |
48 | render() {
49 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`);
50 | },
51 | };
52 |
53 | export default {
54 | name: "App",
55 | setup() {},
56 | render() {
57 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
58 | },
59 | };
60 |
--------------------------------------------------------------------------------
/examples/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | // emit
8 | return h("div", {}, [
9 | h("div", {}, "App"),
10 | h(Foo, {
11 | onAdd(a, b) {
12 | console.log("onAdd", a, b);
13 | },
14 | onAddFoo() {
15 | console.log("onAddFoo");
16 | },
17 | }),
18 | ]);
19 | },
20 |
21 | setup() {
22 | return {};
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/examples/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | setup(props, { emit }) {
5 | const emitAdd = () => {
6 | console.log("emit add");
7 | emit("add", 1, 2);
8 | emit("add-foo");
9 | };
10 |
11 | return {
12 | emitAdd,
13 | };
14 | },
15 | render() {
16 | const btn = h(
17 | "button",
18 | {
19 | onClick: this.emitAdd,
20 | },
21 | "emitAdd"
22 | );
23 |
24 | const foo = h("p", {}, "foo");
25 | return h("div", {}, [foo, btn]);
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/examples/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/examples/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | // Fragment 以及 Text
5 | export const App = {
6 | name: "App",
7 | render() {
8 | const app = h("div", {}, "App");
9 | // object key
10 | const foo = h(
11 | Foo,
12 | {},
13 | {
14 | header: ({ age }) => [
15 | h("p", {}, "header" + age),
16 | createTextVNode("你好呀"),
17 | ],
18 | footer: () => h("p", {}, "footer"),
19 | }
20 | );
21 | // 数组 vnode
22 | // const foo = h(Foo, {}, h("p", {}, "123"));
23 | return h("div", {}, [app, foo]);
24 | },
25 |
26 | setup() {
27 | return {};
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/examples/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | setup() {
5 | return {};
6 | },
7 | render() {
8 | const foo = h("p", {}, "foo");
9 |
10 | // Foo .vnode. children
11 | console.log(this.$slots);
12 | // children -> vnode
13 | //
14 | // renderSlots
15 | // 具名插槽
16 | // 1. 获取到要渲染的元素 1
17 | // 2. 要获取到渲染的位置
18 | // 作用域插槽
19 | const age = 18;
20 | return h("div", {}, [
21 | renderSlots(this.$slots, "header", {
22 | age,
23 | }),
24 | foo,
25 | renderSlots(this.$slots, "footer"),
26 | ]);
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/examples/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/examples/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]);
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance();
12 | console.log("App:", instance);
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/examples/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name:"Foo",
5 | setup() {
6 | const instance = getCurrentInstance();
7 | console.log("Foo:", instance);
8 | return {};
9 | },
10 | render() {
11 | return h("div", {}, "foo");
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/examples/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/examples/helloworld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | // 必须要写 render
7 | name: "App",
8 | render() {
9 | window.self = this;
10 | // ui
11 | return h(
12 | "div",
13 | {
14 | id: "root",
15 | class: ["red", "hard"],
16 | onClick() {
17 | console.log("click");
18 | },
19 | onMousedown() {
20 | console.log("mousedown");
21 | },
22 | },
23 | [
24 | h("div", {}, "hi," + this.msg),
25 | h(Foo, {
26 | count: 1,
27 | }),
28 | ]
29 | // "hi, " + this.msg
30 | // string
31 | // "hi, mini-vue"
32 | // Array
33 | // [h("p", { class:"red"}, "hi"), h("p", {class:"blue"}, "mini-vue")]
34 | );
35 | },
36 |
37 | setup() {
38 | return {
39 | msg: "mini-vue-haha",
40 | };
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/examples/helloworld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | setup(props) {
5 | // props.count
6 | console.log(props);
7 |
8 | // 3.
9 | // shallow readonly
10 | props.count++;
11 | console.log(props);
12 | },
13 | render() {
14 | return h("div", {}, "foo: " + this.count);
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/examples/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/helloworld/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/examples/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | import ArrayToText from "./ArrayToText.js";
4 | import TextToText from "./TextToText.js";
5 | import TextToArray from "./TextToArray.js";
6 | import ArrayToArray from "./ArrayToArray.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {},
11 |
12 | render() {
13 | return h("div", { tId: 1 }, [
14 | h("p", {}, "主页"),
15 | // 老的是 array 新的是 text
16 | // h(ArrayToText),
17 | // 老的是 text 新的是 text
18 | // h(TextToText),
19 | // 老的是 text 新的是 array
20 | // h(TextToArray),
21 | // 老的是 array 新的是 array
22 | h(ArrayToArray),
23 | ]);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/examples/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "B" }, "B"),
12 | // h("p", { key: "C" }, "C"),
13 | // ];
14 | // const nextChildren = [
15 | // h("p", { key: "A" }, "A"),
16 | // h("p", { key: "B" }, "B"),
17 | // h("p", { key: "D" }, "D"),
18 | // h("p", { key: "E" }, "E"),
19 | // ];
20 |
21 | // 2. 右侧的对比
22 | // a (b c)
23 | // d e (b c)
24 | // const prevChildren = [
25 | // h("p", { key: "A" }, "A"),
26 | // h("p", { key: "B" }, "B"),
27 | // h("p", { key: "C" }, "C"),
28 | // ];
29 | // const nextChildren = [
30 | // h("p", { key: "D" }, "D"),
31 | // h("p", { key: "E" }, "E"),
32 | // h("p", { key: "B" }, "B"),
33 | // h("p", { key: "C" }, "C"),
34 | // ];
35 |
36 | // 3. 新的比老的长
37 | // 创建新的
38 | // 左侧
39 | // (a b)
40 | // (a b) c
41 | // i = 2, e1 = 1, e2 = 2
42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
43 | // const nextChildren = [
44 | // h("p", { key: "A" }, "A"),
45 | // h("p", { key: "B" }, "B"),
46 | // h("p", { key: "C" }, "C"),
47 | // h("p", { key: "D" }, "D"),
48 | // ];
49 |
50 | // 右侧
51 | // (a b)
52 | // c (a b)
53 | // i = 0, e1 = -1, e2 = 0
54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
55 | // const nextChildren = [
56 | // h("p", { key: "C" }, "C"),
57 | // h("p", { key: "A" }, "A"),
58 | // h("p", { key: "B" }, "B"),
59 | // ];
60 |
61 | // 4. 老的比新的长
62 | // 删除老的
63 | // 左侧
64 | // (a b) c
65 | // (a b)
66 | // i = 2, e1 = 2, e2 = 1
67 | // const prevChildren = [
68 | // h("p", { key: "A" }, "A"),
69 | // h("p", { key: "B" }, "B"),
70 | // h("p", { key: "C" }, "C"),
71 | // ];
72 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
73 |
74 | // 右侧
75 | // a (b c)
76 | // (b c)
77 | // i = 0, e1 = 0, e2 = -1
78 |
79 | // const prevChildren = [
80 | // h("p", { key: "A" }, "A"),
81 | // h("p", { key: "B" }, "B"),
82 | // h("p", { key: "C" }, "C"),
83 | // ];
84 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
85 |
86 | // 5. 对比中间的部分
87 | // 删除老的 (在老的里面存在,新的里面不存在)
88 | // 5.1
89 | // a,b,(c,d),f,g
90 | // a,b,(e,c),f,g
91 | // D 节点在新的里面是没有的 - 需要删除掉
92 | // C 节点 props 也发生了变化
93 |
94 | // const prevChildren = [
95 | // h("p", { key: "A" }, "A"),
96 | // h("p", { key: "B" }, "B"),
97 | // h("p", { key: "C", id: "c-prev" }, "C"),
98 | // h("p", { key: "D" }, "D"),
99 | // h("p", { key: "F" }, "F"),
100 | // h("p", { key: "G" }, "G"),
101 | // ];
102 |
103 | // const nextChildren = [
104 | // h("p", { key: "A" }, "A"),
105 | // h("p", { key: "B" }, "B"),
106 | // h("p", { key: "E" }, "E"),
107 | // h("p", { key: "C", id:"c-next" }, "C"),
108 | // h("p", { key: "F" }, "F"),
109 | // h("p", { key: "G" }, "G"),
110 | // ];
111 |
112 | // 5.1.1
113 | // a,b,(c,e,d),f,g
114 | // a,b,(e,c),f,g
115 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
116 | // const prevChildren = [
117 | // h("p", { key: "A" }, "A"),
118 | // h("p", { key: "B" }, "B"),
119 | // h("p", { key: "C", id: "c-prev" }, "C"),
120 | // h("p", { key: "E" }, "E"),
121 | // h("p", { key: "D" }, "D"),
122 | // h("p", { key: "F" }, "F"),
123 | // h("p", { key: "G" }, "G"),
124 | // ];
125 |
126 | // const nextChildren = [
127 | // h("p", { key: "A" }, "A"),
128 | // h("p", { key: "B" }, "B"),
129 | // h("p", { key: "E" }, "E"),
130 | // h("p", { key: "C", id:"c-next" }, "C"),
131 | // h("p", { key: "F" }, "F"),
132 | // h("p", { key: "G" }, "G"),
133 | // ];
134 |
135 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
136 |
137 | // 2.1
138 | // a,b,(c,d,e),f,g
139 | // a,b,(e,c,d),f,g
140 | // 最长子序列: [1,2]
141 |
142 | // const prevChildren = [
143 | // h("p", { key: "A" }, "A"),
144 | // h("p", { key: "B" }, "B"),
145 | // h("p", { key: "C" }, "C"),
146 | // h("p", { key: "D" }, "D"),
147 | // h("p", { key: "E" }, "E"),
148 | // h("p", { key: "F" }, "F"),
149 | // h("p", { key: "G" }, "G"),
150 | // ];
151 |
152 | // const nextChildren = [
153 | // h("p", { key: "A" }, "A"),
154 | // h("p", { key: "B" }, "B"),
155 | // h("p", { key: "E" }, "E"),
156 | // h("p", { key: "C" }, "C"),
157 | // h("p", { key: "D" }, "D"),
158 | // h("p", { key: "F" }, "F"),
159 | // h("p", { key: "G" }, "G"),
160 | // ];
161 |
162 | // 3. 创建新的节点
163 | // a,b,(c,e),f,g
164 | // a,b,(e,c,d),f,g
165 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
166 | // const prevChildren = [
167 | // h("p", { key: "A" }, "A"),
168 | // h("p", { key: "B" }, "B"),
169 | // h("p", { key: "C" }, "C"),
170 | // h("p", { key: "E" }, "E"),
171 | // h("p", { key: "F" }, "F"),
172 | // h("p", { key: "G" }, "G"),
173 | // ];
174 |
175 | // const nextChildren = [
176 | // h("p", { key: "A" }, "A"),
177 | // h("p", { key: "B" }, "B"),
178 | // h("p", { key: "E" }, "E"),
179 | // h("p", { key: "C" }, "C"),
180 | // h("p", { key: "D" }, "D"),
181 | // h("p", { key: "F" }, "F"),
182 | // h("p", { key: "G" }, "G"),
183 | // ];
184 |
185 | // 综合例子
186 | // a,b,(c,d,e,z),f,g
187 | // a,b,(d,c,y,e),f,g
188 | const prevChildren = [
189 | h("p", { key: "A" }, "A"),
190 | h("p", { key: "B" }, "B"),
191 | h("p", { key: "C" }, "C"),
192 | h("p", { key: "D" }, "D"),
193 | h("p", { key: "E" }, "E"),
194 | h("p", { key: "Z" }, "Z"),
195 | h("p", { key: "F" }, "F"),
196 | h("p", { key: "G" }, "G"),
197 | ];
198 |
199 | const nextChildren = [
200 | h("p", { key: "A" }, "A"),
201 | h("p", { key: "B" }, "B"),
202 | h("p", { key: "D" }, "D"),
203 | h("p", { key: "C" }, "C"),
204 | h("p", { key: "Y" }, "Y"),
205 | h("p", { key: "E" }, "E"),
206 | h("p", { key: "F" }, "F"),
207 | h("p", { key: "G" }, "G"),
208 | ];
209 |
210 | export default {
211 | name: "ArrayToArray",
212 | setup() {
213 | const isChange = ref(false);
214 | window.isChange = isChange;
215 |
216 | return {
217 | isChange,
218 | };
219 | },
220 | render() {
221 | const self = this;
222 |
223 | return self.isChange === true
224 | ? h("div", {}, nextChildren)
225 | : h("div", {}, prevChildren);
226 | },
227 | };
228 |
--------------------------------------------------------------------------------
/examples/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
5 | const nextChildren = "newChildren";
6 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "ArrayToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/examples/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "TextToArray",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/examples/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = "newChild";
7 |
8 | export default {
9 | name: "TextToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/examples/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/examples/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | setup() {
7 | const count = ref(0);
8 |
9 | const onClick = () => {
10 | count.value++;
11 | };
12 |
13 | const props = ref({
14 | foo: "foo",
15 | bar: "bar",
16 | });
17 | const onChangePropsDemo1 = () => {
18 | props.value.foo = "new-foo";
19 | };
20 |
21 | const onChangePropsDemo2 = () => {
22 | props.value.foo = undefined;
23 | };
24 |
25 | const onChangePropsDemo3 = () => {
26 | props.value = {
27 | foo: "foo",
28 | };
29 | };
30 |
31 | return {
32 | count,
33 | onClick,
34 | onChangePropsDemo1,
35 | onChangePropsDemo2,
36 | onChangePropsDemo3,
37 | props,
38 | };
39 | },
40 | render() {
41 | return h(
42 | "div",
43 | {
44 | id: "root",
45 | ...this.props,
46 | },
47 | [
48 | h("div", {}, "count:" + this.count),
49 | h(
50 | "button",
51 | {
52 | onClick: this.onClick,
53 | },
54 | "click"
55 | ),
56 | h(
57 | "button",
58 | {
59 | onClick: this.onChangePropsDemo1,
60 | },
61 | "changeProps - 值改变了 - 修改"
62 | ),
63 |
64 | h(
65 | "button",
66 | {
67 | onClick: this.onChangePropsDemo2,
68 | },
69 | "changeProps - 值变成了 undefined - 删除"
70 | ),
71 |
72 | h(
73 | "button",
74 | {
75 | onClick: this.onChangePropsDemo3,
76 | },
77 | "changeProps - key 在新的里面没有了 - 删除"
78 | ),
79 | ]
80 | );
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/examples/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const Fragment = Symbol("Fragment");
6 | const Text = Symbol("Text");
7 | function createVNode(type, props, children) {
8 | const vnode = {
9 | type,
10 | props,
11 | children,
12 | el: null,
13 | shapeFlag: getShapeType(type),
14 | key: props && props.key,
15 | };
16 | if (typeof children === "string") {
17 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
18 | }
19 | else if (Array.isArray(children)) {
20 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
21 | }
22 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
23 | if (typeof children === "object") {
24 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
25 | }
26 | }
27 | return vnode;
28 | }
29 | function createTextVNode(text) {
30 | return createVNode(Text, {}, text);
31 | }
32 | function getShapeType(type) {
33 | return typeof type === "string"
34 | ? 1 /* ELEMENT */
35 | : 2 /* STATEFUL_COMPONENT */;
36 | }
37 |
38 | function h(type, props, children) {
39 | return createVNode(type, props, children);
40 | }
41 |
42 | function renderSlots(slots, name, props) {
43 | let slot = slots[name];
44 | if (slot) {
45 | if (typeof slot === "function") {
46 | return createVNode(Fragment, {}, slot(props));
47 | }
48 | }
49 | }
50 |
51 | const extend = Object.assign;
52 | const isObject = (value) => {
53 | return value !== null && typeof value === "object";
54 | };
55 | const hasChanged = (val, newValue) => {
56 | return !Object.is(val, newValue);
57 | };
58 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
59 | const camelize = (str) => {
60 | return str.replace(/-(\w)/g, (_, c) => {
61 | return c ? c.toUpperCase() : "";
62 | });
63 | };
64 | const capitalize = (str) => {
65 | return str.charAt(0).toUpperCase() + str.slice(1);
66 | };
67 | const toHandlerKey = (str) => {
68 | return str ? "on" + capitalize(str) : "";
69 | };
70 |
71 | const publicPropertiesMap = {
72 | $el: (i) => i.vnode.el,
73 | $slots: (i) => i.slots,
74 | };
75 | const PublicInstanceProxyHandlers = {
76 | get({ _: instance }, key) {
77 | // setupState
78 | const { setupState, props } = instance;
79 | if (hasOwn(setupState, key)) {
80 | return setupState[key];
81 | }
82 | else if (hasOwn(props, key)) {
83 | return props[key];
84 | }
85 | const publicGetter = publicPropertiesMap[key];
86 | if (publicGetter) {
87 | return publicGetter(instance);
88 | }
89 | },
90 | };
91 |
92 | function initProps(intstance, rawProps) {
93 | intstance.props = rawProps || {};
94 | }
95 |
96 | function initSlots(instance, children) {
97 | const { vnode } = instance;
98 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
99 | normalizeObjectSlots(children, instance.slots);
100 | }
101 | }
102 | function normalizeObjectSlots(children, slots) {
103 | for (let key in children) {
104 | let value = children[key];
105 | slots[key] = (props) => normalizeSlotValue(value(props));
106 | }
107 | }
108 | function normalizeSlotValue(value) {
109 | return Array.isArray(value) ? value : [value];
110 | }
111 |
112 | let activeEffect;
113 | let shouldTrack = false;
114 | class ReactiveEffect {
115 | constructor(fn, scheduler) {
116 | this.deps = [];
117 | this.active = true;
118 | this._fn = fn;
119 | this.scheduler = scheduler;
120 | }
121 | run() {
122 | if (!this.active) {
123 | return this._fn();
124 | }
125 | // 应该收集
126 | shouldTrack = true;
127 | activeEffect = this;
128 | const r = this._fn();
129 | // 重置
130 | shouldTrack = false;
131 | return r;
132 | }
133 | stop() {
134 | if (this.active) {
135 | cleanupEffect(this);
136 | if (this.onStop) {
137 | this.onStop();
138 | }
139 | this.active = false;
140 | }
141 | }
142 | }
143 | function cleanupEffect(effect) {
144 | effect.deps.forEach((dep) => {
145 | dep.delete(effect);
146 | });
147 | // 把 effect.deps 清空
148 | effect.deps.length = 0;
149 | }
150 | const targetMap = new Map();
151 | function track(target, key) {
152 | if (!isTracking())
153 | return;
154 | // target -> key -> dep
155 | let depsMap = targetMap.get(target);
156 | if (!depsMap) {
157 | depsMap = new Map();
158 | targetMap.set(target, depsMap);
159 | }
160 | let dep = depsMap.get(key);
161 | if (!dep) {
162 | dep = new Set();
163 | depsMap.set(key, dep);
164 | }
165 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
166 | if (dep.has(activeEffect))
167 | return;
168 | trackEffects(dep);
169 | }
170 | function trackEffects(dep) {
171 | dep.add(activeEffect);
172 | activeEffect.deps.push(dep);
173 | }
174 | function isTracking() {
175 | return shouldTrack && activeEffect !== undefined;
176 | }
177 | function trigger(target, key) {
178 | let depsMap = targetMap.get(target);
179 | let dep = depsMap.get(key);
180 | triggerEffects(dep);
181 | }
182 | function triggerEffects(dep) {
183 | for (const effect of dep) {
184 | if (effect.scheduler) {
185 | effect.scheduler();
186 | }
187 | else {
188 | effect.run();
189 | }
190 | }
191 | }
192 | // 返回一个runner函数
193 | function effect(fn, options = {}) {
194 | // fn
195 | const _effect = new ReactiveEffect(fn, options.scheduler);
196 | extend(_effect, options);
197 | _effect.run(); // 依赖收集的入口
198 | const runner = _effect.run.bind(_effect);
199 | runner.effect = _effect;
200 | return runner;
201 | }
202 |
203 | const get = createGetter();
204 | const set = createSetter();
205 | const readonlyGet = createGetter(true);
206 | const shallowReadonlyGet = createGetter(true, true);
207 | function createGetter(isReadonly = false, shallow = false) {
208 | return function get(target, key) {
209 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
210 | return !isReadonly;
211 | }
212 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
213 | return isReadonly;
214 | }
215 | const res = Reflect.get(target, key);
216 | if (shallow) {
217 | return res;
218 | }
219 | if (isObject(res)) {
220 | return isReadonly ? readonly(res) : reactive(res);
221 | }
222 | if (!isReadonly) {
223 | track(target, key);
224 | }
225 | return res;
226 | };
227 | }
228 | function createSetter() {
229 | return function set(target, key, value) {
230 | const res = Reflect.set(target, key, value);
231 | trigger(target, key);
232 | return res;
233 | };
234 | }
235 | const mutableHandlers = {
236 | get,
237 | set,
238 | };
239 | const readonlyHandlers = {
240 | get: readonlyGet,
241 | set(target, key) {
242 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target);
243 | return true;
244 | },
245 | };
246 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
247 | get: shallowReadonlyGet,
248 | });
249 |
250 | function reactive(raw) {
251 | return createReactiveObject(raw, mutableHandlers);
252 | }
253 | function readonly(raw) {
254 | return createReactiveObject(raw, readonlyHandlers);
255 | }
256 | function shallowReadonly(raw) {
257 | return createReactiveObject(raw, shallowReadonlyHandlers);
258 | }
259 | function createReactiveObject(target, baseHandles) {
260 | return new Proxy(target, baseHandles);
261 | }
262 |
263 | function emit(instance, event, ...args) {
264 | const { props } = instance;
265 | const handlerName = toHandlerKey(camelize(event));
266 | const handler = props[handlerName];
267 | handler && handler(...args);
268 | }
269 |
270 | class RefImpl {
271 | constructor(value) {
272 | this._v_isRef = true;
273 | this._rawValue = value; // 保存原始值
274 | this._value = convert(value); // 如果是对象,则转化成响应式对象
275 | this.dep = new Set();
276 | }
277 | get value() {
278 | trackRefValue(this);
279 | return this._value;
280 | }
281 | set value(newVal) {
282 | // 如果值没有改变,那么没必要重复触发
283 | if (hasChanged(newVal, this._rawValue)) {
284 | this._rawValue = newVal;
285 | this._value = convert(newVal);
286 | triggerEffects(this.dep);
287 | }
288 | }
289 | }
290 | function ref(value) {
291 | return new RefImpl(value);
292 | }
293 | function convert(value) {
294 | return isObject(value) ? reactive(value) : value;
295 | }
296 | function trackRefValue(ref) {
297 | if (isTracking()) {
298 | trackEffects(ref.dep);
299 | }
300 | }
301 | function isRef(ref) {
302 | return !!ref._v_isRef;
303 | }
304 | function unRef(ref) {
305 | return isRef(ref) ? ref.value : ref;
306 | }
307 | function proxyRefs(objectWithRefs) {
308 | return new Proxy(objectWithRefs, {
309 | get(target, key) {
310 | return unRef(Reflect.get(target, key));
311 | },
312 | set(target, key, value) {
313 | if (isRef(target[key]) && !isRef(value)) {
314 | return (target[key].value = value);
315 | }
316 | else {
317 | return Reflect.set(target, key, value);
318 | }
319 | },
320 | });
321 | }
322 |
323 | function createComponentInstance(vnode, parent) {
324 | const component = {
325 | vnode,
326 | type: vnode.type,
327 | setupState: {},
328 | props: {},
329 | slots: {},
330 | provides: parent ? parent.provides : {},
331 | parent,
332 | isMounted: false,
333 | emit: () => { },
334 | };
335 | component.emit = emit.bind(null, component);
336 | return component;
337 | }
338 | function setupComponent(instance) {
339 | // TODO
340 | initProps(instance, instance.vnode.props);
341 | initSlots(instance, instance.vnode.children);
342 | setupStatefulComponent(instance);
343 | }
344 | function setupStatefulComponent(instance) {
345 | const Component = instance.type;
346 | const { setup } = Component;
347 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
348 | if (setup) {
349 | setCurrentInstance(instance);
350 | const setupResult = setup(shallowReadonly(instance.props), {
351 | emit: instance.emit,
352 | });
353 | setCurrentInstance(null);
354 | handleSetupResult(instance, setupResult);
355 | }
356 | }
357 | function handleSetupResult(instance, setupResult) {
358 | if (typeof setupResult === "object") {
359 | instance.setupState = proxyRefs(setupResult);
360 | }
361 | finishComponentSetup(instance);
362 | }
363 | // 最终肯定是要返回一个render函数
364 | function finishComponentSetup(instance) {
365 | const Component = instance.type;
366 | if (Component.render) {
367 | instance.render = Component.render;
368 | }
369 | }
370 | let currentInstance = null;
371 | function getCurrentInstance() {
372 | return currentInstance;
373 | }
374 | function setCurrentInstance(instance) {
375 | currentInstance = instance;
376 | }
377 |
378 | function provide(key, value) {
379 | const currentInstance = getCurrentInstance();
380 | if (currentInstance) {
381 | let { provides } = currentInstance;
382 | const parentProvides = currentInstance.parent.provides;
383 | if (parentProvides === provides) {
384 | provides = currentInstance.provides = Object.create(parentProvides);
385 | }
386 | provides[key] = value;
387 | }
388 | }
389 | function inject(key, defaultValue) {
390 | const currentInstance = getCurrentInstance();
391 | if (currentInstance) {
392 | const parentProvides = currentInstance.parent.provides;
393 | if (key in parentProvides) {
394 | return parentProvides[key];
395 | }
396 | else if (defaultValue) {
397 | if (typeof defaultValue === "function") {
398 | return defaultValue();
399 | }
400 | return defaultValue;
401 | }
402 | }
403 | }
404 |
405 | function createAppAPI(render) {
406 | return function createApp(rootComponent) {
407 | return {
408 | mount(rootContainer) {
409 | const vnode = createVNode(rootComponent);
410 | render(vnode, rootContainer);
411 | },
412 | };
413 | };
414 | }
415 |
416 | function createRenderer(options) {
417 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
418 | function render(vnode, container) {
419 | patch(null, vnode, container, null, null);
420 | }
421 | function patch(n1, n2, container, parentComponent, anchor) {
422 | const { type, shapeFlag } = n2;
423 | switch (type) {
424 | case Fragment:
425 | processFragment(n1, n2, container, parentComponent, anchor);
426 | break;
427 | case Text:
428 | processText(n1, n2, container);
429 | break;
430 | default:
431 | if (shapeFlag & 1 /* ELEMENT */) {
432 | processElement(n1, n2, container, parentComponent, anchor);
433 | }
434 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
435 | processComponent(n1, n2, container, parentComponent, anchor);
436 | }
437 | break;
438 | }
439 | }
440 | function processText(n1, n2, container) {
441 | const { children } = n2;
442 | const textVNode = (n2.el = document.createTextNode(children));
443 | container.append(textVNode);
444 | }
445 | function processFragment(n1, n2, container, parentComponent, anchor) {
446 | mountChildren(n2.children, container, parentComponent, anchor);
447 | }
448 | function processElement(n1, n2, container, parentComponent, anchor) {
449 | if (!n1) {
450 | mountElement(n2, container, parentComponent, anchor);
451 | }
452 | else {
453 | patchElement(n1, n2, container, parentComponent, anchor);
454 | }
455 | }
456 | function patchElement(n1, n2, container, parentComponent, anchor) {
457 | console.log("patchElement");
458 | console.log("n1", n1);
459 | console.log("n2", n2);
460 | const oldProps = n1.props;
461 | const newProps = n2.props;
462 | const el = (n2.el = n1.el);
463 | patchChildren(n1, n2, el, parentComponent, anchor);
464 | patchProps(el, oldProps, newProps);
465 | }
466 | function patchChildren(n1, n2, container, parentComponent, anchor) {
467 | const prevShapeFlag = n1.shapeFlag;
468 | const shapeFlag = n2.shapeFlag;
469 | const c1 = n1.children;
470 | const c2 = n2.children;
471 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
472 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
473 | unmountChildren(c1);
474 | }
475 | if (c1 !== c2) {
476 | hostSetElementText(container, c2);
477 | }
478 | }
479 | else {
480 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
481 | hostSetElementText(container, "");
482 | mountChildren(c2, container, parentComponent, anchor);
483 | }
484 | else {
485 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
486 | }
487 | }
488 | }
489 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
490 | const l2 = c2.length;
491 | let i = 0;
492 | let e1 = c1.length - 1;
493 | let e2 = l2 - 1;
494 | function isSomeVNodeType(n1, n2) {
495 | return n1.type === n2.type && n1.key === n2.key;
496 | }
497 | // 左侧进行对比相同节点
498 | while (i <= e1 && i <= e2) {
499 | let n1 = c1[i];
500 | let n2 = c2[i];
501 | if (isSomeVNodeType(n1, n2)) {
502 | patch(n1, n2, container, parentComponent, parentAnchor);
503 | }
504 | else {
505 | break;
506 | }
507 | i++;
508 | }
509 | // 右侧进行对比相同节点
510 | while (i <= e1 && i <= e2) {
511 | let n1 = c1[e1];
512 | let n2 = c2[e2];
513 | if (isSomeVNodeType(n1, n2)) {
514 | patch(n1, n2, container, parentComponent, parentAnchor);
515 | }
516 | else {
517 | break;
518 | }
519 | e1--;
520 | e2--;
521 | }
522 | if (i > e1) {
523 | if (i <= e2) {
524 | // 旧节点结束,新节点没有结束的情况
525 | const nextPos = e2 + 1;
526 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
527 | while (i <= e2) {
528 | patch(null, c2[i], container, parentComponent, anchor);
529 | i++;
530 | }
531 | }
532 | }
533 | else if (i > e2) {
534 | // 新节点结束,旧节点未结束的情况
535 | while (i <= e1) {
536 | hostRemove(c1[i].el);
537 | i++;
538 | }
539 | }
540 | else {
541 | // 中间对比
542 | let s1 = i;
543 | let s2 = i;
544 | let patched = 0;
545 | let moved = false;
546 | let maxNewIndexSoFar = 0;
547 | const toBePatched = e2 - s2 + 1;
548 | const keyToNewIndexMap = new Map();
549 | const newIndexToOldIndexMap = new Array(toBePatched);
550 | for (let i = 0; i < toBePatched; i++)
551 | newIndexToOldIndexMap[i] = 0;
552 | // 建立旧节点key映射到新节点index的hash表
553 | for (let i = s2; i <= e2; i++) {
554 | const nextChild = c2[i];
555 | keyToNewIndexMap.set(nextChild.key, i);
556 | }
557 | for (let i = s1; i <= e1; i++) {
558 | const prevChild = c1[i];
559 | if (patched >= toBePatched) {
560 | hostRemove(prevChild.el);
561 | continue;
562 | }
563 | // 哈希表中找是否存在的值
564 | let newIndex;
565 | if (prevChild.key != null) {
566 | newIndex = keyToNewIndexMap.get(prevChild.key);
567 | }
568 | else {
569 | for (let j = s2; j < e2; j++) {
570 | if (isSomeVNodeType(prevChild, s2[j])) {
571 | newIndex = j;
572 | break;
573 | }
574 | }
575 | }
576 | // 如果哈希表中没有找到,则进行删除操作
577 | if (newIndex == null) {
578 | hostRemove(prevChild.el);
579 | }
580 | else {
581 | // 如果在哈希表中找到
582 | if (newIndex >= maxNewIndexSoFar) {
583 | maxNewIndexSoFar = newIndex;
584 | }
585 | else {
586 | moved = true;
587 | }
588 | // 建立新节点index(这个index只针对中间的节点序列)到旧节点index的映射关系
589 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
590 | patch(prevChild, c2[newIndex], container, parentComponent, null);
591 | patched++;
592 | }
593 | }
594 | const increasingNewIndexSequence = moved
595 | ? getSequence(newIndexToOldIndexMap)
596 | : [];
597 | let j = increasingNewIndexSequence.length - 1; // 生成一个最长的递增子序列
598 | for (let i = toBePatched - 1; i >= 0; i--) {
599 | const nextIndex = i + s2;
600 | const nextChild = c2[nextIndex];
601 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
602 | if (newIndexToOldIndexMap[i] === 0) {
603 | // 新节点没有在旧节点中找到,给了个标识,直接在这里进行插入
604 | patch(null, nextChild, container, parentComponent, anchor);
605 | }
606 | else if (moved) {
607 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
608 | // 这时对顺序进行调整
609 | hostInsert(nextChild.el, container, anchor);
610 | }
611 | else {
612 | j--;
613 | }
614 | }
615 | }
616 | }
617 | }
618 | function unmountChildren(children) {
619 | for (let i = 0; i < children.length; i++) {
620 | const el = children[i].el;
621 | hostRemove(el);
622 | }
623 | }
624 | function patchProps(el, oldProps, newProps) {
625 | if (oldProps !== newProps) {
626 | for (const key in newProps) {
627 | const prevProp = oldProps[key];
628 | const nextProp = newProps[key];
629 | if (prevProp !== nextProp) {
630 | hostPatchProp(el, key, prevProp, nextProp);
631 | }
632 | }
633 | if (JSON.stringify(oldProps) !== "{}") {
634 | for (const key in oldProps) {
635 | if (!(key in newProps)) {
636 | hostPatchProp(el, key, oldProps[key], null);
637 | }
638 | }
639 | }
640 | }
641 | }
642 | function processComponent(n1, n2, container, parentComponent, anchor) {
643 | mountComponent(n2, container, parentComponent, anchor);
644 | }
645 | function mountElement(vnode, container, parentComponent, anchor) {
646 | // createElement
647 | const el = (vnode.el = hostCreateElement(vnode.type));
648 | const { children, shapeFlag } = vnode;
649 | // children
650 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
651 | el.textContent = children;
652 | }
653 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
654 | mountChildren(vnode.children, el, parentComponent, anchor);
655 | }
656 | // props
657 | const { props } = vnode;
658 | for (const key in props) {
659 | hostPatchProp(el, key, null, props[key]);
660 | }
661 | hostInsert(el, container, anchor);
662 | }
663 | function mountChildren(children, container, parentComponent, anchor) {
664 | children.forEach((v) => {
665 | patch(null, v, container, parentComponent, anchor);
666 | });
667 | }
668 | function mountComponent(initialVNode, container, parentComponent, anchor) {
669 | const instance = createComponentInstance(initialVNode, parentComponent);
670 | setupComponent(instance);
671 | setupRenderEffect(instance, initialVNode, container, anchor);
672 | }
673 | function setupRenderEffect(instance, initialVNode, container, anchor) {
674 | effect(() => {
675 | if (!instance.isMounted) {
676 | console.log("init");
677 | const { proxy } = instance;
678 | const subTree = (instance.subTree = instance.render.call(proxy));
679 | patch(null, subTree, container, instance, anchor);
680 | initialVNode.el = subTree.el;
681 | instance.isMounted = true;
682 | }
683 | else {
684 | console.log("update");
685 | const { proxy } = instance;
686 | const subTree = instance.render.call(proxy);
687 | const prevSubTree = instance.subTree;
688 | instance.subTree = subTree;
689 | patch(prevSubTree, subTree, container, instance, anchor);
690 | }
691 | });
692 | }
693 | return {
694 | createApp: createAppAPI(render),
695 | };
696 | }
697 | function getSequence(arr) {
698 | const p = arr.slice();
699 | const result = [0];
700 | let i, j, u, v, c;
701 | const len = arr.length;
702 | for (i = 0; i < len; i++) {
703 | const arrI = arr[i];
704 | if (arrI !== 0) {
705 | j = result[result.length - 1];
706 | if (arr[j] < arrI) {
707 | p[i] = j;
708 | result.push(i);
709 | continue;
710 | }
711 | u = 0;
712 | v = result.length - 1;
713 | while (u < v) {
714 | c = (u + v) >> 1;
715 | if (arr[result[c]] < arrI) {
716 | u = c + 1;
717 | }
718 | else {
719 | v = c;
720 | }
721 | }
722 | if (arrI < arr[result[u]]) {
723 | if (u > 0) {
724 | p[i] = result[u - 1];
725 | }
726 | result[u] = i;
727 | }
728 | }
729 | }
730 | u = result.length;
731 | v = result[u - 1];
732 | while (u-- > 0) {
733 | result[u] = v;
734 | v = p[v];
735 | }
736 | return result;
737 | }
738 |
739 | function createElement(type) {
740 | return document.createElement(type);
741 | }
742 | function patchProp(el, key, oldValue, nextValue) {
743 | const isOn = (key) => /^on[A-Z]/.test(key);
744 | if (isOn(key)) {
745 | const event = key.slice(2).toLowerCase();
746 | el.addEventListener(event, nextValue);
747 | }
748 | else {
749 | if (nextValue == null) {
750 | el.removeAttribute(key);
751 | }
752 | else {
753 | el.setAttribute(key, nextValue);
754 | }
755 | }
756 | }
757 | function insert(child, parent, anchor) {
758 | parent.insertBefore(child, anchor || null);
759 | }
760 | function remove(child) {
761 | const parent = child.parentNode;
762 | if (parent) {
763 | parent.removeChild(child);
764 | }
765 | }
766 | function setElementText(el, text) {
767 | el.textContent = text;
768 | }
769 | const renderer = createRenderer({
770 | createElement,
771 | patchProp,
772 | insert,
773 | remove,
774 | setElementText,
775 | });
776 | function createApp(...args) {
777 | return renderer.createApp(...args);
778 | }
779 |
780 | exports.createApp = createApp;
781 | exports.createRenderer = createRenderer;
782 | exports.createTextVNode = createTextVNode;
783 | exports.getCurrentInstance = getCurrentInstance;
784 | exports.h = h;
785 | exports.inject = inject;
786 | exports.provide = provide;
787 | exports.proxyRefs = proxyRefs;
788 | exports.ref = ref;
789 | exports.renderSlots = renderSlots;
790 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | const Fragment = Symbol("Fragment");
2 | const Text = Symbol("Text");
3 | function createVNode(type, props, children) {
4 | const vnode = {
5 | type,
6 | props,
7 | children,
8 | el: null,
9 | shapeFlag: getShapeType(type),
10 | key: props && props.key,
11 | };
12 | if (typeof children === "string") {
13 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
14 | }
15 | else if (Array.isArray(children)) {
16 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
17 | }
18 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
19 | if (typeof children === "object") {
20 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
21 | }
22 | }
23 | return vnode;
24 | }
25 | function createTextVNode(text) {
26 | return createVNode(Text, {}, text);
27 | }
28 | function getShapeType(type) {
29 | return typeof type === "string"
30 | ? 1 /* ELEMENT */
31 | : 2 /* STATEFUL_COMPONENT */;
32 | }
33 |
34 | function h(type, props, children) {
35 | return createVNode(type, props, children);
36 | }
37 |
38 | function renderSlots(slots, name, props) {
39 | let slot = slots[name];
40 | if (slot) {
41 | if (typeof slot === "function") {
42 | return createVNode(Fragment, {}, slot(props));
43 | }
44 | }
45 | }
46 |
47 | const extend = Object.assign;
48 | const isObject = (value) => {
49 | return value !== null && typeof value === "object";
50 | };
51 | const hasChanged = (val, newValue) => {
52 | return !Object.is(val, newValue);
53 | };
54 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
55 | const camelize = (str) => {
56 | return str.replace(/-(\w)/g, (_, c) => {
57 | return c ? c.toUpperCase() : "";
58 | });
59 | };
60 | const capitalize = (str) => {
61 | return str.charAt(0).toUpperCase() + str.slice(1);
62 | };
63 | const toHandlerKey = (str) => {
64 | return str ? "on" + capitalize(str) : "";
65 | };
66 |
67 | const publicPropertiesMap = {
68 | $el: (i) => i.vnode.el,
69 | $slots: (i) => i.slots,
70 | };
71 | const PublicInstanceProxyHandlers = {
72 | get({ _: instance }, key) {
73 | // setupState
74 | const { setupState, props } = instance;
75 | if (hasOwn(setupState, key)) {
76 | return setupState[key];
77 | }
78 | else if (hasOwn(props, key)) {
79 | return props[key];
80 | }
81 | const publicGetter = publicPropertiesMap[key];
82 | if (publicGetter) {
83 | return publicGetter(instance);
84 | }
85 | },
86 | };
87 |
88 | function initProps(intstance, rawProps) {
89 | intstance.props = rawProps || {};
90 | }
91 |
92 | function initSlots(instance, children) {
93 | const { vnode } = instance;
94 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
95 | normalizeObjectSlots(children, instance.slots);
96 | }
97 | }
98 | function normalizeObjectSlots(children, slots) {
99 | for (let key in children) {
100 | let value = children[key];
101 | slots[key] = (props) => normalizeSlotValue(value(props));
102 | }
103 | }
104 | function normalizeSlotValue(value) {
105 | return Array.isArray(value) ? value : [value];
106 | }
107 |
108 | let activeEffect;
109 | let shouldTrack = false;
110 | class ReactiveEffect {
111 | constructor(fn, scheduler) {
112 | this.deps = [];
113 | this.active = true;
114 | this._fn = fn;
115 | this.scheduler = scheduler;
116 | }
117 | run() {
118 | if (!this.active) {
119 | return this._fn();
120 | }
121 | // 应该收集
122 | shouldTrack = true;
123 | activeEffect = this;
124 | const r = this._fn();
125 | // 重置
126 | shouldTrack = false;
127 | return r;
128 | }
129 | stop() {
130 | if (this.active) {
131 | cleanupEffect(this);
132 | if (this.onStop) {
133 | this.onStop();
134 | }
135 | this.active = false;
136 | }
137 | }
138 | }
139 | function cleanupEffect(effect) {
140 | effect.deps.forEach((dep) => {
141 | dep.delete(effect);
142 | });
143 | // 把 effect.deps 清空
144 | effect.deps.length = 0;
145 | }
146 | const targetMap = new Map();
147 | function track(target, key) {
148 | if (!isTracking())
149 | return;
150 | // target -> key -> dep
151 | let depsMap = targetMap.get(target);
152 | if (!depsMap) {
153 | depsMap = new Map();
154 | targetMap.set(target, depsMap);
155 | }
156 | let dep = depsMap.get(key);
157 | if (!dep) {
158 | dep = new Set();
159 | depsMap.set(key, dep);
160 | }
161 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
162 | if (dep.has(activeEffect))
163 | return;
164 | trackEffects(dep);
165 | }
166 | function trackEffects(dep) {
167 | dep.add(activeEffect);
168 | activeEffect.deps.push(dep);
169 | }
170 | function isTracking() {
171 | return shouldTrack && activeEffect !== undefined;
172 | }
173 | function trigger(target, key) {
174 | let depsMap = targetMap.get(target);
175 | let dep = depsMap.get(key);
176 | triggerEffects(dep);
177 | }
178 | function triggerEffects(dep) {
179 | for (const effect of dep) {
180 | if (effect.scheduler) {
181 | effect.scheduler();
182 | }
183 | else {
184 | effect.run();
185 | }
186 | }
187 | }
188 | // 返回一个runner函数
189 | function effect(fn, options = {}) {
190 | // fn
191 | const _effect = new ReactiveEffect(fn, options.scheduler);
192 | extend(_effect, options);
193 | _effect.run(); // 依赖收集的入口
194 | const runner = _effect.run.bind(_effect);
195 | runner.effect = _effect;
196 | return runner;
197 | }
198 |
199 | const get = createGetter();
200 | const set = createSetter();
201 | const readonlyGet = createGetter(true);
202 | const shallowReadonlyGet = createGetter(true, true);
203 | function createGetter(isReadonly = false, shallow = false) {
204 | return function get(target, key) {
205 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
206 | return !isReadonly;
207 | }
208 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
209 | return isReadonly;
210 | }
211 | const res = Reflect.get(target, key);
212 | if (shallow) {
213 | return res;
214 | }
215 | if (isObject(res)) {
216 | return isReadonly ? readonly(res) : reactive(res);
217 | }
218 | if (!isReadonly) {
219 | track(target, key);
220 | }
221 | return res;
222 | };
223 | }
224 | function createSetter() {
225 | return function set(target, key, value) {
226 | const res = Reflect.set(target, key, value);
227 | trigger(target, key);
228 | return res;
229 | };
230 | }
231 | const mutableHandlers = {
232 | get,
233 | set,
234 | };
235 | const readonlyHandlers = {
236 | get: readonlyGet,
237 | set(target, key) {
238 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target);
239 | return true;
240 | },
241 | };
242 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
243 | get: shallowReadonlyGet,
244 | });
245 |
246 | function reactive(raw) {
247 | return createReactiveObject(raw, mutableHandlers);
248 | }
249 | function readonly(raw) {
250 | return createReactiveObject(raw, readonlyHandlers);
251 | }
252 | function shallowReadonly(raw) {
253 | return createReactiveObject(raw, shallowReadonlyHandlers);
254 | }
255 | function createReactiveObject(target, baseHandles) {
256 | return new Proxy(target, baseHandles);
257 | }
258 |
259 | function emit(instance, event, ...args) {
260 | const { props } = instance;
261 | const handlerName = toHandlerKey(camelize(event));
262 | const handler = props[handlerName];
263 | handler && handler(...args);
264 | }
265 |
266 | class RefImpl {
267 | constructor(value) {
268 | this._v_isRef = true;
269 | this._rawValue = value; // 保存原始值
270 | this._value = convert(value); // 如果是对象,则转化成响应式对象
271 | this.dep = new Set();
272 | }
273 | get value() {
274 | trackRefValue(this);
275 | return this._value;
276 | }
277 | set value(newVal) {
278 | // 如果值没有改变,那么没必要重复触发
279 | if (hasChanged(newVal, this._rawValue)) {
280 | this._rawValue = newVal;
281 | this._value = convert(newVal);
282 | triggerEffects(this.dep);
283 | }
284 | }
285 | }
286 | function ref(value) {
287 | return new RefImpl(value);
288 | }
289 | function convert(value) {
290 | return isObject(value) ? reactive(value) : value;
291 | }
292 | function trackRefValue(ref) {
293 | if (isTracking()) {
294 | trackEffects(ref.dep);
295 | }
296 | }
297 | function isRef(ref) {
298 | return !!ref._v_isRef;
299 | }
300 | function unRef(ref) {
301 | return isRef(ref) ? ref.value : ref;
302 | }
303 | function proxyRefs(objectWithRefs) {
304 | return new Proxy(objectWithRefs, {
305 | get(target, key) {
306 | return unRef(Reflect.get(target, key));
307 | },
308 | set(target, key, value) {
309 | if (isRef(target[key]) && !isRef(value)) {
310 | return (target[key].value = value);
311 | }
312 | else {
313 | return Reflect.set(target, key, value);
314 | }
315 | },
316 | });
317 | }
318 |
319 | function createComponentInstance(vnode, parent) {
320 | const component = {
321 | vnode,
322 | type: vnode.type,
323 | setupState: {},
324 | props: {},
325 | slots: {},
326 | provides: parent ? parent.provides : {},
327 | parent,
328 | isMounted: false,
329 | emit: () => { },
330 | };
331 | component.emit = emit.bind(null, component);
332 | return component;
333 | }
334 | function setupComponent(instance) {
335 | // TODO
336 | initProps(instance, instance.vnode.props);
337 | initSlots(instance, instance.vnode.children);
338 | setupStatefulComponent(instance);
339 | }
340 | function setupStatefulComponent(instance) {
341 | const Component = instance.type;
342 | const { setup } = Component;
343 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
344 | if (setup) {
345 | setCurrentInstance(instance);
346 | const setupResult = setup(shallowReadonly(instance.props), {
347 | emit: instance.emit,
348 | });
349 | setCurrentInstance(null);
350 | handleSetupResult(instance, setupResult);
351 | }
352 | }
353 | function handleSetupResult(instance, setupResult) {
354 | if (typeof setupResult === "object") {
355 | instance.setupState = proxyRefs(setupResult);
356 | }
357 | finishComponentSetup(instance);
358 | }
359 | // 最终肯定是要返回一个render函数
360 | function finishComponentSetup(instance) {
361 | const Component = instance.type;
362 | if (Component.render) {
363 | instance.render = Component.render;
364 | }
365 | }
366 | let currentInstance = null;
367 | function getCurrentInstance() {
368 | return currentInstance;
369 | }
370 | function setCurrentInstance(instance) {
371 | currentInstance = instance;
372 | }
373 |
374 | function provide(key, value) {
375 | const currentInstance = getCurrentInstance();
376 | if (currentInstance) {
377 | let { provides } = currentInstance;
378 | const parentProvides = currentInstance.parent.provides;
379 | if (parentProvides === provides) {
380 | provides = currentInstance.provides = Object.create(parentProvides);
381 | }
382 | provides[key] = value;
383 | }
384 | }
385 | function inject(key, defaultValue) {
386 | const currentInstance = getCurrentInstance();
387 | if (currentInstance) {
388 | const parentProvides = currentInstance.parent.provides;
389 | if (key in parentProvides) {
390 | return parentProvides[key];
391 | }
392 | else if (defaultValue) {
393 | if (typeof defaultValue === "function") {
394 | return defaultValue();
395 | }
396 | return defaultValue;
397 | }
398 | }
399 | }
400 |
401 | function createAppAPI(render) {
402 | return function createApp(rootComponent) {
403 | return {
404 | mount(rootContainer) {
405 | const vnode = createVNode(rootComponent);
406 | render(vnode, rootContainer);
407 | },
408 | };
409 | };
410 | }
411 |
412 | function createRenderer(options) {
413 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
414 | function render(vnode, container) {
415 | patch(null, vnode, container, null, null);
416 | }
417 | function patch(n1, n2, container, parentComponent, anchor) {
418 | const { type, shapeFlag } = n2;
419 | switch (type) {
420 | case Fragment:
421 | processFragment(n1, n2, container, parentComponent, anchor);
422 | break;
423 | case Text:
424 | processText(n1, n2, container);
425 | break;
426 | default:
427 | if (shapeFlag & 1 /* ELEMENT */) {
428 | processElement(n1, n2, container, parentComponent, anchor);
429 | }
430 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
431 | processComponent(n1, n2, container, parentComponent, anchor);
432 | }
433 | break;
434 | }
435 | }
436 | function processText(n1, n2, container) {
437 | const { children } = n2;
438 | const textVNode = (n2.el = document.createTextNode(children));
439 | container.append(textVNode);
440 | }
441 | function processFragment(n1, n2, container, parentComponent, anchor) {
442 | mountChildren(n2.children, container, parentComponent, anchor);
443 | }
444 | function processElement(n1, n2, container, parentComponent, anchor) {
445 | if (!n1) {
446 | mountElement(n2, container, parentComponent, anchor);
447 | }
448 | else {
449 | patchElement(n1, n2, container, parentComponent, anchor);
450 | }
451 | }
452 | function patchElement(n1, n2, container, parentComponent, anchor) {
453 | console.log("patchElement");
454 | console.log("n1", n1);
455 | console.log("n2", n2);
456 | const oldProps = n1.props;
457 | const newProps = n2.props;
458 | const el = (n2.el = n1.el);
459 | patchChildren(n1, n2, el, parentComponent, anchor);
460 | patchProps(el, oldProps, newProps);
461 | }
462 | function patchChildren(n1, n2, container, parentComponent, anchor) {
463 | const prevShapeFlag = n1.shapeFlag;
464 | const shapeFlag = n2.shapeFlag;
465 | const c1 = n1.children;
466 | const c2 = n2.children;
467 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
468 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
469 | unmountChildren(c1);
470 | }
471 | if (c1 !== c2) {
472 | hostSetElementText(container, c2);
473 | }
474 | }
475 | else {
476 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
477 | hostSetElementText(container, "");
478 | mountChildren(c2, container, parentComponent, anchor);
479 | }
480 | else {
481 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
482 | }
483 | }
484 | }
485 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
486 | const l2 = c2.length;
487 | let i = 0;
488 | let e1 = c1.length - 1;
489 | let e2 = l2 - 1;
490 | function isSomeVNodeType(n1, n2) {
491 | return n1.type === n2.type && n1.key === n2.key;
492 | }
493 | // 左侧进行对比相同节点
494 | while (i <= e1 && i <= e2) {
495 | let n1 = c1[i];
496 | let n2 = c2[i];
497 | if (isSomeVNodeType(n1, n2)) {
498 | patch(n1, n2, container, parentComponent, parentAnchor);
499 | }
500 | else {
501 | break;
502 | }
503 | i++;
504 | }
505 | // 右侧进行对比相同节点
506 | while (i <= e1 && i <= e2) {
507 | let n1 = c1[e1];
508 | let n2 = c2[e2];
509 | if (isSomeVNodeType(n1, n2)) {
510 | patch(n1, n2, container, parentComponent, parentAnchor);
511 | }
512 | else {
513 | break;
514 | }
515 | e1--;
516 | e2--;
517 | }
518 | if (i > e1) {
519 | if (i <= e2) {
520 | // 旧节点结束,新节点没有结束的情况
521 | const nextPos = e2 + 1;
522 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
523 | while (i <= e2) {
524 | patch(null, c2[i], container, parentComponent, anchor);
525 | i++;
526 | }
527 | }
528 | }
529 | else if (i > e2) {
530 | // 新节点结束,旧节点未结束的情况
531 | while (i <= e1) {
532 | hostRemove(c1[i].el);
533 | i++;
534 | }
535 | }
536 | else {
537 | // 中间对比
538 | let s1 = i;
539 | let s2 = i;
540 | let patched = 0;
541 | let moved = false;
542 | let maxNewIndexSoFar = 0;
543 | const toBePatched = e2 - s2 + 1;
544 | const keyToNewIndexMap = new Map();
545 | const newIndexToOldIndexMap = new Array(toBePatched);
546 | for (let i = 0; i < toBePatched; i++)
547 | newIndexToOldIndexMap[i] = 0;
548 | // 建立旧节点key映射到新节点index的hash表
549 | for (let i = s2; i <= e2; i++) {
550 | const nextChild = c2[i];
551 | keyToNewIndexMap.set(nextChild.key, i);
552 | }
553 | for (let i = s1; i <= e1; i++) {
554 | const prevChild = c1[i];
555 | if (patched >= toBePatched) {
556 | hostRemove(prevChild.el);
557 | continue;
558 | }
559 | // 哈希表中找是否存在的值
560 | let newIndex;
561 | if (prevChild.key != null) {
562 | newIndex = keyToNewIndexMap.get(prevChild.key);
563 | }
564 | else {
565 | for (let j = s2; j < e2; j++) {
566 | if (isSomeVNodeType(prevChild, s2[j])) {
567 | newIndex = j;
568 | break;
569 | }
570 | }
571 | }
572 | // 如果哈希表中没有找到,则进行删除操作
573 | if (newIndex == null) {
574 | hostRemove(prevChild.el);
575 | }
576 | else {
577 | // 如果在哈希表中找到
578 | if (newIndex >= maxNewIndexSoFar) {
579 | maxNewIndexSoFar = newIndex;
580 | }
581 | else {
582 | moved = true;
583 | }
584 | // 建立新节点index(这个index只针对中间的节点序列)到旧节点index的映射关系
585 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
586 | patch(prevChild, c2[newIndex], container, parentComponent, null);
587 | patched++;
588 | }
589 | }
590 | const increasingNewIndexSequence = moved
591 | ? getSequence(newIndexToOldIndexMap)
592 | : [];
593 | let j = increasingNewIndexSequence.length - 1; // 生成一个最长的递增子序列
594 | for (let i = toBePatched - 1; i >= 0; i--) {
595 | const nextIndex = i + s2;
596 | const nextChild = c2[nextIndex];
597 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
598 | if (newIndexToOldIndexMap[i] === 0) {
599 | // 新节点没有在旧节点中找到,给了个标识,直接在这里进行插入
600 | patch(null, nextChild, container, parentComponent, anchor);
601 | }
602 | else if (moved) {
603 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
604 | // 这时对顺序进行调整
605 | hostInsert(nextChild.el, container, anchor);
606 | }
607 | else {
608 | j--;
609 | }
610 | }
611 | }
612 | }
613 | }
614 | function unmountChildren(children) {
615 | for (let i = 0; i < children.length; i++) {
616 | const el = children[i].el;
617 | hostRemove(el);
618 | }
619 | }
620 | function patchProps(el, oldProps, newProps) {
621 | if (oldProps !== newProps) {
622 | for (const key in newProps) {
623 | const prevProp = oldProps[key];
624 | const nextProp = newProps[key];
625 | if (prevProp !== nextProp) {
626 | hostPatchProp(el, key, prevProp, nextProp);
627 | }
628 | }
629 | if (JSON.stringify(oldProps) !== "{}") {
630 | for (const key in oldProps) {
631 | if (!(key in newProps)) {
632 | hostPatchProp(el, key, oldProps[key], null);
633 | }
634 | }
635 | }
636 | }
637 | }
638 | function processComponent(n1, n2, container, parentComponent, anchor) {
639 | mountComponent(n2, container, parentComponent, anchor);
640 | }
641 | function mountElement(vnode, container, parentComponent, anchor) {
642 | // createElement
643 | const el = (vnode.el = hostCreateElement(vnode.type));
644 | const { children, shapeFlag } = vnode;
645 | // children
646 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
647 | el.textContent = children;
648 | }
649 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
650 | mountChildren(vnode.children, el, parentComponent, anchor);
651 | }
652 | // props
653 | const { props } = vnode;
654 | for (const key in props) {
655 | hostPatchProp(el, key, null, props[key]);
656 | }
657 | hostInsert(el, container, anchor);
658 | }
659 | function mountChildren(children, container, parentComponent, anchor) {
660 | children.forEach((v) => {
661 | patch(null, v, container, parentComponent, anchor);
662 | });
663 | }
664 | function mountComponent(initialVNode, container, parentComponent, anchor) {
665 | const instance = createComponentInstance(initialVNode, parentComponent);
666 | setupComponent(instance);
667 | setupRenderEffect(instance, initialVNode, container, anchor);
668 | }
669 | function setupRenderEffect(instance, initialVNode, container, anchor) {
670 | effect(() => {
671 | if (!instance.isMounted) {
672 | console.log("init");
673 | const { proxy } = instance;
674 | const subTree = (instance.subTree = instance.render.call(proxy));
675 | patch(null, subTree, container, instance, anchor);
676 | initialVNode.el = subTree.el;
677 | instance.isMounted = true;
678 | }
679 | else {
680 | console.log("update");
681 | const { proxy } = instance;
682 | const subTree = instance.render.call(proxy);
683 | const prevSubTree = instance.subTree;
684 | instance.subTree = subTree;
685 | patch(prevSubTree, subTree, container, instance, anchor);
686 | }
687 | });
688 | }
689 | return {
690 | createApp: createAppAPI(render),
691 | };
692 | }
693 | function getSequence(arr) {
694 | const p = arr.slice();
695 | const result = [0];
696 | let i, j, u, v, c;
697 | const len = arr.length;
698 | for (i = 0; i < len; i++) {
699 | const arrI = arr[i];
700 | if (arrI !== 0) {
701 | j = result[result.length - 1];
702 | if (arr[j] < arrI) {
703 | p[i] = j;
704 | result.push(i);
705 | continue;
706 | }
707 | u = 0;
708 | v = result.length - 1;
709 | while (u < v) {
710 | c = (u + v) >> 1;
711 | if (arr[result[c]] < arrI) {
712 | u = c + 1;
713 | }
714 | else {
715 | v = c;
716 | }
717 | }
718 | if (arrI < arr[result[u]]) {
719 | if (u > 0) {
720 | p[i] = result[u - 1];
721 | }
722 | result[u] = i;
723 | }
724 | }
725 | }
726 | u = result.length;
727 | v = result[u - 1];
728 | while (u-- > 0) {
729 | result[u] = v;
730 | v = p[v];
731 | }
732 | return result;
733 | }
734 |
735 | function createElement(type) {
736 | return document.createElement(type);
737 | }
738 | function patchProp(el, key, oldValue, nextValue) {
739 | const isOn = (key) => /^on[A-Z]/.test(key);
740 | if (isOn(key)) {
741 | const event = key.slice(2).toLowerCase();
742 | el.addEventListener(event, nextValue);
743 | }
744 | else {
745 | if (nextValue == null) {
746 | el.removeAttribute(key);
747 | }
748 | else {
749 | el.setAttribute(key, nextValue);
750 | }
751 | }
752 | }
753 | function insert(child, parent, anchor) {
754 | parent.insertBefore(child, anchor || null);
755 | }
756 | function remove(child) {
757 | const parent = child.parentNode;
758 | if (parent) {
759 | parent.removeChild(child);
760 | }
761 | }
762 | function setElementText(el, text) {
763 | el.textContent = text;
764 | }
765 | const renderer = createRenderer({
766 | createElement,
767 | patchProp,
768 | insert,
769 | remove,
770 | setElementText,
771 | });
772 | function createApp(...args) {
773 | return renderer.createApp(...args);
774 | }
775 |
776 | export { createApp, createRenderer, createTextVNode, getCurrentInstance, h, inject, provide, proxyRefs, ref, renderSlots };
777 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "guide-mini-vue",
3 | "version": "1.0.0",
4 | "main": "lib/guide-mini-vue.cjs.js",
5 | "module": "lib/guide-mini-vue.esm.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js --watch"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.15.5",
13 | "@babel/preset-env": "^7.15.4",
14 | "@babel/preset-typescript": "^7.15.0",
15 | "@types/jest": "^27.0.1",
16 | "babel-jest": "^27.1.0",
17 | "jest": "^27.1.0",
18 | "rollup": "^2.61.1",
19 | "tslib": "^2.3.1",
20 | "typescript": "^4.4.2"
21 | },
22 | "dependencies": {
23 | "@rollup/plugin-typescript": "^8.3.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript";
2 | import pkg from "./package.json";
3 |
4 | export default {
5 | input: "./src/index.ts",
6 | output: [
7 | {
8 | format: "cjs",
9 | file: pkg.main,
10 | },
11 | {
12 | format: "es",
13 | file: pkg.module,
14 | },
15 | ],
16 | plugins: [typescript()],
17 | };
18 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口
2 | export * from "./runtime-dom";
3 | export * from "./reactivity";
4 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandler.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "../shared";
2 | import { track, trigger } from "./effect";
3 | import { reactive, ReactiveFlags, readonly } from "./reactive";
4 |
5 | function createGetter(isReadonly = false, shallow = false) {
6 | return function get(target, key) {
7 | if (key === ReactiveFlags.IS_REACTIVE) {
8 | return !isReadonly;
9 | } else if (key === ReactiveFlags.IS_READONLY) {
10 | return isReadonly;
11 | }
12 |
13 | const res = Reflect.get(target, key);
14 |
15 | if (shallow) {
16 | return res;
17 | }
18 |
19 | if (isObject(res)) {
20 | return isReadonly ? readonly(res) : reactive(res);
21 | }
22 | // 如果不是只读,需要进行依赖收集
23 | if (!isReadonly) {
24 | track(target, key);
25 | }
26 | return res;
27 | };
28 | }
29 |
30 | function createSetter() {
31 | return function set(target, key, value) {
32 | const res = Reflect.set(target, key, value);
33 |
34 | trigger(target, key);
35 | return res;
36 | };
37 | }
38 |
39 | const get = createGetter();
40 | const set = createSetter();
41 | const readonlyGet = createGetter(true);
42 | const shallowReadonlyGet = createGetter(true, true);
43 |
44 | export const mutableHandlers = {
45 | get,
46 | set,
47 | };
48 |
49 | export const readonlyHandlers = {
50 | get: readonlyGet,
51 | set(target, key) {
52 | console.warn(
53 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`,
54 | target
55 | );
56 |
57 | return true;
58 | },
59 | };
60 |
61 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
62 | get: shallowReadonlyGet,
63 | });
64 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl {
4 | private _dirty: boolean;
5 | private _value: any;
6 | private _effect: any;
7 |
8 | constructor(getter) {
9 | this._dirty = true;
10 | // 派发更新时没有立即进行依赖收集
11 | // 而是打开了一个开关,等下次读取时再重新进行依赖收集
12 | this._effect = new ReactiveEffect(getter, () => {
13 | if (!this._dirty) {
14 | this._dirty = true;
15 | }
16 | });
17 | }
18 | get value() {
19 | // 如果没有更新时,那么只是拿取缓存值
20 | if (this._dirty) {
21 | this._dirty = false;
22 | this._value = this._effect.run(); // 进行依赖收集
23 | }
24 | return this._value; // 拿取缓存
25 | }
26 | }
27 |
28 | // 初始化时调用一次,之后如果没有发生更新,那么拿的就是缓存的值
29 | // 如果更新了,不会立即触发getter函数,而是等到下一次读取值的时候再进行触发
30 | export function computed(getter) {
31 | return new ComputedRefImpl(getter);
32 | }
33 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared";
2 |
3 | let activeEffect;
4 | let shouldTrack = false;
5 | export class ReactiveEffect {
6 | private _fn: any;
7 | deps = [];
8 | active = true;
9 | onStop?: () => void;
10 | public scheduler: Function | undefined;
11 | constructor(fn, scheduler?: Function) {
12 | this._fn = fn;
13 | this.scheduler = scheduler;
14 | }
15 | run() {
16 | if (!this.active) {
17 | return this._fn();
18 | }
19 |
20 | // 打开收集依赖的开关
21 | shouldTrack = true;
22 | // 指向当前活跃的观察者
23 | activeEffect = this;
24 | // 这个过程会涉及到对响应式数据操作
25 | const r = this._fn();
26 |
27 | // 重置
28 | shouldTrack = false;
29 |
30 | return r;
31 | }
32 | stop() {
33 | if (this.active) {
34 | cleanupEffect(this);
35 | if (this.onStop) {
36 | this.onStop();
37 | }
38 | this.active = false;
39 | }
40 | }
41 | }
42 |
43 | function cleanupEffect(effect) {
44 | effect.deps.forEach((dep: any) => {
45 | dep.delete(effect);
46 | });
47 |
48 | // 把 effect.deps 清空
49 | effect.deps.length = 0;
50 | }
51 |
52 | const targetMap = new Map();
53 |
54 | // 依赖收集实现的原理
55 | export function track(target, key) {
56 | if (!isTracking()) return;
57 | // target -> key -> dep
58 | let depsMap = targetMap.get(target);
59 | if (!depsMap) {
60 | depsMap = new Map();
61 | targetMap.set(target, depsMap);
62 | }
63 |
64 | let dep = depsMap.get(key);
65 | if (!dep) {
66 | dep = new Set();
67 | depsMap.set(key, dep);
68 | }
69 |
70 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
71 | if (dep.has(activeEffect)) return;
72 | trackEffects(dep);
73 | }
74 |
75 | export function trackEffects(dep) {
76 | dep.add(activeEffect);
77 | activeEffect.deps.push(dep);
78 | }
79 |
80 | export function isTracking() {
81 | return shouldTrack && activeEffect !== undefined;
82 | }
83 |
84 | export function trigger(target, key) {
85 | let depsMap = targetMap.get(target);
86 | let dep = depsMap.get(key);
87 |
88 | triggerEffects(dep);
89 | }
90 |
91 | export function triggerEffects(dep) {
92 | for (const effect of dep) {
93 | if (effect.scheduler) {
94 | effect.scheduler();
95 | } else {
96 | effect.run();
97 | }
98 | }
99 | }
100 |
101 | // 返回一个runner函数
102 | export function effect(fn, options: any = {}) {
103 | // fn
104 | const _effect = new ReactiveEffect(fn, options.scheduler);
105 | extend(_effect, options);
106 | // 依赖收集的入口
107 | _effect.run();
108 |
109 | const runner: any = _effect.run.bind(_effect);
110 | runner.effect = _effect;
111 |
112 | return runner;
113 | }
114 |
115 | export function stop(runner) {
116 | runner.effect.stop();
117 | }
118 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs } from "./ref";
2 |
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | mutableHandlers,
3 | readonlyHandlers,
4 | shallowReadonlyHandlers,
5 | } from "./baseHandler";
6 |
7 | export const enum ReactiveFlags {
8 | IS_REACTIVE = "__v_isReactive",
9 | IS_READONLY = "__v_isReadonly",
10 | }
11 |
12 | export function reactive(raw) {
13 | return createReactiveObject(raw, mutableHandlers);
14 | }
15 |
16 | export function readonly(raw) {
17 | return createReactiveObject(raw, readonlyHandlers);
18 | }
19 |
20 | export function shallowReadonly(raw) {
21 | return createReactiveObject(raw, shallowReadonlyHandlers);
22 | }
23 |
24 | export function isReactive(value) {
25 | return !!value[ReactiveFlags.IS_REACTIVE];
26 | }
27 |
28 | export function isReadonly(value) {
29 | return !!value[ReactiveFlags.IS_READONLY];
30 | }
31 |
32 | export function isProxy(value) {
33 | return isReactive(value) || isReadonly(value);
34 | }
35 |
36 | function createReactiveObject(target, baseHandles) {
37 | return new Proxy(target, baseHandles);
38 | }
39 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "../shared";
2 | import { trackEffects, triggerEffects, isTracking } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | class RefImpl {
6 | private _value: any;
7 | public dep: any;
8 | private _rawValue: any;
9 | public _v_isRef = true;
10 | constructor(value) {
11 | this._rawValue = value; // 保存原始值
12 | this._value = convert(value); // 如果是对象,则转化成响应式对象
13 | this.dep = new Set();
14 | }
15 | get value() {
16 | trackRefValue(this);
17 | return this._value;
18 | }
19 | set value(newVal) {
20 | // 如果值没有改变,那么没必要重复触发
21 | if (hasChanged(newVal, this._rawValue)) {
22 | this._rawValue = newVal;
23 | this._value = convert(newVal);
24 | triggerEffects(this.dep);
25 | }
26 | }
27 | }
28 |
29 | export function ref(value) {
30 | return new RefImpl(value);
31 | }
32 |
33 | function convert(value) {
34 | return isObject(value) ? reactive(value) : value;
35 | }
36 |
37 | function trackRefValue(ref) {
38 | if (isTracking()) {
39 | trackEffects(ref.dep);
40 | }
41 | }
42 |
43 | export function isRef(ref) {
44 | return !!ref._v_isRef;
45 | }
46 |
47 | export function unRef(ref) {
48 | return isRef(ref) ? ref.value : ref;
49 | }
50 |
51 | export function proxyRefs(objectWithRefs) {
52 | return new Proxy(objectWithRefs, {
53 | get(target, key) {
54 | return unRef(Reflect.get(target, key));
55 | },
56 | set(target, key, value) {
57 | if (isRef(target[key]) && !isRef(value)) {
58 | return (target[key].value = value);
59 | } else {
60 | return Reflect.set(target, key, value);
61 | }
62 | },
63 | });
64 | }
65 |
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../computed";
2 | import { reactive } from "../reactive";
3 |
4 | describe("computed", () => {
5 | it("happy path", () => {
6 | const user = reactive({
7 | age: 1,
8 | });
9 |
10 | const age = computed(() => {
11 | return user.age;
12 | });
13 |
14 | expect(age.value).toBe(1);
15 | });
16 |
17 | it("should compute lazily", () => {
18 | const value = reactive({
19 | foo: 1,
20 | });
21 | const getter = jest.fn(() => {
22 | return value.foo;
23 | });
24 | const cValue = computed(getter);
25 |
26 | // lazy
27 | expect(getter).not.toHaveBeenCalled();
28 |
29 | expect(cValue.value).toBe(1);
30 | expect(getter).toHaveBeenCalledTimes(1);
31 |
32 | // should not compute again
33 | cValue.value; // get
34 | expect(getter).toHaveBeenCalledTimes(1);
35 |
36 | // should not compute until needed
37 | value.foo = 2;
38 | expect(getter).toHaveBeenCalledTimes(1);
39 |
40 | // now it should compute
41 | expect(cValue.value).toBe(2);
42 | expect(getter).toHaveBeenCalledTimes(2);
43 |
44 | // should not compute again
45 | cValue.value;
46 | expect(getter).toHaveBeenCalledTimes(2);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect, stop } from "../effect";
2 | import { reactive } from "../reactive";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | const user = reactive({
7 | age: 10,
8 | });
9 |
10 | let nextAge;
11 | effect(() => {
12 | nextAge = user.age + 1;
13 | });
14 |
15 | expect(nextAge).toBe(11);
16 |
17 | // update
18 | user.age++;
19 | expect(nextAge).toBe(12);
20 | });
21 |
22 | it("should return runner when call effect", () => {
23 | // 当调用 runner 的时候可以重新执行 effect.run
24 | // runner 的返回值就是用户给的 fn 的返回值
25 | let foo = 0;
26 | const runner = effect(() => {
27 | foo++;
28 | return foo;
29 | });
30 |
31 | expect(foo).toBe(1);
32 | runner();
33 | expect(foo).toBe(2);
34 | expect(runner()).toBe(3);
35 | });
36 |
37 | it("scheduler", () => {
38 | let dummy;
39 | let run: any;
40 | const scheduler = jest.fn(() => {
41 | run = runner;
42 | });
43 | const obj = reactive({ foo: 1 });
44 | const runner = effect(
45 | () => {
46 | dummy = obj.foo;
47 | },
48 | { scheduler }
49 | );
50 | expect(scheduler).not.toHaveBeenCalled();
51 | expect(dummy).toBe(1);
52 | // should be called on first trigger
53 | obj.foo++;
54 | expect(scheduler).toHaveBeenCalledTimes(1);
55 | // // should not run yet
56 | expect(dummy).toBe(1);
57 | // // manually run
58 | run();
59 | // // should have run
60 | expect(dummy).toBe(2);
61 | });
62 |
63 | it("stop", () => {
64 | let dummy;
65 | const obj = reactive({ prop: 1 });
66 | const runner = effect(() => {
67 | dummy = obj.prop;
68 | });
69 | obj.prop = 2;
70 | expect(dummy).toBe(2);
71 | stop(runner);
72 | // obj.prop = 3;
73 | obj.prop++;
74 | expect(dummy).toBe(2);
75 |
76 | // stopped effect should still be manually callable
77 | runner();
78 | expect(dummy).toBe(3);
79 | });
80 |
81 | it("onStop", () => {
82 | const obj = reactive({
83 | foo: 1,
84 | });
85 | const onStop = jest.fn();
86 | let dummy;
87 | const runner = effect(
88 | () => {
89 | dummy = obj.foo;
90 | },
91 | {
92 | onStop,
93 | }
94 | );
95 |
96 | stop(runner);
97 | expect(onStop).toBeCalledTimes(1);
98 | });
99 | });
100 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive, reactive, isProxy } from "../reactive";
2 | describe("reactive", () => {
3 | it("happy path", () => {
4 | const original = { foo: 1 };
5 | const observed = reactive(original);
6 | expect(observed).not.toBe(original);
7 | expect(observed.foo).toBe(1);
8 | expect(isReactive(observed)).toBe(true);
9 | expect(isReactive(original)).toBe(false);
10 | expect(isProxy(observed)).toBe(true);
11 | });
12 |
13 | test("nested reactives", () => {
14 | const original = {
15 | nested: {
16 | foo: 1,
17 | },
18 | array: [{ bar: 2 }],
19 | };
20 | const observed = reactive(original);
21 | expect(isReactive(observed.nested)).toBe(true);
22 | expect(isReactive(observed.array)).toBe(true);
23 | expect(isReactive(observed.array[0])).toBe(true);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReadonly, readonly } from "../reactive";
2 |
3 | describe("readonly", () => {
4 | it("should make nested values readonly", () => {
5 | const original = { foo: 1, bar: { baz: 2 } };
6 | const wrapped = readonly(original);
7 | expect(wrapped).not.toBe(original);
8 | expect(isReadonly(wrapped)).toBe(true);
9 | expect(isReadonly(original)).toBe(false);
10 | expect(isReadonly(wrapped.bar)).toBe(true);
11 | expect(isReadonly(original.bar)).toBe(false);
12 | expect(isProxy(wrapped)).toBe(true);
13 |
14 | expect(wrapped.foo).toBe(1);
15 | });
16 |
17 | it("should call console.warn when set", () => {
18 | console.warn = jest.fn();
19 | const user = readonly({
20 | age: 10,
21 | });
22 |
23 | user.age = 11;
24 | expect(console.warn).toHaveBeenCalled();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { reactive } from "../reactive";
3 | import { isRef, ref, unRef, proxyRefs } from "../ref";
4 | describe("ref", () => {
5 | it("happy path", () => {
6 | const a = ref(1);
7 | expect(a.value).toBe(1);
8 | });
9 |
10 | it("should be reactive", () => {
11 | const a = ref(1);
12 | let dummy;
13 | let calls = 0;
14 | effect(() => {
15 | calls++;
16 | dummy = a.value;
17 | });
18 | expect(calls).toBe(1);
19 | expect(dummy).toBe(1);
20 | a.value = 2;
21 | expect(calls).toBe(2);
22 | expect(dummy).toBe(2);
23 | // same value should not trigger
24 | a.value = 2;
25 | expect(calls).toBe(2);
26 | expect(dummy).toBe(2);
27 | });
28 |
29 | it("should make nested properties reactive", () => {
30 | const a = ref({
31 | count: 1,
32 | });
33 | let dummy;
34 | effect(() => {
35 | dummy = a.value.count;
36 | });
37 | expect(dummy).toBe(1);
38 | a.value.count = 2;
39 | expect(dummy).toBe(2);
40 | });
41 |
42 | it("isRef", () => {
43 | const a = ref(1);
44 | const user = reactive({
45 | age: 1,
46 | });
47 | expect(isRef(a)).toBe(true);
48 | expect(isRef(1)).toBe(false);
49 | expect(isRef(user)).toBe(false);
50 | });
51 |
52 | it("unRef", () => {
53 | const a = ref(1);
54 | expect(unRef(a)).toBe(1);
55 | expect(unRef(1)).toBe(1);
56 | });
57 |
58 | it("proxyRefs", () => {
59 | const user = {
60 | age: ref(10),
61 | name: "xiaohong",
62 | };
63 |
64 | const proxyUser = proxyRefs(user);
65 | expect(user.age.value).toBe(10);
66 | expect(proxyUser.age).toBe(10);
67 | expect(proxyUser.name).toBe("xiaohong");
68 |
69 | proxyUser.age = 20;
70 |
71 | expect(proxyUser.age).toBe(20);
72 | expect(user.age.value).toBe(20);
73 |
74 | proxyUser.age = ref(10);
75 | expect(proxyUser.age).toBe(10);
76 | expect(user.age.value).toBe(10);
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../reactive";
2 |
3 | describe("shallowReadonly", () => {
4 | test("should not make non-reactive properties reactive", () => {
5 | const props = shallowReadonly({ n: { foo: 1 } });
6 | expect(isReadonly(props)).toBe(true);
7 | expect(isReadonly(props.n)).toBe(false);
8 | });
9 |
10 | it("should call console.warn when set", () => {
11 | console.warn = jest.fn();
12 | const user = shallowReadonly({
13 | age: 10,
14 | });
15 |
16 | user.age = 11;
17 | expect(console.warn).toHaveBeenCalled();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | export function provide(key, value) {
4 | const currentInstance: any = getCurrentInstance();
5 | if (currentInstance) {
6 | let { provides } = currentInstance;
7 | const parentProvides = currentInstance.parent.provides;
8 |
9 | if (parentProvides === provides) {
10 | provides = currentInstance.provides = Object.create(parentProvides);
11 | }
12 |
13 | provides[key] = value;
14 | }
15 | }
16 |
17 | export function inject(key, defaultValue) {
18 | const currentInstance: any = getCurrentInstance();
19 | if (currentInstance) {
20 | const parentProvides = currentInstance.parent.provides;
21 | if (key in parentProvides) {
22 | return parentProvides[key];
23 | } else if (defaultValue) {
24 | if (typeof defaultValue === "function") {
25 | return defaultValue();
26 | }
27 | return defaultValue;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
2 | import { initProps } from "./componentProps";
3 | import { initSlots } from "./componentSlots";
4 | import { shallowReadonly } from "../reactivity/reactive";
5 | import { emit } from "./componentEmit";
6 | import { proxyRefs } from "../reactivity";
7 |
8 | export function createComponentInstance(vnode: any, parent: any) {
9 | const component = {
10 | vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | props: {},
14 | slots: {},
15 | provides: parent ? parent.provides : {},
16 | parent,
17 | isMounted: false,
18 | emit: () => {},
19 | };
20 | component.emit = emit.bind(null, component) as any;
21 | return component;
22 | }
23 |
24 | export function setupComponent(instance) {
25 | // TODO
26 | initProps(instance, instance.vnode.props);
27 | initSlots(instance, instance.vnode.children);
28 | setupStatefulComponent(instance);
29 | }
30 |
31 | function setupStatefulComponent(instance: any) {
32 | const Component = instance.type;
33 | const { setup } = Component;
34 |
35 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
36 |
37 | if (setup) {
38 | setCurrentInstance(instance);
39 | const setupResult = setup(shallowReadonly(instance.props), {
40 | emit: instance.emit,
41 | });
42 | setCurrentInstance(null);
43 | handleSetupResult(instance, setupResult);
44 | }
45 | }
46 |
47 | function handleSetupResult(instance, setupResult: any) {
48 | if (typeof setupResult === "object") {
49 | instance.setupState = proxyRefs(setupResult);
50 | }
51 |
52 | finishComponentSetup(instance);
53 | }
54 |
55 | // 最终肯定是要返回一个render函数
56 | function finishComponentSetup(instance: any) {
57 | const Component = instance.type;
58 |
59 | if (Component.render) {
60 | instance.render = Component.render;
61 | }
62 | }
63 |
64 | let currentInstance = null;
65 |
66 | export function getCurrentInstance() {
67 | return currentInstance;
68 | }
69 |
70 | export function setCurrentInstance(instance) {
71 | currentInstance = instance;
72 | }
73 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "../shared/index";
2 |
3 | export function emit(instance, event, ...args) {
4 | const { props } = instance;
5 | const handlerName = toHandlerKey(camelize(event));
6 | const handler = props[handlerName];
7 | handler && handler(...args);
8 | }
9 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(intstance, rawProps) {
2 | intstance.props = rawProps || {};
3 | }
4 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "../shared/index";
2 |
3 | const publicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | };
7 |
8 | export const PublicInstanceProxyHandlers = {
9 | get({ _: instance }, key) {
10 | // setupState
11 | const { setupState, props } = instance;
12 | if (hasOwn(setupState, key)) {
13 | return setupState[key];
14 | } else if (hasOwn(props, key)) {
15 | return props[key];
16 | }
17 |
18 | const publicGetter = publicPropertiesMap[key];
19 | if (publicGetter) {
20 | return publicGetter(instance);
21 | }
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export function initSlots(instance, children) {
4 | const { vnode } = instance;
5 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
6 | normalizeObjectSlots(children, instance.slots);
7 | }
8 | }
9 |
10 | function normalizeObjectSlots(children, slots) {
11 | for (let key in children) {
12 | let value = children[key];
13 | slots[key] = (props) => normalizeSlotValue(value(props));
14 | }
15 | }
16 |
17 | function normalizeSlotValue(value) {
18 | return Array.isArray(value) ? value : [value];
19 | }
20 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function createAppAPI(render) {
4 | return function createApp(rootComponent) {
5 | return {
6 | mount(rootContainer) {
7 | const vnode = createVNode(rootComponent);
8 | render(vnode, rootContainer);
9 | },
10 | };
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function h(type, props?, children?) {
4 | return createVNode(type, props, children);
5 | }
6 |
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | let slot = slots[name];
5 | if (slot) {
6 | if (typeof slot === "function") {
7 | return createVNode(Fragment, {}, slot(props));
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | export { h } from "./h";
2 | export { renderSlots } from "./helpers/renderSlots";
3 | export { createTextVNode } from "./vnode";
4 | export { getCurrentInstance } from "./component";
5 | export { inject, provide } from "./apiInject";
6 | export { createRenderer } from "./renderer";
7 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { createComponentInstance, setupComponent } from "./component";
2 | import { ShapeFlags } from "../shared/ShapeFlags";
3 | import { Fragment, Text } from "./vnode";
4 | import { createAppAPI } from "./createApp";
5 | import { effect } from "../reactivity/effect";
6 |
7 | export function createRenderer(options) {
8 | const {
9 | createElement: hostCreateElement,
10 | patchProp: hostPatchProp,
11 | insert: hostInsert,
12 | remove: hostRemove,
13 | setElementText: hostSetElementText,
14 | } = options;
15 |
16 | function render(vnode, container) {
17 | patch(null, vnode, container, null, null);
18 | }
19 |
20 | function patch(n1, n2, container, parentComponent, anchor) {
21 | const { type, shapeFlag } = n2;
22 |
23 | switch (type) {
24 | case Fragment:
25 | processFragment(n1, n2, container, parentComponent, anchor);
26 | break;
27 | case Text:
28 | processText(n1, n2, container);
29 | break;
30 | default:
31 | if (shapeFlag & ShapeFlags.ELEMENT) {
32 | processElement(n1, n2, container, parentComponent, anchor);
33 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
34 | processComponent(n1, n2, container, parentComponent, anchor);
35 | }
36 | break;
37 | }
38 | }
39 |
40 | function processText(n1, n2, container) {
41 | const { children } = n2;
42 | const textVNode = (n2.el = document.createTextNode(children));
43 | container.append(textVNode);
44 | }
45 |
46 | function processFragment(n1, n2, container, parentComponent, anchor) {
47 | mountChildren(n2.children, container, parentComponent, anchor);
48 | }
49 |
50 | function processElement(n1, n2, container, parentComponent, anchor) {
51 | if (!n1) {
52 | mountElement(n2, container, parentComponent, anchor);
53 | } else {
54 | patchElement(n1, n2, container, parentComponent, anchor);
55 | }
56 | }
57 |
58 | function patchElement(n1, n2, container, parentComponent, anchor) {
59 | console.log("patchElement");
60 | console.log("n1", n1);
61 | console.log("n2", n2);
62 |
63 | const oldProps = n1.props;
64 | const newProps = n2.props;
65 | const el = (n2.el = n1.el);
66 | patchChildren(n1, n2, el, parentComponent, anchor);
67 | patchProps(el, oldProps, newProps);
68 | }
69 |
70 | function patchChildren(n1, n2, container, parentComponent, anchor) {
71 | const prevShapeFlag = n1.shapeFlag;
72 | const shapeFlag = n2.shapeFlag;
73 | const c1 = n1.children;
74 | const c2 = n2.children;
75 |
76 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
77 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
78 | unmountChildren(c1);
79 | }
80 | if (c1 !== c2) {
81 | hostSetElementText(container, c2);
82 | }
83 | } else {
84 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
85 | hostSetElementText(container, "");
86 | mountChildren(c2, container, parentComponent, anchor);
87 | } else {
88 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
89 | }
90 | }
91 | }
92 |
93 | function patchKeyedChildren(
94 | c1,
95 | c2,
96 | container,
97 | parentComponent,
98 | parentAnchor
99 | ) {
100 | const l2 = c2.length;
101 | let i = 0;
102 | let e1 = c1.length - 1;
103 | let e2 = l2 - 1;
104 | function isSomeVNodeType(n1, n2) {
105 | return n1.type === n2.type && n1.key === n2.key;
106 | }
107 |
108 | // 左侧进行对比相同节点
109 | while (i <= e1 && i <= e2) {
110 | let n1 = c1[i];
111 | let n2 = c2[i];
112 | if (isSomeVNodeType(n1, n2)) {
113 | patch(n1, n2, container, parentComponent, parentAnchor);
114 | } else {
115 | break;
116 | }
117 | i++;
118 | }
119 |
120 | // 右侧进行对比相同节点
121 | while (i <= e1 && i <= e2) {
122 | let n1 = c1[e1];
123 | let n2 = c2[e2];
124 | if (isSomeVNodeType(n1, n2)) {
125 | patch(n1, n2, container, parentComponent, parentAnchor);
126 | } else {
127 | break;
128 | }
129 | e1--;
130 | e2--;
131 | }
132 |
133 | if (i > e1) {
134 | if (i <= e2) {
135 | // 旧节点结束,新节点没有结束的情况
136 | const nextPos = e2 + 1;
137 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
138 | while (i <= e2) {
139 | patch(null, c2[i], container, parentComponent, anchor);
140 | i++;
141 | }
142 | }
143 | } else if (i > e2) {
144 | // 新节点结束,旧节点未结束的情况
145 | while (i <= e1) {
146 | hostRemove(c1[i].el);
147 | i++;
148 | }
149 | } else {
150 | // 中间对比
151 | let s1 = i;
152 | let s2 = i;
153 | let patched = 0;
154 | let moved = false;
155 | let maxNewIndexSoFar = 0;
156 | const toBePatched = e2 - s2 + 1;
157 | const keyToNewIndexMap = new Map();
158 | const newIndexToOldIndexMap = new Array(toBePatched);
159 |
160 | for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
161 |
162 | // 建立旧节点key映射到新节点index的hash表
163 | for (let i = s2; i <= e2; i++) {
164 | const nextChild = c2[i];
165 | keyToNewIndexMap.set(nextChild.key, i);
166 | }
167 |
168 | for (let i = s1; i <= e1; i++) {
169 | const prevChild = c1[i];
170 |
171 | if (patched >= toBePatched) {
172 | hostRemove(prevChild.el);
173 | continue;
174 | }
175 |
176 | // 哈希表中找是否存在的值
177 | let newIndex;
178 | if (prevChild.key != null) {
179 | newIndex = keyToNewIndexMap.get(prevChild.key);
180 | } else {
181 | for (let j = s2; j < e2; j++) {
182 | if (isSomeVNodeType(prevChild, s2[j])) {
183 | newIndex = j;
184 | break;
185 | }
186 | }
187 | }
188 | // 如果哈希表中没有找到,则进行删除操作
189 | if (newIndex == null) {
190 | hostRemove(prevChild.el);
191 | } else {
192 | // 如果在哈希表中找到
193 | if (newIndex >= maxNewIndexSoFar) {
194 | maxNewIndexSoFar = newIndex;
195 | } else {
196 | moved = true;
197 | }
198 | // 建立新节点index(这个index只针对中间的节点序列)到旧节点index的映射关系
199 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
200 | patch(prevChild, c2[newIndex], container, parentComponent, null);
201 | patched++;
202 | }
203 | }
204 |
205 | const increasingNewIndexSequence = moved
206 | ? getSequence(newIndexToOldIndexMap)
207 | : [];
208 | let j = increasingNewIndexSequence.length - 1; // 生成一个最长的递增子序列
209 | for (let i = toBePatched - 1; i >= 0; i--) {
210 | const nextIndex = i + s2;
211 | const nextChild = c2[nextIndex];
212 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
213 | if (newIndexToOldIndexMap[i] === 0) {
214 | // 新节点没有在旧节点中找到,给了个标识,直接在这里进行插入
215 | patch(null, nextChild, container, parentComponent, anchor);
216 | } else if (moved) {
217 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
218 | // 这时对顺序进行调整
219 | hostInsert(nextChild.el, container, anchor);
220 | } else {
221 | j--;
222 | }
223 | }
224 | }
225 | }
226 | }
227 |
228 | function unmountChildren(children) {
229 | for (let i = 0; i < children.length; i++) {
230 | const el = children[i].el;
231 | hostRemove(el);
232 | }
233 | }
234 |
235 | function patchProps(el, oldProps, newProps) {
236 | if (oldProps !== newProps) {
237 | for (const key in newProps) {
238 | const prevProp = oldProps[key];
239 | const nextProp = newProps[key];
240 |
241 | if (prevProp !== nextProp) {
242 | hostPatchProp(el, key, prevProp, nextProp);
243 | }
244 | }
245 |
246 | if (JSON.stringify(oldProps) !== "{}") {
247 | for (const key in oldProps) {
248 | if (!(key in newProps)) {
249 | hostPatchProp(el, key, oldProps[key], null);
250 | }
251 | }
252 | }
253 | }
254 | }
255 |
256 | function processComponent(
257 | n1: any,
258 | n2: any,
259 | container: any,
260 | parentComponent: any,
261 | anchor: any
262 | ) {
263 | mountComponent(n2, container, parentComponent, anchor);
264 | }
265 |
266 | function mountElement(vnode, container, parentComponent, anchor) {
267 | // createElement
268 | const el = (vnode.el = hostCreateElement(vnode.type));
269 |
270 | const { children, shapeFlag } = vnode;
271 | // children
272 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
273 | el.textContent = children;
274 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
275 | mountChildren(vnode.children, el, parentComponent, anchor);
276 | }
277 |
278 | // props
279 | const { props } = vnode;
280 | for (const key in props) {
281 | hostPatchProp(el, key, null, props[key]);
282 | }
283 |
284 | hostInsert(el, container, anchor);
285 | }
286 |
287 | function mountChildren(children, container, parentComponent, anchor) {
288 | children.forEach((v) => {
289 | patch(null, v, container, parentComponent, anchor);
290 | });
291 | }
292 |
293 | function mountComponent(
294 | initialVNode: any,
295 | container: any,
296 | parentComponent: any,
297 | anchor: any
298 | ) {
299 | const instance = createComponentInstance(initialVNode, parentComponent);
300 |
301 | setupComponent(instance);
302 | setupRenderEffect(instance, initialVNode, container, anchor);
303 | }
304 |
305 | function setupRenderEffect(instance: any, initialVNode, container, anchor) {
306 | effect(() => {
307 | if (!instance.isMounted) {
308 | console.log("init");
309 | const { proxy } = instance;
310 | const subTree = (instance.subTree = instance.render.call(proxy));
311 |
312 | patch(null, subTree, container, instance, anchor);
313 |
314 | initialVNode.el = subTree.el;
315 |
316 | instance.isMounted = true;
317 | } else {
318 | console.log("update");
319 | const { proxy } = instance;
320 | const subTree = instance.render.call(proxy);
321 | const prevSubTree = instance.subTree;
322 | instance.subTree = subTree;
323 |
324 | patch(prevSubTree, subTree, container, instance, anchor);
325 | }
326 | });
327 | }
328 |
329 | return {
330 | createApp: createAppAPI(render),
331 | };
332 | }
333 |
334 | function getSequence(arr) {
335 | const p = arr.slice();
336 | const result = [0];
337 | let i, j, u, v, c;
338 | const len = arr.length;
339 | for (i = 0; i < len; i++) {
340 | const arrI = arr[i];
341 | if (arrI !== 0) {
342 | j = result[result.length - 1];
343 | if (arr[j] < arrI) {
344 | p[i] = j;
345 | result.push(i);
346 | continue;
347 | }
348 | u = 0;
349 | v = result.length - 1;
350 | while (u < v) {
351 | c = (u + v) >> 1;
352 | if (arr[result[c]] < arrI) {
353 | u = c + 1;
354 | } else {
355 | v = c;
356 | }
357 | }
358 | if (arrI < arr[result[u]]) {
359 | if (u > 0) {
360 | p[i] = result[u - 1];
361 | }
362 | result[u] = i;
363 | }
364 | }
365 | }
366 | u = result.length;
367 | v = result[u - 1];
368 | while (u-- > 0) {
369 | result[u] = v;
370 | v = p[v];
371 | }
372 | return result;
373 | }
374 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export const Fragment = Symbol("Fragment");
4 | export const Text = Symbol("Text");
5 |
6 | export function createVNode(type, props?, children?) {
7 | const vnode = {
8 | type,
9 | props,
10 | children,
11 | el: null,
12 | shapeFlag: getShapeType(type),
13 | key: props && props.key,
14 | };
15 | if (typeof children === "string") {
16 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
17 | } else if (Array.isArray(children)) {
18 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
19 | }
20 |
21 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
22 | if (typeof children === "object") {
23 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
24 | }
25 | }
26 | return vnode;
27 | }
28 |
29 | export function createTextVNode(text: string) {
30 | return createVNode(Text, {}, text);
31 | }
32 |
33 | function getShapeType(type) {
34 | return typeof type === "string"
35 | ? ShapeFlags.ELEMENT
36 | : ShapeFlags.STATEFUL_COMPONENT;
37 | }
38 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../runtime-core";
2 |
3 | function createElement(type) {
4 | return document.createElement(type);
5 | }
6 |
7 | function patchProp(el, key, oldValue, nextValue) {
8 | const isOn = (key: string) => /^on[A-Z]/.test(key);
9 | if (isOn(key)) {
10 | const event = key.slice(2).toLowerCase();
11 | el.addEventListener(event, nextValue);
12 | } else {
13 | if (nextValue == null) {
14 | el.removeAttribute(key);
15 | } else {
16 | el.setAttribute(key, nextValue);
17 | }
18 | }
19 | }
20 |
21 | function insert(child, parent, anchor) {
22 | parent.insertBefore(child, anchor || null);
23 | }
24 |
25 | function remove(child) {
26 | const parent = child.parentNode;
27 | if (parent) {
28 | parent.removeChild(child);
29 | }
30 | }
31 |
32 | function setElementText(el, text) {
33 | el.textContent = text;
34 | }
35 |
36 | const renderer: any = createRenderer({
37 | createElement,
38 | patchProp,
39 | insert,
40 | remove,
41 | setElementText,
42 | });
43 |
44 | export function createApp(...args) {
45 | return renderer.createApp(...args);
46 | }
47 |
48 | export * from "../runtime-core";
49 |
--------------------------------------------------------------------------------
/src/shared/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, // 0001
3 | STATEFUL_COMPONENT = 1 << 1, // 0010
4 | TEXT_CHILDREN = 1 << 2, // 0100
5 | ARRAY_CHILDREN = 1 << 3, // 1000
6 | SLOT_CHILDREN = 1 << 4,
7 | }
8 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export const extend = Object.assign;
2 |
3 | export const isObject = (value) => {
4 | return value !== null && typeof value === "object";
5 | };
6 |
7 | export const hasChanged = (val, newValue) => {
8 | return !Object.is(val, newValue);
9 | };
10 |
11 | export const hasOwn = (val, key) =>
12 | Object.prototype.hasOwnProperty.call(val, key);
13 |
14 | export const camelize = (str: string) => {
15 | return str.replace(/-(\w)/g, (_, c: string) => {
16 | return c ? c.toUpperCase() : "";
17 | });
18 | };
19 |
20 | const capitalize = (str: string) => {
21 | return str.charAt(0).toUpperCase() + str.slice(1);
22 | };
23 |
24 | export const toHandlerKey = (str: string) => {
25 | return str ? "on" + capitalize(str) : "";
26 | };
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15 | "lib": [
16 | "DOM",
17 | "es6"
18 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
19 | // "jsx": "preserve", /* Specify what JSX code is generated. */
20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
25 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
28 |
29 | /* Modules */
30 | "module": "esnext" /* Specify what module code is generated. */,
31 | // "rootDir": "./", /* Specify the root folder within your source files. */
32 | "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
36 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
37 | "types": [
38 | "jest"
39 | ] /* Specify type package names to be included without being referenced in a source file. */,
40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
41 | // "resolveJsonModule": true, /* Enable importing .json files */
42 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
43 |
44 | /* JavaScript Support */
45 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
46 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
47 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
48 |
49 | /* Emit */
50 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
51 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
52 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
53 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
54 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
55 | // "outDir": "./", /* Specify an output folder for all emitted files. */
56 | // "removeComments": true, /* Disable emitting comments. */
57 | // "noEmit": true, /* Disable emitting files from a compilation. */
58 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
59 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
60 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
61 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
63 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
64 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
65 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
66 | // "newLine": "crlf", /* Set the newline character for emitting files. */
67 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
68 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
69 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
70 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
71 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
72 |
73 | /* Interop Constraints */
74 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
75 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
76 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
77 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
78 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
79 |
80 | /* Type Checking */
81 | "strict": true /* Enable all strict type-checking options. */,
82 | "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
83 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
84 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
85 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
86 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
87 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
88 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
89 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
90 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
91 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
92 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
93 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
94 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
95 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
96 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
97 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
98 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
99 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
100 |
101 | /* Completeness */
102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
103 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
104 | }
105 | }
106 |
--------------------------------------------------------------------------------