├── .gitignore
├── .vscode
└── settings.json
├── App.js
├── README.md
├── babel.config.js
├── core
├── index.js
└── reactivity
│ └── index.js
├── example
├── componemtSlot
│ ├── APP.js
│ ├── foo.js
│ ├── index.html
│ └── main.js
├── componentEmit
│ ├── APP.js
│ ├── foo.js
│ ├── index.html
│ └── main.js
├── getcurrentinstance
│ ├── APP.js
│ ├── foo.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
├── projectInject
│ ├── APP.js
│ ├── foo.js
│ ├── index.html
│ └── main.js
├── reactivity
│ ├── APP.js
│ ├── foo.js
│ ├── index.html
│ └── main.js
└── update
│ ├── APP.js
│ ├── foo.js
│ ├── index.html
│ └── main.js
├── index.html
├── lib
├── guide.cjs.js
└── guide.esm.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── index.js
├── index.ts
├── reactivity
│ ├── bseHandlers.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ ├── shared
│ │ └── index.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── componemtPublicInstance.ts
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentSlots.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helper
│ │ └── renderSlot.ts
│ ├── index.ts
│ ├── render.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ └── sharedFlags.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "compile-hero.disable-compile-files-on-did-save-code": true
3 | }
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | render(context) { },
4 | setup() {
5 |
6 | }
7 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### 初始化项目
4 |
5 |
6 | ## 简易版mini-vue的实现
7 |
8 | 解决jest报错 yarn add jest @types/jest --dev
9 |
10 | 响应式文件夹
11 | runtime-core
12 | runtime-dom
13 | shared 主要方法函数
14 |
15 | jest 报错需要安装babel并且创建babel.js
16 | yarn add --dev babel-jest @babel/core @babel/preset-env
17 | yarn add --dev @babel/preset-typescript //支持typescript
18 |
19 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {
4 | targets: {
5 | node: 'current'
6 | }
7 | }],
8 | '@babel/preset-typescript',
9 | ],
10 | };
--------------------------------------------------------------------------------
/core/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | effectWatcher
3 | } from './reactivity/index'
4 | export function createApp(rootComponent) {
5 | return {
6 | mount(rootContainer) {
7 | const context = rootComponent.setup()
8 | effectWatcher(() => {
9 | const element = rootComponent.render(context)
10 | rootContainer.appendChild(element)
11 | })
12 |
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/core/reactivity/index.js:
--------------------------------------------------------------------------------
1 | let currentEffect = null;
2 | class Dep {
3 | constructor(val) {
4 | this.effects = new Set();
5 | this._val = val;
6 | }
7 | get value() {
8 | this.depend()
9 | return this._val;
10 | }
11 | set value(newValue) {
12 | this._val = newValue;
13 | this.notices()
14 | }
15 | // 1收集依赖
16 | depend() {
17 | console.log('收集依赖', currentEffect)
18 | if (currentEffect) {
19 |
20 | this.effects.add(currentEffect)
21 | }
22 | }
23 | // 触发依赖
24 | notices() {
25 | console.log('触发了===notices', this.effects.size)
26 | // c触发一下我们之前收集到的依赖
27 | this.effects.forEach(effect => {
28 | effect();
29 | })
30 | }
31 |
32 | }
33 | const dep = new Dep(123)
34 |
35 | function effectWatch(effect) {
36 | // 收集依赖
37 |
38 | currentEffect = effect
39 | effect()
40 | // dep.depend()
41 | currentEffect = null
42 | }
43 | let b;
44 | // effectWatch(() => {
45 |
46 | // b = dep.value + 10
47 | // console.log('hello',b)
48 | // })
49 | // dep.value = 11
50 | // dep.notices()
51 |
52 |
53 | const targetMap = new Map()
54 |
55 | // 收集依赖
56 | function getDep(target, key) {
57 | let depsMap = targetMap.get(target);
58 | if (!depsMap) {
59 | depsMap = new Map();
60 | targetMap.set(target, depsMap)
61 | }
62 | let deps = depsMap.get(key);
63 | if (!deps) {
64 | deps = new Dep();
65 | depsMap.set(key, deps)
66 | }
67 | return deps;
68 | }
69 |
70 | function reactive(raw) {
71 | return new Proxy(raw, {
72 | get(target, key) {
73 | const deps = getDep(target, key);
74 | console.log('key===', key)
75 | // 依赖收集
76 | deps.depend()
77 | return Reflect.get(target, key)
78 | },
79 | set(target, key, value) {
80 | // 触发依赖
81 | const deps = getDep(target, key);
82 | console.log('set===', deps)
83 | const result = Reflect.set(target, key, value)
84 | deps.notices();
85 | return result;
86 | }
87 | })
88 | }
89 | const user = reactive({
90 | age: 12
91 | })
92 | let double;
93 | effectWatch(() => {
94 | console.log('effectWatch=====')
95 | double = user.age * 2
96 | console.log('double', double)
97 | })
98 | user.age = 14
--------------------------------------------------------------------------------
/example/componemtSlot/APP.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | createTextVnode
4 | } from '../../lib/guide.esm.js'
5 | import {
6 | Foo
7 | } from './foo.js'
8 | export const APP = {
9 | render() {
10 | window.self = this;
11 | const app = h('div', {
12 | id: 'app'
13 | }, 'app')
14 | const foo = h(Foo, {}, {
15 | footer: () => [h('p', {}, 'slots111'), createTextVnode('你好呀text节点')],
16 | header: ({
17 | age
18 | }) => h('p', {}, 'slots222' + age)
19 | })
20 | // const foo = h(Foo, {}, h('p', {}, 'slots111'))
21 |
22 | return h('div', {}, [app, foo])
23 | },
24 | setup() {
25 | return {
26 | name: 'world12345666'
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/example/componemtSlot/foo.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | renderSlot
4 | } from '../../lib/guide.esm.js'
5 | export const Foo = {
6 | setup() {},
7 | render() {
8 | console.log('slots', this.$slots)
9 | const foo = h('p', {}, 'p元素foo')
10 | // renderSlot(this.$slots, "footer")
11 | return h('div', {}, [renderSlot(this.$slots, "header",{age:18}), foo,])
12 |
13 | }
14 | }
--------------------------------------------------------------------------------
/example/componemtSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/componemtSlot/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | createApp
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | APP
6 | } from './APP.js'
7 | const rootContainer=document.getElementById('app')
8 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/componentEmit/APP.js:
--------------------------------------------------------------------------------
1 | import {
2 | h
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | Foo
6 | } from './foo.js'
7 | export const APP = {
8 | render() {
9 | window.self = this;
10 | return h('div', {
11 | id: 'app111',
12 | class: 'name',
13 | },
14 | [h('div', {
15 | id: 'red'
16 | }, '测试emit功能'), h(Foo, {
17 | onAdd(a,v) {
18 | console.log('我是APP组件的onAdd事件',a,v);
19 | },
20 | onAddFoo(a, v) {
21 | console.log('我是APP组件的onAddFoo事件', a, v);
22 | }
23 | })]
24 | );
25 | },
26 | setup() {
27 | return {
28 | name: 'world12345666'
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/example/componentEmit/foo.js:
--------------------------------------------------------------------------------
1 | import {
2 | h
3 | } from '../../lib/guide.esm.js'
4 | export const Foo = {
5 | setup(props, {
6 | emit
7 | }) {
8 | console.log('props', props);
9 | const emitAdd = () => {
10 | console.log('我是emitAdd事件');
11 | emit('Add', 1, 2)
12 | emit('add-foo', 3, 4)
13 | }
14 | return {
15 | emitAdd
16 | }
17 | },
18 | render() {
19 | const btn = h('button', {
20 | onClick: this.emitAdd
21 | }, 'emitAdd')
22 | const foo = h('p', {}, '我是foo组件')
23 | return h('div', {
24 | id: 'foo'
25 | }, [btn, foo]);
26 | }
27 | }
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | createApp
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | APP
6 | } from './APP.js'
7 | const rootContainer=document.getElementById('app')
8 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/getcurrentinstance/APP.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,getCurrentInstance
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | Foo
6 | } from './foo.js'
7 | export const APP = {
8 | render() {
9 | window.self = this;
10 | return h('div', {
11 | id: 'app111',
12 | class: 'name',
13 | },
14 | [h('div', {
15 | id: 'red'
16 | }, '测试emit功能'), h(Foo, {
17 | onAdd(a,v) {
18 | console.log('我是APP组件的onAdd事件',a,v);
19 | },
20 | onAddFoo(a, v) {
21 | console.log('我是APP组件的onAddFoo事件', a, v);
22 | }
23 | })]
24 | );
25 | },
26 | setup() {
27 | const instance = getCurrentInstance()
28 | console.log('instanceAPP', instance)
29 | return {
30 | name: 'world12345666'
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/example/getcurrentinstance/foo.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,getCurrentInstance
3 | } from '../../lib/guide.esm.js'
4 | export const Foo = {
5 | setup(props, {
6 | emit
7 | }) {
8 | const instance = getCurrentInstance()
9 | console.log('instanceFoo------', instance)
10 | const emitAdd = () => {
11 | console.log('我是emitAdd事件');
12 | emit('Add', 1, 2)
13 | emit('add-foo', 3, 4)
14 | }
15 | return {
16 | emitAdd
17 | }
18 | },
19 | render() {
20 | const btn = h('button', {
21 | onClick: this.emitAdd
22 | }, 'emitAdd')
23 | const foo = h('p', {}, '我是foo组件')
24 | return h('div', {
25 | id: 'foo'
26 | }, [btn, foo]);
27 | }
28 | }
--------------------------------------------------------------------------------
/example/getcurrentinstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/getcurrentinstance/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | createApp
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | APP
6 | } from './APP.js'
7 | const rootContainer=document.getElementById('app')
8 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | h
3 | } from "../../lib/guide.esm.js";
4 |
5 | import ArrayToText from "./ArrayToText.js";
6 | import TextToText from "./TextToText.js";
7 | import TextToArray from "./TextToArray.js";
8 | import ArrayToArray from "./ArrayToArray.js";
9 |
10 | export default {
11 | name: "App",
12 | setup() {},
13 |
14 | render() {
15 | return h("div", {
16 | tId: 1
17 | }, [
18 | h("p", {}, "主页"),
19 | // 老的是 array 新的是 text
20 | // h(ArrayToText),
21 | // 老的是 text 新的是 text
22 | // h(TextToText),
23 | // 老的是 text 新的是 array
24 | // h(TextToArray)
25 | // 老的是 array 新的是 array
26 | h(ArrayToArray),
27 | ]);
28 | },
29 | };
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import {
5 | ref,
6 | h
7 | } from "../../lib/guide.esm.js";
8 |
9 | // 1. 左侧的对比
10 | // (a b) c
11 | // (a b) d e
12 | // const prevChildren = [
13 | // h("p", { key: "A" }, "A"),
14 | // h("p", { key: "B" }, "B"),
15 | // h("p", { key: "C" }, "C"),
16 | // ];
17 | // const nextChildren = [
18 | // h("p", { key: "A" }, "A"),
19 | // h("p", { key: "B" }, "B"),
20 | // h("p", { key: "D" }, "D"),
21 | // h("p", { key: "E" }, "E"),
22 | // ];
23 |
24 | // 2. 右侧的对比
25 | // a (b c)
26 | // d e (b c)
27 | // const prevChildren = [
28 | // h("p", { key: "A" }, "A"),
29 | // h("p", { key: "B" }, "B"),
30 | // h("p", { key: "C" }, "C"),
31 | // ];
32 | // const nextChildren = [
33 | // h("p", { key: "D" }, "D"),
34 | // h("p", { key: "E" }, "E"),
35 | // h("p", { key: "B" }, "B"),
36 | // h("p", { key: "C" }, "C"),
37 | // ];
38 |
39 | // 3. 新的比老的长
40 | // 创建新的
41 | // 左侧
42 | // (a b)
43 | // (a b) c
44 | // i = 2, e1 = 1, e2 = 2
45 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
46 | // const nextChildren = [
47 | // h("p", { key: "A" }, "A"),
48 | // h("p", { key: "B" }, "B"),
49 | // h("p", { key: "C" }, "C"),
50 | // h("p", { key: "D" }, "D"),
51 | // ];
52 |
53 | // 右侧
54 | // (a b)
55 | // c (a b)
56 | // i = 0, e1 = -1, e2 = 0
57 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
58 | // const nextChildren = [
59 | // h("p", { key: "C" }, "C"),
60 | // h("p", { key: "A" }, "A"),
61 | // h("p", { key: "B" }, "B"),
62 | // ];
63 |
64 | // 4. 老的比新的长
65 | // 删除老的
66 | // 左侧
67 | // (a b) c
68 | // (a b)
69 | // i = 2, e1 = 2, e2 = 1
70 | // const prevChildren = [
71 | // h("p", { key: "A" }, "A"),
72 | // h("p", { key: "B" }, "B"),
73 | // h("p", { key: "C" }, "C"),
74 | // ];
75 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
76 |
77 | // 右侧
78 | // a (b c)
79 | // (b c)
80 | // i = 0, e1 = 0, e2 = -1
81 |
82 | // const prevChildren = [
83 | // h("p", { key: "A" }, "A"),
84 | // h("p", { key: "B" }, "B"),
85 | // h("p", { key: "C" }, "C"),
86 | // ];
87 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
88 |
89 | // 5. 对比中间的部分
90 | // 删除老的 (在老的里面存在,新的里面不存在)
91 | // 5.1
92 | // a,b,(c,d),f,g
93 | // a,b,(e,c),f,g
94 | // D 节点在新的里面是没有的 - 需要删除掉
95 | // C 节点 props 也发生了变化
96 |
97 | // const prevChildren = [
98 | // h("p", { key: "A" }, "A"),
99 | // h("p", { key: "B" }, "B"),
100 | // h("p", { key: "C", id: "c-prev" }, "C"),
101 | // h("p", { key: "D" }, "D"),
102 | // h("p", { key: "F" }, "F"),
103 | // h("p", { key: "G" }, "G"),
104 | // ];
105 |
106 | // const nextChildren = [
107 | // h("p", { key: "A" }, "A"),
108 | // h("p", { key: "B" }, "B"),
109 | // h("p", { key: "E" }, "E"),
110 | // h("p", { key: "C", id:"c-next" }, "C"),
111 | // h("p", { key: "F" }, "F"),
112 | // h("p", { key: "G" }, "G"),
113 | // ];
114 |
115 | // 5.1.1
116 | // a,b,(c,e,d),f,g
117 | // a,b,(e,c),f,g
118 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
119 | // const prevChildren = [
120 | // h("p", { key: "A" }, "A"),
121 | // h("p", { key: "B" }, "B"),
122 | // h("p", { key: "C", id: "c-prev" }, "C"),
123 | // h("p", { key: "E" }, "E"),
124 | // h("p", { key: "D" }, "D"),
125 | // h("p", { key: "F" }, "F"),
126 | // h("p", { key: "G" }, "G"),
127 | // ];
128 |
129 | // const nextChildren = [
130 | // h("p", { key: "A" }, "A"),
131 | // h("p", { key: "B" }, "B"),
132 | // h("p", { key: "E" }, "E"),
133 | // h("p", { key: "C", id:"c-next" }, "C"),
134 | // h("p", { key: "F" }, "F"),
135 | // h("p", { key: "G" }, "G"),
136 | // ];
137 |
138 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
139 |
140 | // 2.1
141 | // a,b,(c,d,e),f,g
142 | // a,b,(e,c,d),f,g
143 | // 最长子序列: [1,2]
144 |
145 | // const prevChildren = [
146 | // h("p", { key: "A" }, "A"),
147 | // h("p", { key: "B" }, "B"),
148 | // h("p", { key: "C" }, "C"),
149 | // h("p", { key: "D" }, "D"),
150 | // h("p", { key: "E" }, "E"),
151 | // h("p", { key: "F" }, "F"),
152 | // h("p", { key: "G" }, "G"),
153 | // ];
154 |
155 | // const nextChildren = [
156 | // h("p", { key: "A" }, "A"),
157 | // h("p", { key: "B" }, "B"),
158 | // h("p", { key: "E" }, "E"),
159 | // h("p", { key: "C" }, "C"),
160 | // h("p", { key: "D" }, "D"),
161 | // h("p", { key: "F" }, "F"),
162 | // h("p", { key: "G" }, "G"),
163 | // ];
164 |
165 | // 3. 创建新的节点
166 | // a,b,(c,e),f,g
167 | // a,b,(e,c,d),f,g
168 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
169 | // const prevChildren = [
170 | // h("p", { key: "A" }, "A"),
171 | // h("p", { key: "B" }, "B"),
172 | // h("p", { key: "C" }, "C"),
173 | // h("p", { key: "E" }, "E"),
174 | // h("p", { key: "F" }, "F"),
175 | // h("p", { key: "G" }, "G"),
176 | // ];
177 |
178 | // const nextChildren = [
179 | // h("p", { key: "A" }, "A"),
180 | // h("p", { key: "B" }, "B"),
181 | // h("p", { key: "E" }, "E"),
182 | // h("p", { key: "C" }, "C"),
183 | // h("p", { key: "D" }, "D"),
184 | // h("p", { key: "F" }, "F"),
185 | // h("p", { key: "G" }, "G"),
186 | // ];
187 |
188 | // 综合例子
189 | // a,b,(c,d,e,z),f,g
190 | // a,b,(d,c,y,e),f,g
191 |
192 | // const prevChildren = [
193 | // h("p", { key: "A" }, "A"),
194 | // h("p", { key: "B" }, "B"),
195 | // h("p", { key: "C" }, "C"),
196 | // h("p", { key: "D" }, "D"),
197 | // h("p", { key: "E" }, "E"),
198 | // h("p", { key: "Z" }, "Z"),
199 | // h("p", { key: "F" }, "F"),
200 | // h("p", { key: "G" }, "G"),
201 | // ];
202 |
203 | // const nextChildren = [
204 | // h("p", { key: "A" }, "A"),
205 | // h("p", { key: "B" }, "B"),
206 | // h("p", { key: "D" }, "D"),
207 | // h("p", { key: "C" }, "C"),
208 | // h("p", { key: "Y" }, "Y"),
209 | // h("p", { key: "E" }, "E"),
210 | // h("p", { key: "F" }, "F"),
211 | // h("p", { key: "G" }, "G"),
212 | // ];
213 |
214 | // fix c 节点应该是 move 而不是删除之后重新创建的
215 | const prevChildren = [
216 | h("p", {
217 | key: "A"
218 | }, "A"),
219 | h("p", {}, "C"),
220 | h("p", {
221 | key: "B"
222 | }, "B"),
223 | h("p", {
224 | key: "D"
225 | }, "D"),
226 | ];
227 |
228 | const nextChildren = [
229 | h("p", {
230 | key: "A"
231 | }, "A"),
232 | h("p", {
233 | key: "B"
234 | }, "B"),
235 | h("p", {}, "C"),
236 | h("p", {
237 | key: "D"
238 | }, "D"),
239 | ];
240 |
241 | export default {
242 | name: "ArrayToArray",
243 | setup() {
244 | const isChange = ref(false);
245 | window.isChange = isChange;
246 |
247 | return {
248 | isChange,
249 | };
250 | },
251 | render() {
252 | const self = this;
253 |
254 | return self.isChange === true ?
255 | h("div", {}, nextChildren) :
256 | h("div", {}, prevChildren);
257 | },
258 | };
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import {
5 | ref,
6 | h
7 | } from "../../lib/guide.esm.js";
8 | const nextChildren = "newChildren";
9 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
10 |
11 | export default {
12 | name: "ArrayToText",
13 | setup() {
14 | const isChange = ref(false);
15 | window.isChange = isChange;
16 |
17 | return {
18 | isChange,
19 | };
20 | },
21 | render() {
22 | const self = this;
23 |
24 | return self.isChange === true ?
25 | h("div", {}, nextChildren) :
26 | h("div", {}, prevChildren);
27 | },
28 | };
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import {
4 | ref,
5 | h
6 | } from "../../lib/guide.esm.js";
7 |
8 | const prevChildren = "oldChild";
9 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
10 |
11 | export default {
12 | name: "TextToArray",
13 | setup() {
14 | const isChange = ref(false);
15 | window.isChange = isChange;
16 |
17 | return {
18 | isChange,
19 | };
20 | },
21 | render() {
22 | const self = this;
23 |
24 | return self.isChange === true ?
25 | h("div", {}, nextChildren) :
26 | h("div", {}, prevChildren);
27 | },
28 | };
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import {
4 | ref,
5 | h
6 | } from "../../lib/guide.esm.js";
7 |
8 | const prevChildren = "oldChild";
9 | const nextChildren = "newChild";
10 |
11 | export default {
12 | name: "TextToText",
13 | setup() {
14 | const isChange = ref(false);
15 | window.isChange = isChange;
16 |
17 | return {
18 | isChange,
19 | };
20 | },
21 | render() {
22 | const self = this;
23 |
24 | return self.isChange === true ?
25 | h("div", {}, nextChildren) :
26 | h("div", {}, prevChildren);
27 | },
28 | };
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | createApp
3 | } from "../../lib/guide.esm.js";
4 | import App from "./App.js";
5 |
6 | const rootContainer = document.querySelector("#root");
7 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/projectInject/APP.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | provide
4 | } from '../../lib/guide.esm.js'
5 | import {
6 | Foo
7 | } from './foo.js'
8 | export const APP = {
9 | render() {
10 | window.self = this;
11 |
12 | return h('div', {
13 | id: 'app111',
14 | class: 'name',
15 | },
16 | [h('div', {
17 | id: 'red'
18 | }, '测试emit功能'), h(Foo, {
19 | onAdd(a, v) {
20 | console.log('我是APP组件的onAdd事件', a, v);
21 | },
22 | onAddFoo(a, v) {
23 | console.log('我是APP组件的onAddFoo事件', a, v);
24 | }
25 | })]
26 | );
27 | },
28 | setup() {
29 | provide('foo', 'foo111')
30 | provide('bar', 'bar2222')
31 | return {
32 | name: 'world12345666'
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/example/projectInject/foo.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | inject
4 |
5 | } from '../../lib/guide.esm.js'
6 | export const Foo = {
7 | setup(props) {
8 | const foo = inject('foo')
9 | const bar = inject('bar')
10 | console.log('inject==========',foo, bar)
11 | return {
12 | name:foo,
13 | bar:bar,
14 | name1:123
15 | }
16 | },
17 | render() {
18 | console.log('fooooooo',bar)
19 | return h('div', {
20 | id: 'foo'
21 | }, `inject:foo${name}`);
22 | }
23 | }
--------------------------------------------------------------------------------
/example/projectInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/projectInject/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | createApp
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | APP
6 | } from './APP.js'
7 | const rootContainer=document.getElementById('app')
8 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/reactivity/APP.js:
--------------------------------------------------------------------------------
1 | import {
2 | h
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | Foo
6 | } from './foo.js'
7 | export const APP = {
8 | render() {
9 | window.self = this;
10 | return h('div', {
11 | id: 'app111',
12 | class: 'name',
13 | onClick: () => {
14 | console.log('onclick');
15 | }
16 | },
17 | [h('div', {
18 | id: 'red'
19 | }, 'red'), h(Foo,{count:1})]
20 | // 'hello world' + this.name
21 | // [h('p', {
22 | // id: "red",
23 | // class:['mo','text']
24 | // }, '子组件1'), h('p', {
25 | // id: "blue"
26 | // }, '子组件2')]
27 | );
28 | },
29 | setup() {
30 | return {
31 | name: 'world12345666'
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/example/reactivity/foo.js:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | h
4 | } from '../../lib/guide.esm.js'
5 | export const Foo = {
6 | setup(props) {
7 | console.log('props', props);
8 | },
9 | render() {
10 | return h('div', {
11 | id: 'foo'
12 | }, "foo" + this.count);
13 | }
14 | }
--------------------------------------------------------------------------------
/example/reactivity/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/reactivity/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | createApp
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | APP
6 | } from './APP.js'
7 | const rootContainer=document.getElementById('app')
8 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/update/APP.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,getCurrentInstance
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | Foo
6 | } from './foo.js'
7 | export const APP = {
8 | render() {
9 | window.self = this;
10 | return h('div', {
11 | id: 'app111',
12 | class: 'name',
13 | },
14 | [h('div', {
15 | id: 'red'
16 | }, '测试emit功能'), h(Foo, {
17 | onAdd(a,v) {
18 | console.log('我是APP组件的onAdd事件',a,v);
19 | },
20 | onAddFoo(a, v) {
21 | console.log('我是APP组件的onAddFoo事件', a, v);
22 | }
23 | })]
24 | );
25 | },
26 | setup() {
27 | const instance = getCurrentInstance()
28 | console.log('instanceAPP', instance)
29 | return {
30 | name: 'world12345666'
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/example/update/foo.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | getCurrentInstance,
4 | ref
5 | } from '../../lib/guide.esm.js'
6 | export const Foo = {
7 | setup(props, {
8 | emit
9 | }) {
10 | const instance = getCurrentInstance()
11 | console.log('instanceFoo------', instance)
12 | const emitAdd = () => {
13 | console.log('我是emitAdd事件');
14 | // emit('Add', 1, 2)
15 | // emit('add-foo', 3, 4)
16 | count.value++
17 | // id.value='new-foo'
18 | id.value=undefined;
19 | }
20 | const count=ref(11)
21 | const id=ref('foo123')
22 | return {
23 | count,
24 | emitAdd,
25 | id
26 | }
27 | },
28 | render() {
29 | const btn = h('button', {
30 | onClick: this.emitAdd
31 | }, 'emitAdd')
32 | const foo = h('p', {}, '我是foo组件'+this.count.value)
33 | return h('div', {
34 | id: this.id.value
35 | }, [btn, foo]);
36 | }
37 | }
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | createApp
3 | } from '../../lib/guide.esm.js'
4 | import {
5 | APP
6 | } from './APP.js'
7 | const rootContainer=document.getElementById('app')
8 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lib/guide.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const extend = Object.assign;
6 | function isObject(obj) {
7 | return obj !== null && typeof obj === 'object';
8 | }
9 | function hasChange(val, newValue) {
10 | return !Object.is(val, newValue);
11 | }
12 |
13 | let activeEffect;
14 | let shouldTrick;
15 | class ReactiveEffect {
16 | constructor(fn, scheduler) {
17 | this.scheduler = scheduler;
18 | this.deps = [];
19 | this.active = true;
20 | this._fn = fn;
21 | }
22 | run() {
23 | if (!this.active) {
24 | return this._fn();
25 | }
26 | shouldTrick = true;
27 | // 收集依赖
28 | activeEffect = this;
29 | const result = this._fn();
30 | shouldTrick = false;
31 | return result;
32 | }
33 | stop() {
34 | if (this.active) {
35 | clearUpEffect(this);
36 | this.onStop && this.onStop();
37 | this.active = false;
38 | }
39 | }
40 | }
41 | function clearUpEffect(effect) {
42 | effect.deps.forEach((dep) => {
43 | dep.delete(effect);
44 | });
45 | }
46 | // 依赖收集
47 | const targetMap = new Map();
48 | function track(target, key) {
49 | if (!isTracking())
50 | return;
51 | let depsMap = targetMap.get(target);
52 | if (!depsMap) {
53 | depsMap = new Map();
54 | targetMap.set(target, depsMap);
55 | }
56 | let dep = depsMap.get(key);
57 | if (!dep) {
58 | dep = new Set();
59 | depsMap.set(key, dep);
60 | }
61 | trackEffect(dep);
62 | }
63 | function trackEffect(dep) {
64 | if (dep.has(activeEffect))
65 | return;
66 | dep.add(activeEffect);
67 | activeEffect.deps.push(dep);
68 | }
69 | function isTracking() {
70 | return activeEffect !== undefined && shouldTrick;
71 | }
72 | function effect(fn, options = {}) {
73 | // 上来就要调用fn
74 | const scheduler = options.scheduler; //获取
75 | const _effect = new ReactiveEffect(fn, scheduler);
76 | extend(_effect, options); //挂载所有options的值
77 | _effect.run();
78 | const runner = _effect.run.bind(_effect);
79 | runner.effect = _effect;
80 | return runner;
81 | }
82 | function trigger(target, key) {
83 | let depsMap = targetMap.get(target);
84 | let deps = depsMap.get(key);
85 | triggerEffect(deps);
86 | }
87 | function triggerEffect(deps) {
88 | for (const effect of deps) {
89 | if (effect.scheduler) {
90 | effect.scheduler();
91 | }
92 | else {
93 | effect.run();
94 | }
95 | }
96 | }
97 |
98 | function hostPatchProp(el, key, prevValue, nextValue) {
99 | const isOn = (key) => /^on[A-Z]/.test(key);
100 | if (isOn(key)) {
101 | const event = key.slice(2).toLowerCase();
102 | el.addEventListener(event, nextValue);
103 | }
104 | else {
105 | if (nextValue === undefined || nextValue === null) {
106 | el.removeAttribute(key, nextValue);
107 | }
108 | else {
109 | el.setAttribute(key, nextValue);
110 | }
111 | }
112 | }
113 |
114 | const set = createSetter();
115 | const get = createGetter();
116 | const readonlyGet = createGetter(true);
117 | const shallowReadonlyGet = createGetter(true, true);
118 | function createGetter(isReadOnly = false, shallow = false) {
119 | return function get(target, key) {
120 | if (key === "__v_isReactive__" /* IS_REACTIVE */) {
121 | return !isReadOnly;
122 | }
123 | else if (key === "__v_isReadonly__" /* IS_READ_ONLY */) {
124 | return isReadOnly;
125 | }
126 | const res = Reflect.get(target, key);
127 | if (shallow) {
128 | // 如果是shallow就不需要深层次的观察
129 | return res;
130 | }
131 | // 看看是不是object
132 | if (isObject(res)) {
133 | return isReadOnly ? readonly(res) : reactive(res);
134 | }
135 | if (!isReadOnly) {
136 | track(target, key);
137 | }
138 | return res;
139 | };
140 | }
141 | function createSetter() {
142 | return function get(target, key, value) {
143 | const res = Reflect.set(target, key, value);
144 | trigger(target, key);
145 | return res;
146 | };
147 | }
148 | const mutableHandlers = {
149 | get,
150 | set,
151 | };
152 | const readonlyHandlers = {
153 | get: readonlyGet,
154 | set(target, key, value) {
155 | // readonly的时候不允许set
156 | console.warn(`Attempting to mutate readonly value ${`${target}[${key}]`}`);
157 | return true;
158 | },
159 | };
160 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
161 | get: shallowReadonlyGet,
162 | });
163 |
164 | function reactive(raw) {
165 | return new Proxy(raw, mutableHandlers);
166 | }
167 | function readonly(raw) {
168 | return new Proxy(raw, readonlyHandlers);
169 | }
170 | function shallowReadonly(raw) {
171 | if (!isObject(raw)) {
172 | console.warn(`target ${raw} 必须是一个对象`);
173 | return raw;
174 | }
175 | return new Proxy(raw, shallowReadonlyHandlers);
176 | }
177 |
178 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
179 | const capitalize = (str) => {
180 | return str.charAt(0).toUpperCase() + str.slice(1);
181 | };
182 | const toHandlerKey = (str) => {
183 | return str ? `on${capitalize(str)}` : "";
184 | };
185 | const camelize = (str) => {
186 | return str.replace(/-(\w)/g, (_, c) => {
187 | return c ? c.toUpperCase() : "";
188 | });
189 | };
190 |
191 | const publicProxyMaps = {
192 | $el: (instance) => instance.vnode.el,
193 | $slots: (instance) => instance.slots,
194 | };
195 | const publicInstanceProxyHandler = {
196 | get({ _: instance }, key) {
197 | if (key in instance.setupState) {
198 | return instance.setupState[key];
199 | }
200 | const { props } = instance;
201 | if (hasOwn(instance.setupState, key)) {
202 | return instance.setupState[key];
203 | }
204 | else if (hasOwn(props, key)) {
205 | return props[key];
206 | }
207 | const publicGetter = publicProxyMaps[key];
208 | if (publicGetter) {
209 | return publicGetter(instance);
210 | }
211 | },
212 | };
213 |
214 | function emit(instance, event, ...args) {
215 | console.log("event====", event);
216 | const { props } = instance;
217 | // TPP
218 | // 先去写一个特定的行为再去写一个通用的行为
219 | const handlersName = toHandlerKey(camelize(event));
220 | const handlers = props[handlersName];
221 | handlers && handlers(...args);
222 | }
223 |
224 | function initProps(instance, rawProps) {
225 | instance.props = rawProps || {};
226 | }
227 |
228 | function initSlots(instance, children) {
229 | // 是否是数组,如果不是就给包裹一层
230 | // instance.slots = Array.isArray(children) ? children : [children];
231 | const { vnode } = instance;
232 | if (vnode.shapeFlags & 16 /* SLOT_CHILDREN */) {
233 | const slot = {};
234 | for (let key in children) {
235 | const value = children[key];
236 | slot[key] = (props) => normalizeSlotsValue(value(props));
237 | }
238 | instance.slots = slot;
239 | }
240 | }
241 | function normalizeSlotsValue(value) {
242 | return Array.isArray(value) ? value : [value];
243 | }
244 |
245 | function createComponentInstance(vnode, parent) {
246 | console.log("调试====", parent);
247 | const component = {
248 | vnode: vnode,
249 | type: vnode.type,
250 | setupState: {},
251 | props: {},
252 | provides: parent ? parent.provides : {},
253 | parent,
254 | slot: {},
255 | isMounted: false,
256 | subTree: {},
257 | emit: () => { },
258 | };
259 | component.emit = emit.bind(null, component); //拿到instance
260 | return component;
261 | }
262 | function setComponentInstance(instance) {
263 | // initProps
264 | // initSlots
265 | initSlots(instance, instance.vnode.children);
266 | initProps(instance, instance.vnode.props);
267 | setupStatefulComponent(instance);
268 | }
269 | function setupStatefulComponent(instance) {
270 | setCurrentInstance(instance);
271 | const Component = instance.type;
272 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandler);
273 | const { setup } = Component;
274 | if (setup) {
275 | const setupResult = setup(shallowReadonly(instance.props), {
276 | emit: instance.emit,
277 | }); //props在子组件不去改变
278 | setCurrentInstance(null);
279 | handelSetupResult(setupResult, instance);
280 | }
281 | }
282 | function handelSetupResult(setupResult, instance) {
283 | if (typeof setupResult === "object") {
284 | instance.setupState = setupResult;
285 | }
286 | finishComponentSetup(instance);
287 | }
288 | function finishComponentSetup(instance) {
289 | const Component = instance.type;
290 | if (Component.render) {
291 | instance.render = Component.render;
292 | }
293 | }
294 | let currentInstance = null;
295 | function getCurrentInstance() {
296 | return currentInstance;
297 | }
298 | function setCurrentInstance(instance) {
299 | currentInstance = instance;
300 | }
301 |
302 | function render(vnode, container) {
303 | patch(null, vnode, container, null);
304 | console.log("render==", vnode, container);
305 | }
306 | function patch(n1, n2, container, parentComponent) {
307 | // 处理组件
308 | // 先判断是不是element
309 | // fragment只渲染children
310 | const { type, shapeFlags } = n2;
311 | switch (type) {
312 | case "Fragment":
313 | processFragment(n1, n2, container, parentComponent);
314 | break;
315 | case "Text":
316 | processText(n1, n2, container);
317 | break;
318 | default:
319 | if (shapeFlags & 1 /* ELEMENT */) {
320 | processElement(n1, n2, container, parentComponent);
321 | }
322 | else if (shapeFlags & 2 /* STATEFUL_COMPONENT */) {
323 | processComponent(n1, n2, container, parentComponent);
324 | }
325 | break;
326 | }
327 | }
328 | function processComponent(n1, vnode, container, parentComponent) {
329 | mountComponent(vnode, container, parentComponent);
330 | }
331 | function mountComponent(initialVNode, container, parentComponent) {
332 | const instance = createComponentInstance(initialVNode, parentComponent);
333 | setComponentInstance(instance);
334 | setupRenderEffect(instance, container, initialVNode);
335 | }
336 | function setupRenderEffect(instance, container, initialVNode) {
337 | effect(() => {
338 | if (!instance.isMounted) {
339 | const { proxy } = instance;
340 | console.log("初始化===", proxy);
341 | const subTree = (instance.subTree = instance.render.call(proxy));
342 | patch(null, subTree, container, instance);
343 | initialVNode.el = subTree.el;
344 | instance.isMounted = true;
345 | }
346 | else {
347 | console.log("update");
348 | const { proxy } = instance;
349 | const subTree = instance.render.call(proxy);
350 | const prevSubTree = instance.subTree;
351 | instance.subTree = subTree;
352 | patch(prevSubTree, subTree, container, instance);
353 | // 更新
354 | }
355 | });
356 | }
357 | function processElement(n1, n2, container, parentComponent) {
358 | if (!n1) {
359 | // 初始化
360 | mountElement(n2, container, parentComponent);
361 | }
362 | else {
363 | // 更新
364 | patchElement(n1, n2);
365 | }
366 | }
367 | function patchElement(n1, n2, container, parentComponent) {
368 | console.log("patchElement");
369 | const oldProps = n1.props || {};
370 | const newProps = n2.props || {};
371 | const el = (n2.el = n1.el);
372 | patchProp(el, oldProps, newProps);
373 | }
374 | function patchProp(el, oldProps, newProps) {
375 | // 遍历新的props
376 | for (const key in newProps) {
377 | const prevProp = oldProps[key];
378 | const nextProp = newProps[key];
379 | console.log("111", prevProp, nextProp);
380 | // 如果props不想等就准备更新
381 | if (prevProp !== nextProp) {
382 | hostPatchProp(el, key, prevProp, nextProp);
383 | }
384 | }
385 | }
386 | function mountElement(vnode, container, parentComponent) {
387 | const el = (vnode.el = document.createElement(vnode.type));
388 | const { children, props, shapeFlags } = vnode;
389 | if (shapeFlags & 4 /* TEXT_CHILDREN */) {
390 | el.textContent = children;
391 | }
392 | else if (shapeFlags & 8 /* ARROW_CHILDREN */) {
393 | mountChild(children, el, parentComponent);
394 | }
395 | for (const key in props) {
396 | const value = props[key];
397 | const isOn = (key) => /^on[A-Z]/.test(key);
398 | if (isOn(key)) {
399 | const event = key.slice(2).toLowerCase();
400 | el.addEventListener(event, value);
401 | }
402 | else {
403 | el.setAttribute(key, value);
404 | }
405 | }
406 | container.appendChild(el);
407 | }
408 | function mountChild(vnode, container, parentComponent) {
409 | console.log("mountChild", vnode);
410 | if (Array.isArray(vnode)) {
411 | vnode.forEach((el) => {
412 | patch(null, el, container, parentComponent);
413 | });
414 | }
415 | }
416 | function processFragment(n1, vnode, container, parentComponent) {
417 | mountChild(vnode, container, parentComponent);
418 | }
419 | function processText(n1, vnode, container) {
420 | // 只渲染文字
421 | const { children } = vnode;
422 | const textNode = (vnode.el = document.createTextNode(children));
423 | container.append(textNode);
424 | }
425 | function createRender(options) { }
426 |
427 | function createVnode(type, props, children) {
428 | const vnode = {
429 | type,
430 | props,
431 | children,
432 | shapeFlags: getShapeFlags(type),
433 | el: null,
434 | };
435 | // children 位运算符
436 | if (typeof children === "string") {
437 | vnode.shapeFlags |= 4 /* TEXT_CHILDREN */;
438 | }
439 | else if (Array.isArray(children)) {
440 | vnode.shapeFlags |= 8 /* ARROW_CHILDREN */;
441 | }
442 | // 组件 slot
443 | if (vnode.shapeFlags & 2 /* STATEFUL_COMPONENT */) {
444 | if (typeof children === "object") {
445 | vnode.shapeFlags |= 16 /* SLOT_CHILDREN */;
446 | }
447 | }
448 | return vnode;
449 | }
450 | function createTextVnode(text) {
451 | return createVnode('Text', {}, text);
452 | }
453 | function getShapeFlags(type) {
454 | return typeof type === "string"
455 | ? 1 /* ELEMENT */
456 | : 2 /* STATEFUL_COMPONENT */;
457 | }
458 |
459 | function createApp(rootComponent) {
460 | return {
461 | mount(rootContainer) {
462 | // 先转换成虚拟节点
463 | // 所有节点都基于虚拟节点
464 | const vnode = createVnode(rootComponent);
465 | render(vnode, rootContainer);
466 | },
467 | };
468 | }
469 |
470 | function h(type, props, children) {
471 | return createVnode(type, props, children);
472 | }
473 |
474 | function renderSlot(slots, name, props) {
475 | const slot = slots[name];
476 | if (slot) {
477 | if (typeof slot === "function") {
478 | return createVnode("Fragment", {}, slot(props));
479 | }
480 | }
481 | }
482 |
483 | // 存
484 | function provide(key, value) {
485 | const currentInstance = getCurrentInstance();
486 | if (currentInstance) {
487 | let { provides } = currentInstance;
488 | console.log("存provides====", currentInstance);
489 | const parentProviders = currentInstance.parent && currentInstance.parent.provides;
490 | if (provides === parentProviders) {
491 | provides = currentInstance.provides = Object.create(parentProviders);
492 | }
493 | provides[key] = value;
494 | }
495 | }
496 | // 取
497 | function inject(key, defaultValue) {
498 | const currentInstance = getCurrentInstance();
499 | console.log("inject=====parentProviders", currentInstance);
500 | if (currentInstance) {
501 | const { parent } = currentInstance;
502 | const parentProviders = parent.provides;
503 | if (key in parentProviders) {
504 | return parentProviders[key];
505 | }
506 | else if (defaultValue) {
507 | if (typeof defaultValue === "function") {
508 | return defaultValue();
509 | }
510 | return defaultValue;
511 | }
512 | }
513 | }
514 |
515 | class RefImpl {
516 | constructor(value) {
517 | this._v_isRef = true;
518 | this._rawValue = value;
519 | this._value = convert(value);
520 | // 判断对象是不是对象
521 | this.dep = new Set();
522 | }
523 | set value(newValue) {
524 | // 对比的时候
525 | if (hasChange(newValue, this._rawValue)) {
526 | this._value = convert(newValue);
527 | this._rawValue = newValue;
528 | triggerEffect(this.dep);
529 | }
530 | }
531 | get value() {
532 | if (isTracking()) {
533 | trackEffect(this.dep);
534 | }
535 | return this._value;
536 | }
537 | }
538 | function convert(value) {
539 | return isObject(value) ? reactive(value) : value;
540 | }
541 | function ref(value) {
542 | return new RefImpl(value);
543 | }
544 |
545 | exports.createApp = createApp;
546 | exports.createRender = createRender;
547 | exports.createTextVnode = createTextVnode;
548 | exports.getCurrentInstance = getCurrentInstance;
549 | exports.h = h;
550 | exports.inject = inject;
551 | exports.provide = provide;
552 | exports.ref = ref;
553 | exports.renderSlot = renderSlot;
554 |
--------------------------------------------------------------------------------
/lib/guide.esm.js:
--------------------------------------------------------------------------------
1 | const extend = Object.assign;
2 | function isObject(obj) {
3 | return obj !== null && typeof obj === 'object';
4 | }
5 | function hasChange(val, newValue) {
6 | return !Object.is(val, newValue);
7 | }
8 |
9 | let activeEffect;
10 | let shouldTrick;
11 | class ReactiveEffect {
12 | constructor(fn, scheduler) {
13 | this.scheduler = scheduler;
14 | this.deps = [];
15 | this.active = true;
16 | this._fn = fn;
17 | }
18 | run() {
19 | if (!this.active) {
20 | return this._fn();
21 | }
22 | shouldTrick = true;
23 | // 收集依赖
24 | activeEffect = this;
25 | const result = this._fn();
26 | shouldTrick = false;
27 | return result;
28 | }
29 | stop() {
30 | if (this.active) {
31 | clearUpEffect(this);
32 | this.onStop && this.onStop();
33 | this.active = false;
34 | }
35 | }
36 | }
37 | function clearUpEffect(effect) {
38 | effect.deps.forEach((dep) => {
39 | dep.delete(effect);
40 | });
41 | }
42 | // 依赖收集
43 | const targetMap = new Map();
44 | function track(target, key) {
45 | if (!isTracking())
46 | return;
47 | let depsMap = targetMap.get(target);
48 | if (!depsMap) {
49 | depsMap = new Map();
50 | targetMap.set(target, depsMap);
51 | }
52 | let dep = depsMap.get(key);
53 | if (!dep) {
54 | dep = new Set();
55 | depsMap.set(key, dep);
56 | }
57 | trackEffect(dep);
58 | }
59 | function trackEffect(dep) {
60 | if (dep.has(activeEffect))
61 | return;
62 | dep.add(activeEffect);
63 | activeEffect.deps.push(dep);
64 | }
65 | function isTracking() {
66 | return activeEffect !== undefined && shouldTrick;
67 | }
68 | function effect(fn, options = {}) {
69 | // 上来就要调用fn
70 | const scheduler = options.scheduler; //获取
71 | const _effect = new ReactiveEffect(fn, scheduler);
72 | extend(_effect, options); //挂载所有options的值
73 | _effect.run();
74 | const runner = _effect.run.bind(_effect);
75 | runner.effect = _effect;
76 | return runner;
77 | }
78 | function trigger(target, key) {
79 | let depsMap = targetMap.get(target);
80 | let deps = depsMap.get(key);
81 | triggerEffect(deps);
82 | }
83 | function triggerEffect(deps) {
84 | for (const effect of deps) {
85 | if (effect.scheduler) {
86 | effect.scheduler();
87 | }
88 | else {
89 | effect.run();
90 | }
91 | }
92 | }
93 |
94 | function hostPatchProp(el, key, prevValue, nextValue) {
95 | const isOn = (key) => /^on[A-Z]/.test(key);
96 | if (isOn(key)) {
97 | const event = key.slice(2).toLowerCase();
98 | el.addEventListener(event, nextValue);
99 | }
100 | else {
101 | if (nextValue === undefined || nextValue === null) {
102 | el.removeAttribute(key, nextValue);
103 | }
104 | else {
105 | el.setAttribute(key, nextValue);
106 | }
107 | }
108 | }
109 |
110 | const set = createSetter();
111 | const get = createGetter();
112 | const readonlyGet = createGetter(true);
113 | const shallowReadonlyGet = createGetter(true, true);
114 | function createGetter(isReadOnly = false, shallow = false) {
115 | return function get(target, key) {
116 | if (key === "__v_isReactive__" /* IS_REACTIVE */) {
117 | return !isReadOnly;
118 | }
119 | else if (key === "__v_isReadonly__" /* IS_READ_ONLY */) {
120 | return isReadOnly;
121 | }
122 | const res = Reflect.get(target, key);
123 | if (shallow) {
124 | // 如果是shallow就不需要深层次的观察
125 | return res;
126 | }
127 | // 看看是不是object
128 | if (isObject(res)) {
129 | return isReadOnly ? readonly(res) : reactive(res);
130 | }
131 | if (!isReadOnly) {
132 | track(target, key);
133 | }
134 | return res;
135 | };
136 | }
137 | function createSetter() {
138 | return function get(target, key, value) {
139 | const res = Reflect.set(target, key, value);
140 | trigger(target, key);
141 | return res;
142 | };
143 | }
144 | const mutableHandlers = {
145 | get,
146 | set,
147 | };
148 | const readonlyHandlers = {
149 | get: readonlyGet,
150 | set(target, key, value) {
151 | // readonly的时候不允许set
152 | console.warn(`Attempting to mutate readonly value ${`${target}[${key}]`}`);
153 | return true;
154 | },
155 | };
156 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
157 | get: shallowReadonlyGet,
158 | });
159 |
160 | function reactive(raw) {
161 | return new Proxy(raw, mutableHandlers);
162 | }
163 | function readonly(raw) {
164 | return new Proxy(raw, readonlyHandlers);
165 | }
166 | function shallowReadonly(raw) {
167 | if (!isObject(raw)) {
168 | console.warn(`target ${raw} 必须是一个对象`);
169 | return raw;
170 | }
171 | return new Proxy(raw, shallowReadonlyHandlers);
172 | }
173 |
174 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
175 | const capitalize = (str) => {
176 | return str.charAt(0).toUpperCase() + str.slice(1);
177 | };
178 | const toHandlerKey = (str) => {
179 | return str ? `on${capitalize(str)}` : "";
180 | };
181 | const camelize = (str) => {
182 | return str.replace(/-(\w)/g, (_, c) => {
183 | return c ? c.toUpperCase() : "";
184 | });
185 | };
186 |
187 | const publicProxyMaps = {
188 | $el: (instance) => instance.vnode.el,
189 | $slots: (instance) => instance.slots,
190 | };
191 | const publicInstanceProxyHandler = {
192 | get({ _: instance }, key) {
193 | if (key in instance.setupState) {
194 | return instance.setupState[key];
195 | }
196 | const { props } = instance;
197 | if (hasOwn(instance.setupState, key)) {
198 | return instance.setupState[key];
199 | }
200 | else if (hasOwn(props, key)) {
201 | return props[key];
202 | }
203 | const publicGetter = publicProxyMaps[key];
204 | if (publicGetter) {
205 | return publicGetter(instance);
206 | }
207 | },
208 | };
209 |
210 | function emit(instance, event, ...args) {
211 | console.log("event====", event);
212 | const { props } = instance;
213 | // TPP
214 | // 先去写一个特定的行为再去写一个通用的行为
215 | const handlersName = toHandlerKey(camelize(event));
216 | const handlers = props[handlersName];
217 | handlers && handlers(...args);
218 | }
219 |
220 | function initProps(instance, rawProps) {
221 | instance.props = rawProps || {};
222 | }
223 |
224 | function initSlots(instance, children) {
225 | // 是否是数组,如果不是就给包裹一层
226 | // instance.slots = Array.isArray(children) ? children : [children];
227 | const { vnode } = instance;
228 | if (vnode.shapeFlags & 16 /* SLOT_CHILDREN */) {
229 | const slot = {};
230 | for (let key in children) {
231 | const value = children[key];
232 | slot[key] = (props) => normalizeSlotsValue(value(props));
233 | }
234 | instance.slots = slot;
235 | }
236 | }
237 | function normalizeSlotsValue(value) {
238 | return Array.isArray(value) ? value : [value];
239 | }
240 |
241 | function createComponentInstance(vnode, parent) {
242 | console.log("调试====", parent);
243 | const component = {
244 | vnode: vnode,
245 | type: vnode.type,
246 | setupState: {},
247 | props: {},
248 | provides: parent ? parent.provides : {},
249 | parent,
250 | slot: {},
251 | isMounted: false,
252 | subTree: {},
253 | emit: () => { },
254 | };
255 | component.emit = emit.bind(null, component); //拿到instance
256 | return component;
257 | }
258 | function setComponentInstance(instance) {
259 | // initProps
260 | // initSlots
261 | initSlots(instance, instance.vnode.children);
262 | initProps(instance, instance.vnode.props);
263 | setupStatefulComponent(instance);
264 | }
265 | function setupStatefulComponent(instance) {
266 | setCurrentInstance(instance);
267 | const Component = instance.type;
268 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandler);
269 | const { setup } = Component;
270 | if (setup) {
271 | const setupResult = setup(shallowReadonly(instance.props), {
272 | emit: instance.emit,
273 | }); //props在子组件不去改变
274 | setCurrentInstance(null);
275 | handelSetupResult(setupResult, instance);
276 | }
277 | }
278 | function handelSetupResult(setupResult, instance) {
279 | if (typeof setupResult === "object") {
280 | instance.setupState = setupResult;
281 | }
282 | finishComponentSetup(instance);
283 | }
284 | function finishComponentSetup(instance) {
285 | const Component = instance.type;
286 | if (Component.render) {
287 | instance.render = Component.render;
288 | }
289 | }
290 | let currentInstance = null;
291 | function getCurrentInstance() {
292 | return currentInstance;
293 | }
294 | function setCurrentInstance(instance) {
295 | currentInstance = instance;
296 | }
297 |
298 | function render(vnode, container) {
299 | patch(null, vnode, container, null);
300 | console.log("render==", vnode, container);
301 | }
302 | function patch(n1, n2, container, parentComponent) {
303 | // 处理组件
304 | // 先判断是不是element
305 | // fragment只渲染children
306 | const { type, shapeFlags } = n2;
307 | switch (type) {
308 | case "Fragment":
309 | processFragment(n1, n2, container, parentComponent);
310 | break;
311 | case "Text":
312 | processText(n1, n2, container);
313 | break;
314 | default:
315 | if (shapeFlags & 1 /* ELEMENT */) {
316 | processElement(n1, n2, container, parentComponent);
317 | }
318 | else if (shapeFlags & 2 /* STATEFUL_COMPONENT */) {
319 | processComponent(n1, n2, container, parentComponent);
320 | }
321 | break;
322 | }
323 | }
324 | function processComponent(n1, vnode, container, parentComponent) {
325 | mountComponent(vnode, container, parentComponent);
326 | }
327 | function mountComponent(initialVNode, container, parentComponent) {
328 | const instance = createComponentInstance(initialVNode, parentComponent);
329 | setComponentInstance(instance);
330 | setupRenderEffect(instance, container, initialVNode);
331 | }
332 | function setupRenderEffect(instance, container, initialVNode) {
333 | effect(() => {
334 | if (!instance.isMounted) {
335 | const { proxy } = instance;
336 | console.log("初始化===", proxy);
337 | const subTree = (instance.subTree = instance.render.call(proxy));
338 | patch(null, subTree, container, instance);
339 | initialVNode.el = subTree.el;
340 | instance.isMounted = true;
341 | }
342 | else {
343 | console.log("update");
344 | const { proxy } = instance;
345 | const subTree = instance.render.call(proxy);
346 | const prevSubTree = instance.subTree;
347 | instance.subTree = subTree;
348 | patch(prevSubTree, subTree, container, instance);
349 | // 更新
350 | }
351 | });
352 | }
353 | function processElement(n1, n2, container, parentComponent) {
354 | if (!n1) {
355 | // 初始化
356 | mountElement(n2, container, parentComponent);
357 | }
358 | else {
359 | // 更新
360 | patchElement(n1, n2);
361 | }
362 | }
363 | function patchElement(n1, n2, container, parentComponent) {
364 | console.log("patchElement");
365 | const oldProps = n1.props || {};
366 | const newProps = n2.props || {};
367 | const el = (n2.el = n1.el);
368 | patchProp(el, oldProps, newProps);
369 | }
370 | function patchProp(el, oldProps, newProps) {
371 | // 遍历新的props
372 | for (const key in newProps) {
373 | const prevProp = oldProps[key];
374 | const nextProp = newProps[key];
375 | console.log("111", prevProp, nextProp);
376 | // 如果props不想等就准备更新
377 | if (prevProp !== nextProp) {
378 | hostPatchProp(el, key, prevProp, nextProp);
379 | }
380 | }
381 | }
382 | function mountElement(vnode, container, parentComponent) {
383 | const el = (vnode.el = document.createElement(vnode.type));
384 | const { children, props, shapeFlags } = vnode;
385 | if (shapeFlags & 4 /* TEXT_CHILDREN */) {
386 | el.textContent = children;
387 | }
388 | else if (shapeFlags & 8 /* ARROW_CHILDREN */) {
389 | mountChild(children, el, parentComponent);
390 | }
391 | for (const key in props) {
392 | const value = props[key];
393 | const isOn = (key) => /^on[A-Z]/.test(key);
394 | if (isOn(key)) {
395 | const event = key.slice(2).toLowerCase();
396 | el.addEventListener(event, value);
397 | }
398 | else {
399 | el.setAttribute(key, value);
400 | }
401 | }
402 | container.appendChild(el);
403 | }
404 | function mountChild(vnode, container, parentComponent) {
405 | console.log("mountChild", vnode);
406 | if (Array.isArray(vnode)) {
407 | vnode.forEach((el) => {
408 | patch(null, el, container, parentComponent);
409 | });
410 | }
411 | }
412 | function processFragment(n1, vnode, container, parentComponent) {
413 | mountChild(vnode, container, parentComponent);
414 | }
415 | function processText(n1, vnode, container) {
416 | // 只渲染文字
417 | const { children } = vnode;
418 | const textNode = (vnode.el = document.createTextNode(children));
419 | container.append(textNode);
420 | }
421 | function createRender(options) { }
422 |
423 | function createVnode(type, props, children) {
424 | const vnode = {
425 | type,
426 | props,
427 | children,
428 | shapeFlags: getShapeFlags(type),
429 | el: null,
430 | };
431 | // children 位运算符
432 | if (typeof children === "string") {
433 | vnode.shapeFlags |= 4 /* TEXT_CHILDREN */;
434 | }
435 | else if (Array.isArray(children)) {
436 | vnode.shapeFlags |= 8 /* ARROW_CHILDREN */;
437 | }
438 | // 组件 slot
439 | if (vnode.shapeFlags & 2 /* STATEFUL_COMPONENT */) {
440 | if (typeof children === "object") {
441 | vnode.shapeFlags |= 16 /* SLOT_CHILDREN */;
442 | }
443 | }
444 | return vnode;
445 | }
446 | function createTextVnode(text) {
447 | return createVnode('Text', {}, text);
448 | }
449 | function getShapeFlags(type) {
450 | return typeof type === "string"
451 | ? 1 /* ELEMENT */
452 | : 2 /* STATEFUL_COMPONENT */;
453 | }
454 |
455 | function createApp(rootComponent) {
456 | return {
457 | mount(rootContainer) {
458 | // 先转换成虚拟节点
459 | // 所有节点都基于虚拟节点
460 | const vnode = createVnode(rootComponent);
461 | render(vnode, rootContainer);
462 | },
463 | };
464 | }
465 |
466 | function h(type, props, children) {
467 | return createVnode(type, props, children);
468 | }
469 |
470 | function renderSlot(slots, name, props) {
471 | const slot = slots[name];
472 | if (slot) {
473 | if (typeof slot === "function") {
474 | return createVnode("Fragment", {}, slot(props));
475 | }
476 | }
477 | }
478 |
479 | // 存
480 | function provide(key, value) {
481 | const currentInstance = getCurrentInstance();
482 | if (currentInstance) {
483 | let { provides } = currentInstance;
484 | console.log("存provides====", currentInstance);
485 | const parentProviders = currentInstance.parent && currentInstance.parent.provides;
486 | if (provides === parentProviders) {
487 | provides = currentInstance.provides = Object.create(parentProviders);
488 | }
489 | provides[key] = value;
490 | }
491 | }
492 | // 取
493 | function inject(key, defaultValue) {
494 | const currentInstance = getCurrentInstance();
495 | console.log("inject=====parentProviders", currentInstance);
496 | if (currentInstance) {
497 | const { parent } = currentInstance;
498 | const parentProviders = parent.provides;
499 | if (key in parentProviders) {
500 | return parentProviders[key];
501 | }
502 | else if (defaultValue) {
503 | if (typeof defaultValue === "function") {
504 | return defaultValue();
505 | }
506 | return defaultValue;
507 | }
508 | }
509 | }
510 |
511 | class RefImpl {
512 | constructor(value) {
513 | this._v_isRef = true;
514 | this._rawValue = value;
515 | this._value = convert(value);
516 | // 判断对象是不是对象
517 | this.dep = new Set();
518 | }
519 | set value(newValue) {
520 | // 对比的时候
521 | if (hasChange(newValue, this._rawValue)) {
522 | this._value = convert(newValue);
523 | this._rawValue = newValue;
524 | triggerEffect(this.dep);
525 | }
526 | }
527 | get value() {
528 | if (isTracking()) {
529 | trackEffect(this.dep);
530 | }
531 | return this._value;
532 | }
533 | }
534 | function convert(value) {
535 | return isObject(value) ? reactive(value) : value;
536 | }
537 | function ref(value) {
538 | return new RefImpl(value);
539 | }
540 |
541 | export { createApp, createRender, createTextVnode, getCurrentInstance, h, inject, provide, ref, renderSlot };
542 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "before-minivue",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "lib/guide-cjs.js",
6 | "module":"lib/guide-esm.js",
7 | "scripts": {
8 | "dev": "node src/index.js --watch",
9 | "test": "jest",
10 | "build": "rollup -c"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@rollup/plugin-typescript": "^8.3.1",
17 | "@vue/reactivity": "^3.2.31"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.17.8",
21 | "@babel/preset-env": "^7.16.11",
22 | "@babel/preset-typescript": "^7.16.7",
23 | "@types/jest": "^27.4.1",
24 | "babel-jest": "^27.5.1",
25 | "jest": "^27.5.1",
26 | "rollup": "^2.70.1",
27 | "tslib": "^2.3.1",
28 | "typescript": "^4.6.2"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from '@rollup/plugin-typescript'
2 | export default {
3 | input: './src/index.ts',
4 | output: [{
5 | format: 'cjs',
6 | file: "lib/guide.cjs.js",
7 | },
8 | {
9 | format: 'es',
10 | file: "lib/guide.esm.js",
11 | }
12 | ],
13 | plugins: [typescript()]
14 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const {
2 | reactive,
3 | effect
4 | } = require('@vue/reactivity');
5 |
6 | // 声明一个响应式对象
7 | let a = reactive({
8 | value:10
9 | });
10 | let b
11 | effect(() => {
12 | // 上来就会执行1次
13 | b = a.value + 1000;
14 | console.log(b);
15 | })
16 | a.value = 20 //effect还会再次执行
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // mini 出口
3 | export * from './runtime-core/index'
--------------------------------------------------------------------------------
/src/reactivity/bseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { track, trigger } from "./effect";
2 | import { reactive, ReactiveFlags, readonly } from "./reactive";
3 | import { extend, isObject } from "./shared";
4 | const set = createSetter();
5 | const get = createGetter();
6 | const readonlyGet = createGetter(true);
7 | const shallowReadonlyGet = createGetter(true, true);
8 | function createGetter(isReadOnly: boolean = false, shallow: boolean = false) {
9 | return function get(target, key) {
10 | if (key === ReactiveFlags.IS_REACTIVE) {
11 | return !isReadOnly;
12 | } else if (key === ReactiveFlags.IS_READ_ONLY) {
13 | return isReadOnly;
14 | }
15 |
16 | const res = Reflect.get(target, key);
17 | if (shallow) {
18 | // 如果是shallow就不需要深层次的观察
19 | return res;
20 | }
21 | // 看看是不是object
22 | if (isObject(res)) {
23 | return isReadOnly ? readonly(res) : reactive(res);
24 | }
25 | if (!isReadOnly) {
26 | track(target, key);
27 | }
28 | return res;
29 | };
30 | }
31 | function createSetter() {
32 | return function get(target, key, value) {
33 | const res = Reflect.set(target, key, value);
34 | trigger(target, key);
35 | return res;
36 | };
37 | }
38 | export const mutableHandlers = {
39 | get,
40 | set,
41 | };
42 | export const readonlyHandlers = {
43 | get: readonlyGet,
44 | set(target, key, value) {
45 | // readonly的时候不允许set
46 | console.warn(`Attempting to mutate readonly value ${`${target}[${key}]`}`);
47 | return true;
48 | },
49 | };
50 |
51 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
52 | get: shallowReadonlyGet,
53 | });
54 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 | class computedRefImpl {
3 | private _getter: any;
4 | private _dirty: boolean = true;
5 | private _value: any;
6 | private _effect: ReactiveEffect;
7 | constructor(getter) {
8 | this._getter = getter;
9 | this._effect = new ReactiveEffect(getter, () => {
10 | if (!this._dirty) {
11 | this._dirty = true;
12 | }
13 | });
14 | }
15 | get value() {
16 | if (this._dirty) {
17 | this._dirty = false;
18 | this._value = this._effect.run();
19 | }
20 | return this._value;
21 | }
22 | }
23 | export function computed(getter) {
24 | return new computedRefImpl(getter);
25 | }
26 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "./shared";
2 | let activeEffect;
3 | let shouldTrick;
4 | export class ReactiveEffect {
5 | private _fn: any;
6 | private deps = [];
7 | private active: boolean = true;
8 | onStop?: () => void;
9 | constructor(fn, public scheduler?) {
10 | this._fn = fn;
11 | }
12 | run() {
13 | if (!this.active) {
14 | return this._fn();
15 | }
16 | shouldTrick = true;
17 | // 收集依赖
18 | activeEffect = this;
19 | const result = this._fn();
20 | shouldTrick = false;
21 | return result;
22 | }
23 | stop() {
24 | if (this.active) {
25 | clearUpEffect(this);
26 | this.onStop && this.onStop();
27 | this.active = false;
28 | }
29 | }
30 | }
31 | function clearUpEffect(effect) {
32 | effect.deps.forEach((dep: any) => {
33 | dep.delete(effect);
34 | });
35 | }
36 | // 依赖收集
37 | const targetMap = new Map();
38 | export function track(target, key) {
39 | if (!isTracking()) return;
40 | let depsMap = targetMap.get(target);
41 | if (!depsMap) {
42 | depsMap = new Map();
43 | targetMap.set(target, depsMap);
44 | }
45 | let dep = depsMap.get(key);
46 | if (!dep) {
47 | dep = new Set();
48 | depsMap.set(key, dep);
49 | }
50 |
51 | trackEffect(dep);
52 | }
53 | export function trackEffect(dep) {
54 | if (dep.has(activeEffect)) return;
55 |
56 | dep.add(activeEffect);
57 | activeEffect.deps.push(dep);
58 | }
59 | export function isTracking() {
60 | return activeEffect !== undefined && shouldTrick;
61 | }
62 | export function effect(fn, options: any = {}) {
63 | // 上来就要调用fn
64 | const scheduler = options.scheduler; //获取
65 | const _effect = new ReactiveEffect(fn, scheduler);
66 | extend(_effect, options); //挂载所有options的值
67 | _effect.run();
68 | const runner: any = _effect.run.bind(_effect);
69 | runner.effect = _effect;
70 | return runner;
71 | }
72 |
73 | export function trigger(target, key) {
74 | let depsMap = targetMap.get(target);
75 | let deps = depsMap.get(key);
76 | triggerEffect(deps);
77 | }
78 | export function triggerEffect(deps) {
79 | for (const effect of deps) {
80 | if (effect.scheduler) {
81 | effect.scheduler();
82 | } else {
83 | effect.run();
84 | }
85 | }
86 | }
87 | export function stop(runner) {
88 | runner.effect.stop();
89 | }
90 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export function add(a:number, b:number) {
2 | return a + b;
3 | }
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | mutableHandlers,
3 | readonlyHandlers,
4 | shallowReadonlyHandlers,
5 | } from "./bseHandlers";
6 | import { isObject } from "./shared/index";
7 | export const enum ReactiveFlags {
8 | IS_REACTIVE = "__v_isReactive__",
9 | IS_READ_ONLY = "__v_isReadonly__",
10 | }
11 |
12 | export function reactive(raw) {
13 | return new Proxy(raw, mutableHandlers);
14 | }
15 |
16 | export function readonly(raw) {
17 | return new Proxy(raw, readonlyHandlers);
18 | }
19 | export function isReactive(value) {
20 | return !!value[ReactiveFlags.IS_REACTIVE];
21 | }
22 | export function isReadOnly(value) {
23 | return !!value[ReactiveFlags.IS_READ_ONLY];
24 | }
25 |
26 | export function shallowReadonly(raw) {
27 | if (!isObject(raw)) {
28 | console.warn(`target ${raw} 必须是一个对象`);
29 | return raw;
30 | }
31 | return new Proxy(raw, shallowReadonlyHandlers);
32 | }
33 |
34 | export function isProxy(raw: any): boolean {
35 | return isReadOnly(raw) || isReactive(raw);
36 | }
37 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { isTracking, trackEffect, triggerEffect } from "./effect";
2 | import { reactive } from "./reactive";
3 | import { hasChange, isObject } from "./shared";
4 |
5 | class RefImpl {
6 | private _value: any;
7 | public dep;
8 | public _v_isRef: boolean = true;
9 | private _rawValue: any;
10 | constructor(value) {
11 | this._rawValue = value;
12 | this._value = convert(value);
13 | // 判断对象是不是对象
14 | this.dep = new Set();
15 | }
16 | set value(newValue: any) {
17 | // 对比的时候
18 | if (hasChange(newValue, this._rawValue)) {
19 | this._value = convert(newValue);
20 | this._rawValue = newValue;
21 | triggerEffect(this.dep);
22 | }
23 | }
24 | get value() {
25 | if (isTracking()) {
26 | trackEffect(this.dep);
27 | }
28 |
29 | return this._value;
30 | }
31 | }
32 | function convert(value: any) {
33 | return isObject(value) ? reactive(value) : value;
34 | }
35 | export function ref(value) {
36 | return new RefImpl(value);
37 | }
38 | export function isRef(ref) {
39 | // 如果有值就一定是ref
40 | return !!ref._v_isRef;
41 | }
42 | export function unRef(ref) {
43 | return isRef(ref) ? ref.value : ref;
44 | }
45 | export function proxyRefs(raw: any) {
46 | return new Proxy(raw, {
47 | get(target, key) {
48 | return unRef(Reflect.get(target, key));
49 | },
50 | set(target, key, value) {
51 | if (isRef(target[key]) && !isRef(value)) {
52 | return (target[key].value = value);
53 | } else {
54 | return Reflect.set(target, key, value);
55 | }
56 | },
57 | });
58 | }
59 |
--------------------------------------------------------------------------------
/src/reactivity/shared/index.ts:
--------------------------------------------------------------------------------
1 | export const extend = Object.assign;
2 | export function isObject(obj) {
3 | return obj !== null && typeof obj === 'object';
4 | }
5 | export function hasChange(val, newValue) {
6 | return !Object.is(val, newValue);
7 | }
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../computed"
2 | import { reactive } from "../reactive"
3 |
4 | // 边写一个测试用例
5 | describe('computed', () => {
6 | it('computed path', () => {
7 | const user = reactive({ age: 11 })
8 | const age = computed(() => {
9 | return user.age+1
10 | })
11 | expect(age.value).toBe(12)
12 | })
13 | it('computed should be lazy', () => {
14 | const user = reactive({ age: 1 })
15 | const getter = jest.fn(() => {
16 | return user.age
17 | })
18 | const cValue = computed(getter);
19 | expect(getter).not.toHaveBeenCalled();
20 | expect(cValue.value).toBe(1);
21 | expect(getter).toHaveBeenCalledTimes(1); //调用了多少次
22 | cValue.value; //再次触发看是否只调用1次
23 | expect(getter).toHaveBeenCalledTimes(1);
24 | user.age = 2;
25 | expect(cValue.value).toBe(2);
26 |
27 | })
28 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { reactive } from "../reactive";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | const user = reactive({ age: 10 });
7 | let nextAge;
8 | effect(() => {
9 | nextAge = user.age + 1;
10 | });
11 | expect(nextAge).toBe(11);
12 | // 更新
13 | user.age++;
14 | expect(nextAge).toBe(12);
15 | });
16 | it("", () => {
17 | let foo = 10;
18 | const runner=effect(() => {
19 | foo++;
20 | return 'foo';
21 | });
22 | expect(foo).toBe(11);
23 | const r = runner();
24 | expect(foo).toBe(12);
25 | expect(r).toBe('foo')
26 |
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReactive, reactive } from "../reactive";
2 |
3 | describe("reactive", () => {
4 | it("happy path", () => {
5 | const origin = { foo: 1 };
6 | const observed = reactive(origin);
7 | expect(observed).not.toBe(origin);
8 | expect(observed.foo).toBe(1);
9 |
10 | expect(isReactive(observed)).toBe(true);
11 | expect(isReactive(origin)).toBe(false);
12 | expect(isProxy(observed)).toBe(true);
13 | });
14 | it("notsed reactive path", () => {
15 | const origin = {
16 | notsed: {
17 | foo: 1,
18 | },
19 | array: [{ bar: 2 }],
20 | };
21 | const observed = reactive(origin);
22 | expect(isReactive(observed.notsed)).toBe(true);
23 | expect(isReactive(observed.array)).toBe(true);
24 | expect(isReactive(observed.array[0])).toBe(true);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadOnly, readonly, isProxy } from "../reactive";
2 |
3 | describe("readonly", () => {
4 | it("happy path", () => {
5 | // no set
6 |
7 | const origin = { foo: 1, bar: { baz: 2 } };
8 | const wrapped = readonly(origin);
9 | expect(wrapped).not.toBe(origin);
10 | expect(wrapped.foo).toBe(1);
11 | expect(isReadOnly(wrapped)).toBe(true);
12 | expect(isProxy(wrapped)).toBe(true);
13 | });
14 | it("set warning", () => {
15 | console.warn = jest.fn();
16 | const user = readonly({ age: 2 });
17 | user.age = 3;
18 | expect(console.warn).toBeCalled();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/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 | // 存
4 | export function provide(key, value) {
5 | const currentInstance: any = getCurrentInstance();
6 |
7 | if (currentInstance) {
8 | let { provides } = currentInstance;
9 | console.log("存provides====", currentInstance);
10 | const parentProviders =
11 | currentInstance.parent && currentInstance.parent.provides;
12 | if (provides === parentProviders) {
13 | provides = currentInstance.provides = Object.create(parentProviders);
14 | }
15 | provides[key] = value;
16 | }
17 | }
18 | // 取
19 | export function inject(key, defaultValue) {
20 | const currentInstance: any = getCurrentInstance();
21 | console.log("inject=====parentProviders", currentInstance);
22 | if (currentInstance) {
23 | const { parent } = currentInstance;
24 | const parentProviders = parent.provides;
25 | if (key in parentProviders) {
26 | return parentProviders[key];
27 | } else if (defaultValue) {
28 | if (typeof defaultValue === "function") {
29 | return defaultValue();
30 | }
31 | return defaultValue;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/runtime-core/componemtPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "../shared/sharedFlags";
2 |
3 | const publicProxyMaps = {
4 | $el: (instance) => instance.vnode.el,
5 | $slots:(instance) => instance.slots,
6 | };
7 | export const publicInstanceProxyHandler = {
8 | get({ _: instance }, key) {
9 | if (key in instance.setupState) {
10 | return instance.setupState[key];
11 | }
12 | const { props } = instance;
13 |
14 | if (hasOwn(instance.setupState, key)) {
15 | return instance.setupState[key];
16 | } else if (hasOwn(props, key)) {
17 | return props[key];
18 | }
19 | const publicGetter = publicProxyMaps[key];
20 | if (publicGetter) {
21 | return publicGetter(instance);
22 | }
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { shallowReadonly } from "../reactivity/reactive";
2 | import { publicInstanceProxyHandler } from "./componemtPublicInstance";
3 | import { emit } from "./componentEmit";
4 | import { initProps } from "./componentProps";
5 | import { initSlots } from "./componentSlots";
6 |
7 | export function createComponentInstance(vnode, parent) {
8 | console.log("调试====", parent);
9 | const component = {
10 | vnode: vnode,
11 | type: vnode.type,
12 | setupState: {},
13 | props: {},
14 | provides:parent?parent.provides: {},
15 | parent,
16 | slot: {},
17 | isMounted:false,
18 | subTree:{},
19 | emit: () => {},
20 | };
21 | component.emit = emit.bind(null, component) as any; //拿到instance
22 | return component;
23 | }
24 | export function setComponentInstance(instance) {
25 | // initProps
26 | // initSlots
27 | initSlots(instance, instance.vnode.children);
28 | initProps(instance, instance.vnode.props);
29 | setupStatefulComponent(instance);
30 | }
31 | function setupStatefulComponent(instance) {
32 | setCurrentInstance(instance);
33 | const Component = instance.type;
34 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandler);
35 | const { setup } = Component;
36 | if (setup) {
37 | const setupResult = setup(shallowReadonly(instance.props), {
38 | emit: instance.emit,
39 | }); //props在子组件不去改变
40 | setCurrentInstance(null);
41 | handelSetupResult(setupResult, instance);
42 | }
43 | }
44 |
45 | function handelSetupResult(setupResult: any, instance: any) {
46 | if (typeof setupResult === "object") {
47 | instance.setupState = setupResult;
48 | }
49 | finishComponentSetup(instance);
50 | }
51 | function finishComponentSetup(instance: any) {
52 | const Component = instance.type;
53 | if (Component.render) {
54 | instance.render = Component.render;
55 | }
56 | }
57 |
58 | let currentInstance = null;
59 | export function getCurrentInstance() {
60 | return currentInstance;
61 | }
62 | export function setCurrentInstance(instance: any) {
63 | currentInstance = instance;
64 | }
65 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "../shared/sharedFlags";
2 |
3 | export function emit(instance, event,...args: any[]): void {
4 | console.log("event====", event);
5 | const { props } = instance;
6 | // TPP
7 | // 先去写一个特定的行为再去写一个通用的行为
8 |
9 | const handlersName = toHandlerKey(camelize(event));
10 | const handlers = props[handlersName];
11 | handlers && handlers(...args);
12 | }
13 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 |
2 | export function initProps(instance, rawProps) {
3 | instance.props = rawProps||{}
4 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/sharedFlags";
2 |
3 | export function initSlots(instance, children) {
4 | // 是否是数组,如果不是就给包裹一层
5 | // instance.slots = Array.isArray(children) ? children : [children];
6 | const { vnode } = instance;
7 | if (vnode.shapeFlags & ShapeFlags.SLOT_CHILDREN) {
8 | const slot = {};
9 | for (let key in children) {
10 | const value = children[key];
11 | slot[key] = (props) => normalizeSlotsValue(value(props));
12 | }
13 | instance.slots = slot;
14 | }
15 | }
16 | function normalizeSlotsValue(value) {
17 | return Array.isArray(value) ? value : [value];
18 | }
19 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { render } from "./render";
2 | import { createVnode } from "./vnode";
3 |
4 | export function createApp(rootComponent) {
5 | return {
6 | mount(rootContainer) {
7 | // 先转换成虚拟节点
8 | // 所有节点都基于虚拟节点
9 | const vnode = createVnode(rootComponent);
10 | render(vnode, rootContainer);
11 | },
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVnode } from "./vnode";
2 | export function h(type, props?, children?) {
3 | return createVnode(type, props, children);
4 | }
5 |
--------------------------------------------------------------------------------
/src/runtime-core/helper/renderSlot.ts:
--------------------------------------------------------------------------------
1 | import { createVnode } from "../vnode";
2 |
3 | export function renderSlot(slots, name?,props?) {
4 | const 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 { createApp } from "./createApp";
2 | export { h } from "./h";
3 | export { renderSlot } from "./helper/renderSlot";
4 | export { createTextVnode } from "./vnode";
5 | export { getCurrentInstance } from "./component";
6 | export { inject, provide } from "./apiInject";
7 | export { createRender } from './render';
8 | export {ref} from '../reactivity/ref'
9 |
--------------------------------------------------------------------------------
/src/runtime-core/render.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect";
2 | import { isObject } from "../reactivity/shared/index";
3 | import { hostPatchProp } from "../runtime-dom";
4 | import { ShapeFlags } from "../shared/sharedFlags";
5 | import { createComponentInstance, setComponentInstance } from "./component";
6 |
7 | export function render(vnode, container) {
8 | patch(null, vnode, container, null);
9 | console.log("render==", vnode, container);
10 | }
11 | function patch(n1, n2, container, parentComponent) {
12 | // 处理组件
13 | // 先判断是不是element
14 | // fragment只渲染children
15 | const { type, shapeFlags } = n2;
16 |
17 | switch (type) {
18 | case "Fragment":
19 | processFragment(n1, n2, container, parentComponent);
20 | break;
21 | case "Text":
22 | processText(n1, n2, container);
23 | break;
24 | default:
25 | if (shapeFlags & ShapeFlags.ELEMENT) {
26 | processElement(n1, n2, container, parentComponent);
27 | } else if (shapeFlags & ShapeFlags.STATEFUL_COMPONENT) {
28 | processComponent(n1, n2, container, parentComponent);
29 | }
30 | break;
31 | }
32 | }
33 |
34 | function processComponent(n1, vnode: any, container: any, parentComponent) {
35 | mountComponent(vnode, container, parentComponent);
36 | }
37 | function mountComponent(
38 | initialVNode: any,
39 | container: any,
40 | parentComponent: any
41 | ) {
42 | const instance = createComponentInstance(initialVNode, parentComponent);
43 | setComponentInstance(instance);
44 | setupRenderEffect(instance, container, initialVNode);
45 | }
46 |
47 | function setupRenderEffect(instance: any, container: any, initialVNode: any) {
48 | effect(() => {
49 | if (!instance.isMounted) {
50 | const { proxy } = instance;
51 | console.log("初始化===", proxy);
52 |
53 | const subTree = (instance.subTree = instance.render.call(proxy));
54 | patch(null, subTree, container, instance);
55 | initialVNode.el = subTree.el;
56 | instance.isMounted = true;
57 | } else {
58 | console.log("update");
59 | const { proxy } = instance;
60 | const subTree = instance.render.call(proxy);
61 | const prevSubTree = instance.subTree;
62 | instance.subTree = subTree;
63 | patch(prevSubTree, subTree, container, instance);
64 |
65 | // 更新
66 | }
67 | });
68 | }
69 | function processElement(n1, n2: any, container: any, parentComponent) {
70 | if (!n1) {
71 | // 初始化
72 | mountElement(n2, container, parentComponent);
73 | } else {
74 | // 更新
75 | patchElement(n1, n2, container, parentComponent);
76 | }
77 | }
78 | function patchElement(n1, n2, container, parentComponent) {
79 | console.log("patchElement");
80 | const oldProps = n1.props || {};
81 | const newProps = n2.props || {};
82 | const el = (n2.el = n1.el);
83 | patchChildren(n1,n2)
84 | patchProp(el,oldProps, newProps);
85 | }
86 | function patchChildren(n1, n2) {
87 | const {shapeFlag} = n2.shapeFlag;
88 | const {prevShapeFlag} = n1.shapeFlag;
89 | if(shapeFlag & ShapeFlags.TEXT_CHILDREN){
90 | if(prevShapeFlag & ShapeFlags.ARROW_CHILDREN){
91 | // 把老的清空
92 | unMountChildren(n1.children)
93 | }
94 | }
95 |
96 |
97 | }
98 | function unMountChildren(children){
99 | for(let i =0;i< children.length;i++){
100 | const el=children[i].el;
101 | // hostRemove(el);
102 | }
103 | }
104 | function patchProp(el,oldProps, newProps) {
105 | // 遍历新的props
106 | for (const key in newProps) {
107 | const prevProp = oldProps[key];
108 | const nextProp = newProps[key];
109 | console.log("111", prevProp, nextProp);
110 |
111 | // 如果props不想等就准备更新
112 | if (prevProp !== nextProp) {
113 | hostPatchProp(el, key, prevProp, nextProp);
114 | }
115 | }
116 | for (const key in oldProps) {
117 | // 如果不在新的props就需要删除当前熟悉
118 | if(!(key in newProps)){
119 | hostPatchProp(el, key, oldProps[key], null);
120 |
121 | }
122 | }
123 | }
124 | function mountElement(vnode: any, container: any, parentComponent) {
125 | const el = (vnode.el = document.createElement(vnode.type));
126 | const { children, props, shapeFlags } = vnode;
127 | if (shapeFlags & ShapeFlags.TEXT_CHILDREN) {
128 | el.textContent = children;
129 | } else if (shapeFlags & ShapeFlags.ARROW_CHILDREN) {
130 | mountChild(children, el, parentComponent);
131 | }
132 |
133 | for (const key in props) {
134 | const value = props[key];
135 | const isOn = (key) => /^on[A-Z]/.test(key);
136 | if (isOn(key)) {
137 | const event = key.slice(2).toLowerCase();
138 | el.addEventListener(event, value);
139 | } else {
140 | el.setAttribute(key, value);
141 | }
142 | }
143 |
144 | container.appendChild(el);
145 | }
146 | function mountChild(vnode: any, container: any, parentComponent) {
147 | console.log("mountChild", vnode);
148 | if (Array.isArray(vnode)) {
149 | vnode.forEach((el) => {
150 | patch(null, el, container, parentComponent);
151 | });
152 | }
153 | }
154 | function processFragment(n1, vnode: any, container: any, parentComponent) {
155 | mountChild(vnode, container, parentComponent);
156 | }
157 | function processText(n1, vnode: any, container: any) {
158 | // 只渲染文字
159 | const { children } = vnode;
160 | const textNode = (vnode.el = document.createTextNode(children));
161 | container.append(textNode);
162 | }
163 |
164 | export function createRender(options) {}
165 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/sharedFlags";
2 |
3 | export const Text = Symbol("Text");
4 | export function createVnode(type, props?, children?) {
5 | const vnode = {
6 | type,
7 | props,
8 | children,
9 | shapeFlags: getShapeFlags(type),
10 | el: null,
11 | };
12 | // children 位运算符
13 | if (typeof children === "string") {
14 | vnode.shapeFlags |= ShapeFlags.TEXT_CHILDREN;
15 | } else if (Array.isArray(children)) {
16 | vnode.shapeFlags |= ShapeFlags.ARROW_CHILDREN;
17 | }
18 | // 组件 slot
19 | if (vnode.shapeFlags & ShapeFlags.STATEFUL_COMPONENT) {
20 | if (typeof children === "object") {
21 | vnode.shapeFlags |= ShapeFlags.SLOT_CHILDREN;
22 | }
23 | }
24 | return vnode;
25 | }
26 | export function createTextVnode(text: string) {
27 | return createVnode('Text', {}, text);
28 | }
29 | function getShapeFlags(type: any) {
30 | return typeof type === "string"
31 | ? ShapeFlags.ELEMENT
32 | : ShapeFlags.STATEFUL_COMPONENT;
33 | }
34 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRender } from "../runtime-core/render";
2 | function createElement(type) {
3 | return document.createElement(type);
4 | }
5 | function patchProp(el, key, prevValue, nextValue) {
6 | const isOn = (key) => /^on[A-Z]/.test(key);
7 | if (isOn(key)) {
8 | const event = key.slice(2).toLowerCase();
9 | el.addEventListener(event, nextValue);
10 | } else {
11 | el.setAttribute(key, nextValue);
12 | }
13 | }
14 | export function hostPatchProp(el, key, prevValue, nextValue) {
15 | const isOn = (key) => /^on[A-Z]/.test(key);
16 | if (isOn(key)) {
17 | const event = key.slice(2).toLowerCase();
18 | el.addEventListener(event, nextValue);
19 | } else {
20 | if (nextValue === undefined || nextValue === null) {
21 | el.removeAttribute(key, nextValue);
22 | } else {
23 | el.setAttribute(key, nextValue);
24 | }
25 | }
26 | }
27 | function insert(el, parent) {
28 | parent.appendChild(el);
29 | }
30 |
31 | const renderer = createRender({
32 | createElement,
33 | patchProp,
34 | insert,
35 | hostPatchProp,
36 | });
37 |
--------------------------------------------------------------------------------
/src/shared/sharedFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, //01
3 | STATEFUL_COMPONENT = 1 << 1, //10
4 | TEXT_CHILDREN = 1 << 2, //100
5 | ARROW_CHILDREN = 1 << 3, //1000
6 | SLOT_CHILDREN = 1 << 4, //
7 | }
8 | export const hasOwn = (val, key) =>
9 | Object.prototype.hasOwnProperty.call(val, key);
10 | export const capitalize = (str: string) => {
11 | return str.charAt(0).toUpperCase() + str.slice(1);
12 | };
13 | export const toHandlerKey = (str: string) => {
14 | return str ? `on${capitalize(str)}` : "";
15 | };
16 | export const camelize = (str: string) => {
17 | return str.replace(/-(\w)/g, (_, c: string) => {
18 | return c ? c.toUpperCase() : "";
19 | });
20 | };
21 |
--------------------------------------------------------------------------------
/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": ["DOM","ES6"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "esnext", /* Specify what module code is generated. */
28 | // "rootDir": "./", /* Specify the root folder within your source files. */
29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "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. */
50 | // "outDir": "./", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | }
101 | }
102 |
--------------------------------------------------------------------------------