├── .gitignore
├── README.md
├── babel.config.js
├── docs
├── arithmetic.md
├── diff.md
├── effect.md
├── quession.md
└── reactive.md
├── drawio
├── diff.drawio
├── reative.drawio
└── runtime-core.drawio
├── example
├── apiInjectExample
│ ├── App.js
│ ├── index.html
│ └── index.js
├── helloWord
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── index.js
├── nexttickExample
│ ├── App.js
│ ├── index.html
│ └── index.js
├── patchElementExample
│ ├── App.js
│ ├── Array2Array.js
│ ├── Array2Text.js
│ ├── Text2Array.js
│ ├── Text2Text.js
│ ├── index.html
│ └── index.js
├── pixijs
│ ├── App.js
│ ├── index.html
│ └── index.js
├── slotsExample
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── index.js
├── updateElementExample
│ ├── App.js
│ ├── index.html
│ └── index.js
├── updateElementPropsExample
│ ├── App.js
│ ├── Child.js
│ ├── index.html
│ └── index.js
└── updatePropsExample
│ ├── App.js
│ ├── index.html
│ └── index.js
├── lib
├── mini-vue.cjs.js
└── mini-vue.esm.js
├── package.json
├── rollup.config.js
├── src
├── index.ts
├── reactivity
│ ├── baseHandlers.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── lis.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ ├── shallowReactive.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── component.ts
│ ├── componentEmits.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── componentUpdate.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlot.ts
│ ├── index.ts
│ ├── renderer.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── index.ts
│ └── shapeFlags.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 |
4 | yarn-error.log
5 | yarn.lock
6 |
7 | /node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 😀 mini-vue3
2 |
3 | 👴 一步一步学习vue3 源码
4 |
5 | 👵 编写代码节奏:先写测试,再写实现,再写优化
6 |
7 | 
8 |
9 |
10 | # 🍁 Project Process
11 |
12 | ## Reactive
13 |
14 | ## Runtime-core
15 |
16 | ## Compiler-core
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}],
4 | '@babel/preset-typescript'
5 | ]
6 | }
--------------------------------------------------------------------------------
/docs/arithmetic.md:
--------------------------------------------------------------------------------
1 |
2 | # 算法
3 |
4 | ## 最长递归子序列(LIS)
--------------------------------------------------------------------------------
/docs/diff.md:
--------------------------------------------------------------------------------
1 |
2 | # Diff
3 |
4 | ToBePathed: 新节点需要进行对比的长度
5 |
6 | newIndexToOldIndexMap: 获取新节点需要进行中间对比的index索引
7 |
8 | 0 1 2 3 4 5
9 | a b (e f d) g
10 | a b (d e f) g
11 |
12 | diff
13 |
14 | * 经过左侧对比,index的位置是2
15 | * 经过右侧对比,新节点位置是 e1 = 4, e2 = 4
16 | * index位置大于e1,且 index少于等e2,证明着新节点比旧节点的要多,所以要新增节点
17 | * index位置大于新节点e1,证明新节点少于旧节点,需要减少旧节点上的。
18 | * 两种情况都不是的话,需要进行中间对比
19 |
20 | diff middle
21 |
22 | * 获取`新节点`需要对比的节点长度toBePatched = e2 - s2 + 1 (索引需要加1)
23 | * 获取新节点中需要中间对比的节点的key和索引关系 -> keyToNewIndexMap
24 | * 生成新节点需要映射索引的Array(toBePatched) -> newIndexToOldIndexMap
25 | * 循环需要中间对比的旧节点
26 | * 利用旧节点的key值去keyToNewIndexMap找相对应的key值,获取对应的索引nextIndex
27 | * 如果旧节点是没有这个key值的,就需要在新节点上进行遍历isSomeVNodeType(n1, n2),知道对应的索引nextIndex
28 | * 如果nextIndex为空,证明着新节点里面不需要用到,需要进行删除hostRemove()
29 | * 如果nextIndex不为空,此时需要新旧节点进行patch一下
30 | * 此时也需要把新节点的索引假加入到对应的newIndexToOldIndexMap中。
31 | * 旧节点循环后,此时会得到了newIndexToOldIndexMap
32 | * 利用算法最长子序列算法LIS得到对应的索引列表
33 |
34 |
--------------------------------------------------------------------------------
/docs/effect.md:
--------------------------------------------------------------------------------
1 |
2 | # effect
3 |
4 | 功能:收集依赖(观察者 -> 收集依赖),通知收集的依赖(通知观察者 -> 触发依赖)
5 |
6 | 提供三个重要的Function
7 |
8 | effect是将传入的函数转化为reactiveEffect格式的函数
9 |
10 | track主要功能是将reactiveEffect添加为`target[key]`的观察者
11 |
12 | trigger主要功能是通知`target[key]`的观察者(将观察者队列函数一一取出来执行)
13 |
--------------------------------------------------------------------------------
/docs/quession.md:
--------------------------------------------------------------------------------
1 |
2 | dep 对应一个 key 这个逻辑需要自己重新来理一下 (未完成)
3 |
4 | 代理对象 $el 需要理解多一次
5 |
6 | props重新看一次
7 |
8 | diff重新看一次
--------------------------------------------------------------------------------
/docs/reactive.md:
--------------------------------------------------------------------------------
1 |
2 | # reactive
3 |
4 | 对象响应
5 |
6 | Vue2 使用 Object.defineProperty()
7 |
8 | Vue3 使用 Proxy代理
9 |
10 | getter
11 |
12 | Vue2: `return target[key]`
13 |
14 | Vue3: `return Reflect.get(target, key)`
15 |
16 | setter
17 |
18 | Vue2: `target[key] = value`
19 |
20 | Vue3: `Reflect.set(target, key, value)`
21 |
22 |
23 |
--------------------------------------------------------------------------------
/drawio/diff.drawio:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenLiang0419/mini-vue3/c87358f857be17ac0e637e9c92de727a4bd165a2/drawio/diff.drawio
--------------------------------------------------------------------------------
/drawio/reative.drawio:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenLiang0419/mini-vue3/c87358f857be17ac0e637e9c92de727a4bd165a2/drawio/reative.drawio
--------------------------------------------------------------------------------
/drawio/runtime-core.drawio:
--------------------------------------------------------------------------------
1 | 7V1Lk6M2EP41rkoOkwIE2D7OazepylZtZQ/JHDUgY2UBESGP7fz6CJB4WHjXG9vgsfuyi1oSdH9q+ms98EzQY7L5yHG2/MRCEk8cK9xM0NPEcaaOJf8tBNtK4FpeJYg4DSuR3Qi+0H+JEqp+0YqGJO80FIzFgmZdYcDSlASiI8Ocs3W32YLF3admOCKG4EuAY1P6Jw3FspLOnGkj/5XQaKmfbPvzqibBurGyJF/ikK1bIvQ8QY+cMVFdJZtHEhfYaVyqfh/21NaKcZKKQzq4VYc3HK+UbUovsdXGklDaroqMiyWLWIrj50b6wNkqDUlxR0uWmja/M5ZJoS2FfxMhtmog8UowKVqKJFa1ptbKkJyteKD0UJoJzCOiWqFKVGjY6qYs/UhYQgTfygacxFjQt+7oYeUEUd2uwUleKKj6YXMM2PgqFTQhd4H0dQPCLkDrJRXkS4ZLw9byBTkQjDfCBdl801BVq71rq4uqvG6c1dZtli1H9a3jofHfj0ch06O8sTwKmR5FJAR8dF+y/RGdafZ+nMkznWk6ljN5BmwZFsFydF9C1oi+ZNsGKhfrTFPTmXTuM7w3TXtw82Op1kNI3zr4+f+sisTlIWAx4xN0X2gTvf6EJCdI7eWDLNduLqc/F9cFItYrDr5GJbR3O30dz6t77Fw33RcsFXcLnNB4W/WUquAkKysRckuN0pzFEoXdivqGPV1WnBYB2ErJen+/hKUsL1+ajjZ5Of6FLrabbeq6mKbkTnt2WTtv1ZZv4F11t7I246RBVV5F6v8Sfdku/R78Ut2Z41nhwrxNxllA8vyRJRlLC2dUt5XuUN25+zQpLodbS0cOJa57QChBZwoldfY+aOSQYPHtX6p/WXgpCr94uvi0aVc+bVXpoIijw0sn5DhjhRytDcScC4s5+9C/LDT2WHymQJpIHxL/N4xeZHD1x8zT3tOahI6Qnag5Gy1qmssSEDUhap4pal6lUUdSQcAJFqTmgt/SXOC00OYEnHCRTDH1DmCK+ZmYYm6Eu/ssMyCRloiu3bng7KscpXJEZRMU+q++5xe+S+O4JV8sFk4QSDmOaZRKWSCRkl6IHgqEaIDje1WR0DAseacP8u6gnAB1Z/p91N0e0J1T0LM/7tynme68tOuOmvvMTBY//d5C2fWec7xtNcgYTUXeuvPnQtCMtG35Xmeo9XLshz0dZt9uLy8qFZqxrm05bPhntzH89lhJnI5q1w7wydcWhnm/bGd23hcMvaNl6r6B1WtuI0x/+naLYPoD0x+Y/ty0UUfO6XIiVtlpl/fe0QRvfsBU42wTPGSeirhYLtS81+HC0TZQkDtKFrmhokoip54qvrSqmhyyKOgUsk4961zzhNtavaNy8kM+e1JFpMdfH3NEO+9EpZXqtjO4P5g1woYZ5D6QJoBRt2DUKRK6P8rTlc+LRXE+fOic7iIzPbtO40ZJ9fpOmV1qqtez64v80VI92PUF6gdCAaNuwSig/rNQvzXvbmogd0jq7/tkCAgMCAzCIhh1bUYBgZ2DwGbOiPzl9O3YA38Bf0FUBKOuzagj+YumVHzmLMuBt8rd9Vl3ydUfkrZg3RBoC4IhGHULRgFtnXWrcFDe6jsUBrwFvAXR8KaNgmO+x3zHOR0voCPzO87LPfrR8zNfaLQP/pH5Q19AhUCFwBpg1PUZBTtngxz9cAc9+gGfbAKBQVgEo27BKCCwIY5+DMpf+lkt/qL5p+L3ACUCu8hc4Q8R2RLaDvrO3ETft03wnR8HXxabv4tQfSLZ/HEJ9Pwf
--------------------------------------------------------------------------------
/example/apiInjectExample/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, provide, inject } from '../../lib/mini-vue.esm.js'
3 |
4 | window.self = null
5 |
6 | const Foo = {
7 | setup() {
8 | provide("foo", "foo");
9 | provide("bar", "bar");
10 | return {
11 | msg: 'foo-1'
12 | }
13 | },
14 | render() {
15 | const compOne = h("h1", {}, this.msg);
16 | const compTwo = h(FooSecond)
17 |
18 | return h("div", {}, [compOne, compTwo])
19 | }
20 | };
21 |
22 | const FooSecond = {
23 | setup() {
24 | provide('foo', 'fooTwo')
25 | const foo = inject('foo')
26 | return {
27 | msg: 'foo-2',
28 | foo
29 | }
30 | },
31 | render() {
32 | const compOne = h("h1", {}, this.msg + '->' + this.foo);
33 | const bar = h(Bar)
34 | return h("div", {}, [compOne, bar])
35 | }
36 | };
37 |
38 | const Bar = {
39 | setup() {
40 | const bar = inject('bar')
41 | const foo = inject('foo')
42 |
43 | return {
44 | foo, bar
45 | }
46 | },
47 | render() {
48 | const app = h("h1", {}, "Bar")
49 | const constom = h('h2', {}, `Bar-provide-inject: foo - ${this.foo}, bar - ${this.bar}`)
50 |
51 | return h('div', {}, [app, constom])
52 | }
53 | }
54 |
55 | export const App = {
56 | name: 'App',
57 | setup() {
58 | // console.log("current instance", getCurrentInstance())
59 | return {
60 | username: 'BoLi-luyi'
61 | }
62 | },
63 | render() {
64 | window.self = this
65 | const app = h("h1", {}, "App")
66 | const foo = h(Foo)
67 |
68 | return h("div", {}, [app, foo])
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/example/apiInjectExample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/apiInjectExample/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/helloWord/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h } from '../../lib/mini-vue.esm.js'
3 | import { Foo } from './Foo.js';
4 |
5 | window.self = null
6 | export const App = {
7 | name: 'App',
8 | setup() {
9 | return {
10 | username: 'BoLi-luyi'
11 | }
12 | },
13 | render() {
14 | window.self = this
15 | return h(
16 | 'div',
17 | {
18 | id: 'root',
19 | class: 'big',
20 | // onClick: () => {
21 | // console.log('onclick')
22 | // },
23 | // onMouseleave: () => {
24 | // console.log('mouse leave')
25 | // }
26 | },
27 | [
28 | h('h1', { class: "red" }, "Hi"),
29 | h('h2', { class: "blue" }, "mini-vue"),
30 | h(Foo, {
31 | count: 1, onAdd() {
32 | console.log('onAdd')
33 | },
34 | onAddCount(value) {
35 | console.log('onAddCount', value)
36 | }
37 | }, {
38 | "header": h('h1', {}, "123 header"),
39 | "footer": h('h1', { class: 'blue' }, "abc footer")
40 | })
41 | ]
42 | )
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/example/helloWord/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlot } from "../../lib/mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup(props, { emit, slots }) {
6 | // 1. setup能获取到props
7 | console.log(props)
8 |
9 | // 3. props是一个shallow Readonly 无法修改
10 | // props.count++
11 | console.log(props)
12 |
13 | // 事件
14 | const emitAdd = () => {
15 | console.log('emitAdd')
16 | emit('add')
17 | emit('add-count', 123)
18 | }
19 |
20 | // slots
21 | console.log(slots)
22 |
23 | return {
24 | emitAdd
25 | }
26 | },
27 | render() {
28 | // 1. slots
29 | console.log('this.$slots', this.$slots)
30 | // 2. 能够调用到props
31 | // 2. this.$slots -> array, 实际上渲染时候需要的是vnode
32 | // 所以就有了renderSlot
33 | // 3. 具名插槽
34 | // 处理solt-name, renderslot(slot, name)
35 | return h('div', {}, [
36 | renderSlot(this.$slots, 'header'),
37 | h('h', {}, 'count: ' + this.count),
38 | h('button', { onClick: this.emitAdd}, "emitAdd"),
39 | renderSlot(this.$slots, 'footer')
40 | ])
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/example/helloWord/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/helloWord/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/nexttickExample/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref, getCurrentInstance, nextTick } from '../../lib/mini-vue.esm.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | setup() {
7 | const count = ref(0)
8 | const instance = getCurrentInstance()
9 | const onClick = () => {
10 | for (let index = 0; index < 100; index++) {
11 | count.value = index
12 | }
13 | console.log('app', instance)
14 |
15 | nextTick(() => {
16 | console.log('app - nextTick', instance)
17 | })
18 | }
19 | return {
20 | count,
21 | onClick
22 | }
23 | },
24 | render() {
25 | return h(
26 | 'div',
27 | {
28 | id: "root"
29 | },
30 | [
31 | h("p", {}, "nextTick Example"),
32 | h("div", {}, "count:" + this.count), // 收集依赖
33 | h("button", { onClick: this.onClick }, "click")
34 | ]
35 | )
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/example/nexttickExample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/nexttickExample/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/patchElementExample/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h } from '../../lib/mini-vue.esm.js'
3 | import { Array2Text } from './Array2Text.js'
4 | import { Text2Text } from './Text2Text.js'
5 | import { Text2Array } from './Text2Array.js'
6 | import { Array2Array } from './Array2Array.js';
7 |
8 | export const App = {
9 | name: 'App',
10 | setup() {
11 | return {
12 | }
13 | },
14 | render() {
15 | const button = h('div', {}, 'Change Ref')
16 |
17 | return h(
18 | 'div',
19 | {
20 | id: "root"
21 | },
22 | [
23 | button,
24 | // 更新流程:Array更换成Text
25 | // h(Array2Text),
26 | // 更新流程:Text更换成Text
27 | // h(Text2Text)
28 | // 更新流程:Text更换成Array
29 | // h(Text2Array)
30 | // 更新流程:Array更换成Array
31 | h(Array2Array)
32 | ]
33 | )
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/example/patchElementExample/Array2Array.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref } from '../../lib/mini-vue.esm.js'
3 |
4 | export const Array2Array = {
5 | name: 'Array2Array',
6 | setup() {
7 | const patchStatus = ref(true)
8 | window.patchStatus = patchStatus
9 | return {
10 | patchStatus
11 | }
12 | },
13 | render() {
14 |
15 | // 左侧
16 | // old: a, b, c
17 | // new: a, b, d, e
18 | // c -> d, e
19 | // const arrayComponent = h('div', {},
20 | // [
21 | // h('h1', { key: 'A' }, 'Array-A'),
22 | // h('h1', { key: 'B' }, 'Array-B'),
23 | // h('h1', { key: 'C' }, 'Array-C')
24 | // ])
25 | // const textCompoent = h('div', {}, [
26 | // h('h1', { key: 'A' }, 'Array-A'),
27 | // h('h1', { key: 'B' }, 'Array-B'),
28 | // h('h1', { key: 'D' }, 'Array-D'),
29 | // h('h1', { key: 'E' }, 'Array-E')
30 | // ])
31 |
32 | // 右侧
33 | // old: c, a, b
34 | // new: d, e, a, b
35 | // const arrayComponent = h('div', {},
36 | // [
37 | // h('h1', { key: 'C' }, 'Array-C'),
38 | // h('h1', { key: 'A' }, 'Array-A'),
39 | // h('h1', { key: 'B' }, 'Array-B')
40 | // ])
41 | // const textCompoent = h('div', {}, [
42 | // h('h1', { key: 'D' }, 'Array-D'),
43 | // h('h1', { key: 'E' }, 'Array-E'),
44 | // h('h1', { key: 'A' }, 'Array-A'),
45 | // h('h1', { key: 'B' }, 'Array-B')
46 | // ])
47 |
48 | // 新的比老的长 -- 新增
49 | // 左侧
50 | // const arrayComponent = h('div', {},
51 | // [
52 | // h('h1', { key: 'A' }, 'Array-A'),
53 | // h('h1', { key: 'B' }, 'Array-B')
54 | // ])
55 | // const textCompoent = h('div', {}, [
56 | // h('h1', { key: 'A' }, 'Array-A'),
57 | // h('h1', { key: 'B' }, 'Array-B'),
58 | // h('h1', { key: 'D' }, 'Array-D'),
59 | // h('h1', { key: 'E' }, 'Array-E')
60 | // ])
61 |
62 | // 右侧
63 | // const arrayComponent = h('div', {},
64 | // [
65 | // h('h1', { key: 'A' }, 'Array-A'),
66 | // h('h1', { key: 'B' }, 'Array-B')
67 | // ])
68 | // const textCompoent = h('div', {}, [
69 | // h('h1', { key: 'D' }, 'Array-D'),
70 | // h('h1', { key: 'E' }, 'Array-E'),
71 | // h('h1', { key: 'A' }, 'Array-A'),
72 | // h('h1', { key: 'B' }, 'Array-B')
73 | // ])
74 |
75 | // 旧的比新的长 -- 删除
76 | // 左侧
77 | // const arrayComponent = h('div', {},
78 | // [
79 | // h('h1', { key: 'A' }, 'Array-A'),
80 | // h('h1', { key: 'B' }, 'Array-B'),
81 | // h('h1', { key: 'D' }, 'Array-D'),
82 | // h('h1', { key: 'E' }, 'Array-E')
83 |
84 | // ])
85 | // const textCompoent = h('div', {},
86 | // [
87 | // h('h1', { key: 'A' }, 'Array-A'),
88 | // h('h1', { key: 'B' }, 'Array-B')
89 | // ])
90 |
91 | // 右侧
92 | // const arrayComponent = h('div', {},
93 | // [
94 | // h('h1', { key: 'D' }, 'Array-D'),
95 | // h('h1', { key: 'E' }, 'Array-E'),
96 | // h('h1', { key: 'A' }, 'Array-A'),
97 | // h('h1', { key: 'B' }, 'Array-B')
98 |
99 | // ])
100 | // const textCompoent = h('div', {},
101 | // [
102 | // h('h1', { key: 'A' }, 'Array-A'),
103 | // h('h1', { key: 'B' }, 'Array-B')
104 | // ])
105 |
106 | // 中间对比
107 | const arrayComponent = h('div', {},
108 | [
109 | h('h1', { key: 'D' }, 'Array-D'),
110 | h('h1', { key: 'C' }, 'Array-C'),
111 | h('h1', { key: 'G' }, 'Array-G'),
112 | h('h1', { key: 'E' }, 'Array-E'),
113 | h('h1', { key: 'A' }, 'Array-A'),
114 | h('h1', { key: 'B' }, 'Array-B')
115 | ])
116 |
117 | const textCompoent = h('div', {},
118 | [
119 | h('h1', { key: 'D' }, 'Array-D'),
120 | h('h1', { key: 'F' }, 'Array-F'),
121 | h('h1', { key: 'C' }, 'Array-C'),
122 | h('h1', { key: 'G' }, 'Array-G'),
123 | h('h1', { key: 'A' }, 'Array-A'),
124 | h('h1', { key: 'B' }, 'Array-B')
125 |
126 | ])
127 |
128 | return this.patchStatus === true ? arrayComponent : textCompoent
129 | }
130 | };
131 |
--------------------------------------------------------------------------------
/example/patchElementExample/Array2Text.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref } from '../../lib/mini-vue.esm.js'
3 |
4 | export const Array2Text = {
5 | name: 'Array2Text',
6 | setup() {
7 | const patchStatus = ref(true)
8 | window.patchStatus = patchStatus
9 | return {
10 | patchStatus
11 | }
12 | },
13 | render() {
14 | const arrayComponent = h('div', {}, [h('h1', {}, 'Array-1'), h('h1', {}, 'Array-2')])
15 | const textCompoent = h('div', {}, 'Text-Component')
16 |
17 | return this.patchStatus === true ? arrayComponent : textCompoent
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/example/patchElementExample/Text2Array.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref } from '../../lib/mini-vue.esm.js'
3 |
4 | export const Text2Array = {
5 | name: 'Text2Text',
6 | setup() {
7 | const patchStatus = ref(true)
8 | window.patchStatus = patchStatus
9 | return {
10 | patchStatus
11 | }
12 | },
13 | render() {
14 | const oldText = h('div', {}, 'Text-Old')
15 | const arrayComponent = h('div', {}, [h('h1', {}, 'Array-1'), h('h1', {}, 'Array-2')])
16 |
17 | return this.patchStatus === true ? oldText : arrayComponent
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/example/patchElementExample/Text2Text.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref } from '../../lib/mini-vue.esm.js'
3 |
4 | export const Text2Text = {
5 | name: 'Text2Text',
6 | setup() {
7 | const patchStatus = ref(true)
8 | window.patchStatus = patchStatus
9 | return {
10 | patchStatus
11 | }
12 | },
13 | render() {
14 | const oldText = h('div', {}, 'Text-Old')
15 | const newText = h('div', {}, 'Text-New')
16 |
17 | return this.patchStatus === true ? oldText : newText
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/example/patchElementExample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/patchElementExample/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/pixijs/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 |
3 | export const App = {
4 | setup() {
5 | return {
6 | x: 100,
7 | y: 100
8 | }
9 | },
10 | render() {
11 | return h('com', { x: this.x, y: this.y})
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/example/pixijs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/pixijs/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createRenderer } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | console.log(PIXI)
7 | let app = new PIXI.Application({ width: 640, height: 360 });
8 |
9 | document.body.appendChild(app.view)
10 |
11 | const renderer = createRenderer({
12 | createElement(type) {
13 | if (type === 'com') {
14 | const grap = new PIXI.Graphics()
15 | grap.beginFill(0xff0000);
16 | grap.drawRect(0, 0, 200, 100);
17 | grap.endFill()
18 | return grap
19 | }
20 | },
21 | patchProps(el, key, value) {
22 | el[key] = value
23 | },
24 | insert(el, container) {
25 | container.addChild(el)
26 | }
27 | })
28 |
29 | renderer.createApp(App).mount(app.stage)
30 | // const container = document.querySelector('#app');
31 | // createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/slotsExample/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, createTextVNode, getCurrentInstance } from '../../lib/mini-vue.esm.js'
3 | import { Foo } from './Foo.js';
4 |
5 | window.self = null
6 | export const App = {
7 | name: 'App',
8 | setup() {
9 | console.log("current instance", getCurrentInstance())
10 | return {
11 | username: 'BoLi-luyi'
12 | }
13 | },
14 | render() {
15 | window.self = this
16 | const app = h("h1", {}, "app")
17 | const foo = h(Foo,
18 | {},
19 | // [h("div", {}, "app1")]
20 | {
21 | header: ({ msg }) => [h("h1", {}, "abc header " + msg), createTextVNode('textVnode')],
22 | footer: () => h("h1", {}, "asd footer")
23 | }
24 | )
25 |
26 | return h("div", {}, [app, foo])
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/example/slotsExample/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlot, getCurrentInstance } from "../../lib/mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup() {
6 | console.log("current instance", getCurrentInstance())
7 | return {}
8 | },
9 | render() {
10 | // 1. slots
11 | console.log('this.$slots', this.$slots)
12 |
13 | const msg = 'msg'
14 | const foo = h('h1', {}, "foo")
15 | // 2. this.$slots -> array, 实际上渲染时候需要的是vnode
16 | // 所以就有了renderSlot
17 | // 3. 具名插槽
18 | // 处理solt-name, renderslot(slot, name)
19 | // 4. 作用域插槽
20 | return h('div', {}, [renderSlot(this.$slots, "header", { msg }), foo, renderSlot(this.$slots, "footer")])
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/example/slotsExample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/slotsExample/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/updateElementExample/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref } from '../../lib/mini-vue.esm.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | setup() {
7 | const count = ref(0)
8 | const onClick = () => {
9 | count.value++;
10 | }
11 | return {
12 | username: 'BoLi-luyi',
13 | count,
14 | onClick
15 | }
16 | },
17 | render() {
18 | return h(
19 | 'div',
20 | {
21 | id: "root"
22 | },
23 | [
24 | h("div", {}, "count:" + this.count), // 收集依赖
25 | h("button", { onClick: this.onClick }, "click")
26 | ]
27 | )
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/example/updateElementExample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/updateElementExample/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/updateElementPropsExample/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref } from '../../lib/mini-vue.esm.js'
3 | import { Child } from './Child.js'
4 |
5 | export const App = {
6 | name: 'App',
7 | setup() {
8 | const count = ref(0)
9 | const onClick = () => {
10 | count.value++;
11 | }
12 | const username = ref('BoLi-luyi')
13 |
14 | window.username = username
15 |
16 | const onClickComp = () => {
17 | username.value = 'luyi'
18 | }
19 | return {
20 | username,
21 | count,
22 | onClick,
23 | onClickComp
24 | }
25 | },
26 | render() {
27 | return h(
28 | 'div',
29 | {
30 | id: "root"
31 | },
32 | [
33 | h(Child, { msg: this.username }),
34 | h("button", { onClick: this.onClickComp }, "click2"),
35 | h("div", {}, "count: " + this.count),
36 | h("button", { onClick: this.onClick }, "click")
37 | ]
38 | )
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/example/updateElementPropsExample/Child.js:
--------------------------------------------------------------------------------
1 |
2 | import { h } from '../../lib/mini-vue.esm.js'
3 |
4 | export const Child = {
5 | name: 'Child',
6 | setup() {},
7 | render() {
8 | return h("div", {}, [h("div", {}, "child: " + this.$props.msg)]);
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/example/updateElementPropsExample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/updateElementPropsExample/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/example/updatePropsExample/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, ref } from '../../lib/mini-vue.esm.js'
3 |
4 | export const App = {
5 | name: 'App',
6 | setup() {
7 | const obj = ref({
8 | foo: 'foo',
9 | bar: 'bar'
10 | })
11 |
12 | // 场景1: 修改props值
13 | const onClickOne = () => {
14 | obj.value.foo = 'foo-new'
15 | }
16 |
17 | // 场景2: 删除props值 ==> undefined
18 | const onClickTwo = () => {
19 | obj.value.bar = undefined
20 | }
21 |
22 | // 场景3: 在新的props没有
23 | const onClickThree = () => {
24 | obj.value = {
25 | foo: 'foo-three'
26 | }
27 | }
28 |
29 | return {
30 | username: 'BoLi-luyi',
31 | obj,
32 | onClickOne,
33 | onClickTwo,
34 | onClickThree
35 | }
36 | },
37 | render() {
38 | return h(
39 | 'div',
40 | {
41 | id: "root",
42 | ...this.obj
43 | },
44 | [
45 | h("button", { onClick: this.onClickOne }, "场景1: 修改props值"),
46 | h("button", { onClick: this.onClickTwo }, "场景2: 删除props值"),
47 | h("button", { onClick: this.onClickThree }, "场景3: 在新的props没有")
48 | ]
49 | )
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/example/updatePropsExample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/updatePropsExample/index.js:
--------------------------------------------------------------------------------
1 |
2 | // vue3的基本启动配置
3 | import { createApp } from '../../lib/mini-vue.esm.js';
4 | import { App } from './App.js';
5 |
6 | const container = document.querySelector('#app');
7 | createApp(App).mount(container);
--------------------------------------------------------------------------------
/lib/mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | /**
6 | * 判断是否是对象
7 | * @param target
8 | * @returns
9 | */
10 | const isObject = (target) => {
11 | return target !== null && typeof target === 'object';
12 | };
13 | const extend = Object.assign;
14 | const hasChanged = (oldValue, newValue) => {
15 | return !Object.is(oldValue, newValue);
16 | };
17 | const isString = (val) => typeof val === 'string';
18 | const isOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
19 | // add -> Add
20 | const capitalize = (str) => {
21 | return str.charAt(0).toUpperCase() + str.slice(1);
22 | };
23 | // add -> Add -> onAdd
24 | const toHandlerKey = (str) => {
25 | return str ? "on" + capitalize(str) : "";
26 | };
27 | // add-count -> addCount -> AddCount -> onAddCount
28 | // 自定义事件是驼峰写法
29 | const camelize = (str) => {
30 | return str.replace(/-(\w)/g, (match, c) => {
31 | return c ? c.toUpperCase() : "";
32 | });
33 | };
34 |
35 | const Fragment = Symbol('Fragment');
36 | const Text = Symbol('Text');
37 | const createVNode = (type, props, children) => {
38 | const vnode = {
39 | type,
40 | props,
41 | shapeFlag: getShapeFlags(type),
42 | key: props && props.key,
43 | children,
44 | el: null
45 | };
46 | // 使用二进制来进行判断
47 | if (isString(children)) {
48 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
49 | }
50 | else {
51 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
52 | }
53 | // 初始化slot
54 | // 组件 + children => object
55 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
56 | if (isObject(children)) {
57 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
58 | }
59 | }
60 | return vnode;
61 | };
62 | const createTextVNode = (str) => {
63 | return createVNode(Text, {}, str);
64 | };
65 | function getShapeFlags(type) {
66 | return isString(type) ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */;
67 | }
68 |
69 | const h = (type, props, children) => {
70 | return createVNode(type, props, children);
71 | };
72 |
73 | const renderSlot = ($slots, name, props) => {
74 | // return vnode
75 | // return createVNode("div", {}, $slots)
76 | const slot = $slots[name];
77 | if (slot) {
78 | if (typeof slot === 'function') {
79 | return createVNode(Fragment, {}, slot(props));
80 | }
81 | }
82 | };
83 |
84 | // target -> key -> dep
85 | const targetKeyMap = new Map();
86 | let activeEffect;
87 | // 是否应该收集
88 | let shouldTrack = false;
89 | class ReactiveEffect {
90 | constructor(fn, scheduler) {
91 | this.scheduler = scheduler;
92 | this.deps = [];
93 | this.state = true;
94 | this.fn = fn;
95 | this.scheduler = scheduler;
96 | }
97 | run() {
98 | // 判断是不是被stop
99 | if (!this.state) {
100 | return this.fn();
101 | }
102 | // 如果不是被stop,可被收集依赖
103 | // shouldTrack 打开
104 | shouldTrack = true;
105 | activeEffect = this;
106 | const runner = this.fn();
107 | // shouldTrack 是全局,所以需要被reset
108 | shouldTrack = false;
109 | return runner;
110 | }
111 | stop() {
112 | if (this.state) {
113 | if (this.onStop) {
114 | this.onStop();
115 | }
116 | cleanDeps(this);
117 | this.state = false;
118 | }
119 | }
120 | }
121 | /**
122 | * 清除所有依赖dep
123 | * @param effect
124 | */
125 | const cleanDeps = (effect) => {
126 | effect.deps.forEach((dep) => {
127 | dep.delete(effect);
128 | });
129 | };
130 | /**
131 | * 收集依赖
132 | * target -> key -> dep
133 | * @param target
134 | * @param key
135 | * @returns
136 | */
137 | const track = (target, key) => {
138 | // 判断是否可以被收集依赖
139 | // 1. shouldTrack 是否放通
140 | // 2. activeEffect 在run时候是否有被赋值
141 | if (!isTracking())
142 | return;
143 | let depMap;
144 | let dep;
145 | if (!targetKeyMap.has(target)) {
146 | depMap = new Map();
147 | targetKeyMap.set(target, depMap);
148 | }
149 | depMap = targetKeyMap.get(target);
150 | if (!depMap.has(key)) {
151 | dep = new Set();
152 | depMap.set(key, dep);
153 | }
154 | dep = depMap.get(key);
155 | trackEffect(dep);
156 | };
157 | const trackEffect = (dep) => {
158 | if (dep.has(activeEffect))
159 | return;
160 | dep.add(activeEffect);
161 | activeEffect.deps.push(dep);
162 | };
163 | /**
164 | * 触发依赖
165 | * @param target
166 | * @param key
167 | */
168 | const trigger = (target, key) => {
169 | let depsMap = targetKeyMap.get(target);
170 | let dep = depsMap.get(key);
171 | triggerEffect(dep);
172 | };
173 | const triggerEffect = (dep) => {
174 | for (const effect of dep) {
175 | if (effect.scheduler) {
176 | effect.scheduler();
177 | }
178 | else {
179 | effect.run();
180 | }
181 | }
182 | };
183 | const effect = (fn, options = {}) => {
184 | const reactiveEffect = new ReactiveEffect(fn, options.scheduler);
185 | reactiveEffect.onStop = options.onStop;
186 | reactiveEffect.run();
187 | const runner = reactiveEffect.run.bind(reactiveEffect);
188 | runner.effect = reactiveEffect;
189 | return runner;
190 | };
191 | /**
192 | * 可以被收集依赖
193 | * @returns
194 | */
195 | const isTracking = () => {
196 | return shouldTrack && activeEffect != undefined;
197 | };
198 |
199 | const createGetter = (isReadOnly = false, isShallowReadonly = false, isShallowReactive = false) => {
200 | return (target, key) => {
201 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
202 | return !isReadOnly;
203 | }
204 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
205 | return isReadOnly;
206 | }
207 | const res = Reflect.get(target, key);
208 | // shallowReadonly
209 | // shallow 只需要获取表面的那层数据
210 | if (isShallowReadonly) {
211 | return res;
212 | }
213 | if (isObject(res) && !isShallowReactive) {
214 | return isReadOnly ? readonly(res) : reactive(res);
215 | }
216 | // 收集依赖
217 | if (!isReadOnly) {
218 | track(target, key);
219 | }
220 | return res;
221 | };
222 | };
223 | const createSetter = () => {
224 | return (target, key, value) => {
225 | const res = Reflect.set(target, key, value);
226 | // 触发依赖
227 | trigger(target, key);
228 | return res;
229 | };
230 | };
231 | const get = createGetter();
232 | const set = createSetter();
233 | const readonlyGet = createGetter(true);
234 | const shallowReadonlyGet = createGetter(true, true);
235 | const shallowReactiveGet = createGetter(false, false, true);
236 | const baseHandler = {
237 | get,
238 | set
239 | };
240 | const readonlyHandler = {
241 | get: readonlyGet,
242 | set(target, key, value) {
243 | throw new Error(`${target} 因为 readonly模式,所以无法修改`);
244 | }
245 | };
246 | const shallowReadonlyHandler = extend({}, readonlyHandler, {
247 | get: shallowReadonlyGet
248 | });
249 | extend({}, baseHandler, {
250 | get: shallowReactiveGet
251 | });
252 |
253 | function reactive(params) {
254 | return createReactive(params, baseHandler);
255 | }
256 | const readonly = (params) => {
257 | return createReactive(params, readonlyHandler);
258 | };
259 | const shallowReadonly = (params) => {
260 | return createReactive(params, shallowReadonlyHandler);
261 | };
262 | const createReactive = (target, baseHandler) => {
263 | if (!isObject(target)) {
264 | console.warn(`Reactive target ${target} 只能是对象。`);
265 | return target;
266 | }
267 | return new Proxy(target, baseHandler);
268 | };
269 |
270 | class RefImpl {
271 | constructor(_value) {
272 | this.__v_isRef = true;
273 | this._value = convert(_value);
274 | this._rawValue = _value;
275 | this.dep = new Set();
276 | }
277 | get value() {
278 | trackRefEffect(this.dep);
279 | return this._value;
280 | }
281 | set value(newValue) {
282 | if (hasChanged(this._rawValue, newValue)) {
283 | this._rawValue = newValue;
284 | this._value = convert(newValue);
285 | triggerEffect(this.dep);
286 | }
287 | }
288 | }
289 | function trackRefEffect(dep) {
290 | if (isTracking()) {
291 | trackEffect(dep);
292 | }
293 | }
294 | function convert(newValue) {
295 | return isObject(newValue) ? reactive(newValue) : newValue;
296 | }
297 | const ref = (val) => {
298 | return new RefImpl(val);
299 | };
300 | const isRef = (ref) => {
301 | // 在RefImpl 里面给一个判断值
302 | return !!ref.__v_isRef;
303 | };
304 | // 判断是不是ref
305 | // -- 如果是ref,则直接ref.value
306 | // -- 如果不是ref,则直接返回ref
307 | const unRef = (ref) => {
308 | return isRef(ref) ? ref.value : ref;
309 | };
310 | const proxyRef = (proxyTarget) => {
311 | return new Proxy(proxyTarget, {
312 | get(target, key) {
313 | // 利用unRef判断获取值
314 | return unRef(Reflect.get(target, key));
315 | },
316 | set(target, key, value) {
317 | // 判断是不是ref 如果是ref,且返回的值不是ref,则使用.value来赋值
318 | // 否则直接赋值
319 | if (isRef(target[key]) && !isRef(value)) {
320 | return (target[key].value = value);
321 | }
322 | else {
323 | return Reflect.set(target, key, value);
324 | }
325 | }
326 | });
327 | };
328 |
329 | const emit = (instance, event, ...args) => {
330 | const { props } = instance;
331 | // 通过自定义事件名称 获取 事件
332 | const handler = props[toHandlerKey(camelize(event))];
333 | handler && handler(...args);
334 | };
335 |
336 | const initProps = (instance, props) => {
337 | instance.props = props;
338 | };
339 |
340 | const publicPropertiesMap = {
341 | $el: (instance) => instance.vnode.el,
342 | $slots: (instance) => instance.slots,
343 | $props: (instance) => instance.props
344 | };
345 | const publicInstanceProxyHandlers = {
346 | get({ _: instance }, key) {
347 | const { setupState, props } = instance;
348 | if (isOwn(setupState, key)) {
349 | return setupState[key];
350 | }
351 | else if (isOwn(props, key)) {
352 | return props[key];
353 | }
354 | const publicProperty = publicPropertiesMap[key];
355 | if (publicProperty) {
356 | return publicProperty(instance);
357 | }
358 | }
359 | };
360 |
361 | const initSlots = (instance, children) => {
362 | // 1. 单个
363 | // 2. array
364 | // 3. key->value object
365 | // instance.slots = Array.isArray(children) ? children : [children]
366 | const { vnode } = instance;
367 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
368 | normalizeObject(children, instance);
369 | }
370 | };
371 | function normalizeObject(children, instance) {
372 | const solts = {};
373 | for (const key in children) {
374 | const val = children[key];
375 | solts[key] = (props) => normalizeSlotValue(val(props));
376 | }
377 | instance.slots = solts;
378 | }
379 | function normalizeSlotValue(val) {
380 | return Array.isArray(val) ? val : [val];
381 | }
382 |
383 | let instance = null;
384 | const createComponentInstance = (vnode, parent) => {
385 | const componentInstance = {
386 | vnode,
387 | type: vnode.type,
388 | setupState: {},
389 | props: {},
390 | slots: {},
391 | isMounted: false,
392 | subTree: {},
393 | component: null,
394 | update: null,
395 | next: null,
396 | provides: parent ? parent.provides : {},
397 | parent,
398 | emit: () => { }
399 | };
400 | // 初始化emit
401 | // 使用bind 返回一个函数
402 | componentInstance.emit = emit.bind(null, componentInstance);
403 | return componentInstance;
404 | };
405 | const setupComponent = (instance) => {
406 | // TODO 后续处理 -> 只处理了普通的
407 | initProps(instance, instance.vnode.props);
408 | initSlots(instance, instance.vnode.children);
409 | // 处理初始化setup
410 | setupStatefulComponent(instance);
411 | };
412 | function setupStatefulComponent(instance) {
413 | const component = instance.type;
414 | // 组件代理对象 this.xx
415 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandlers);
416 | const { setup } = component;
417 | if (setup) {
418 | // 存储instance,用于getCurrentInstance
419 | setCurrentInstance(instance);
420 | const setupResult = setup(shallowReadonly(instance.props), {
421 | emit: instance.emit
422 | });
423 | setCurrentInstance(null);
424 | handleSetupResult(instance, setupResult);
425 | }
426 | }
427 | function handleSetupResult(instance, setupResult) {
428 | // setupResult 返回的值类型可能是 function | object
429 | // TODO Function先不处理, 先处理Object
430 | if (typeof setupResult === 'object') {
431 | // 通过proxyRef来解决Ref获取值
432 | instance.setupState = proxyRef(setupResult);
433 | }
434 | // 保证render有值
435 | finishComponentSetup(instance);
436 | }
437 | function finishComponentSetup(instance) {
438 | const component = instance.type;
439 | // 暂时默认都有render
440 | instance.render = component.render;
441 | }
442 | const getCurrentInstance = () => {
443 | return instance;
444 | };
445 | function setCurrentInstance(_instance) {
446 | instance = _instance;
447 | }
448 |
449 | // 场景1: 父组件 存, 子组件 取 ==> 存到provides里面
450 | // 场景2: 父组件 存, 子孙组件 取 ==> provides指定父类的provides
451 | // 场景3: 当在中间层组件中使用了 provide
452 | const provide = (key, value) => {
453 | // 存值
454 | const currentInstance = getCurrentInstance();
455 | if (currentInstance) {
456 | let { provides } = currentInstance;
457 | // init
458 | if (provides === currentInstance.parent.provides) {
459 | // 原型指向父类的provides, 创建一个新的
460 | provides = currentInstance.provides = Object.create(currentInstance.provides);
461 | }
462 | provides[key] = value;
463 | }
464 | };
465 | const inject = (key) => {
466 | // 取值
467 | const currentInstance = getCurrentInstance();
468 | if (currentInstance) {
469 | // 父类里面拿
470 | const { parent } = currentInstance;
471 | const { provides } = parent;
472 | return provides[key];
473 | }
474 | };
475 |
476 | const shouldUpdateComponent = (n1, n2) => {
477 | const { props: prevProps } = n1;
478 | const { props: nextProps } = n2;
479 | for (const key in prevProps) {
480 | if (prevProps[key] !== nextProps[key]) {
481 | return true;
482 | }
483 | }
484 | return false;
485 | };
486 |
487 | const createAppAPI = (render) => {
488 | return function (rootComponent) {
489 | return {
490 | mount(rootContainer) {
491 | // 将component转换成vnode
492 | // 后续所有操作都会基于vnode 来进行操作
493 | const vnode = createVNode(rootComponent);
494 | render(vnode, rootContainer);
495 | }
496 | };
497 | };
498 | };
499 |
500 | const queue = [];
501 | const promise = Promise.resolve();
502 | // 解决不要多次创建Promise
503 | let isExceute = false;
504 | const nextTick = (fn) => {
505 | return fn ? promise.then(fn) : promise;
506 | };
507 | const queueJob = (job) => {
508 | // 判断一下是否存在队列,如果存在就不加了
509 | if (!queue.includes(job)) {
510 | queue.push(job);
511 | }
512 | // 执行微任务
513 | excuteQueue();
514 | };
515 | function excuteQueue() {
516 | // MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
517 | if (isExceute)
518 | return;
519 | isExceute = true;
520 | nextTick(() => {
521 | isExceute = false;
522 | let job;
523 | while ((job = queue.shift())) {
524 | job & job();
525 | }
526 | });
527 | }
528 |
529 | const createRenderer = (options) => {
530 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText } = options;
531 | const render = (n2, container) => {
532 | // 调用patch
533 | patch(null, n2, container, null, null);
534 | };
535 | const patch = (n1, n2, container, parent, anchor) => {
536 | // 判断 是不是 element类型
537 | // 如果是Component,type => Object
538 | // 如果是element类型,type => div等标签
539 | const { shapeFlag, type } = n2;
540 | switch (type) {
541 | case Fragment: {
542 | processFragment(n1, n2, container, parent, anchor);
543 | break;
544 | }
545 | case Text: {
546 | processText(n1, n2, container);
547 | break;
548 | }
549 | default: {
550 | if (shapeFlag & 1 /* ELEMENT */) {
551 | // 处理Element
552 | processElement(n1, n2, container, parent, anchor);
553 | }
554 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
555 | // 处理组件
556 | processComponent(n1, n2, container, parent, anchor);
557 | }
558 | }
559 | }
560 | };
561 | const processComponent = (n1, n2, container, parent, anchor) => {
562 | // 挂载组件
563 | // 如果n1有值,证明是需要进行更新
564 | if (!n1) {
565 | mountComponent(n2, container, parent, anchor);
566 | }
567 | else {
568 | updateComponent(n1, n2);
569 | }
570 | };
571 | const updateComponent = (n1, n2) => {
572 | // 判断n1, n2的props是否相等
573 | // 相等才会进行组件更新
574 | const component = (n2.component = n1.component);
575 | if (shouldUpdateComponent(n1, n2)) {
576 | console.log('[Function:updateComponent]:组件更新n1', n1);
577 | console.log('[Function:updateComponent]:组件更新n2', n2);
578 | component.next = n2;
579 | component.update();
580 | }
581 | else {
582 | n2.el = n1.el;
583 | n2.vnode = n2;
584 | }
585 | };
586 | const processFragment = (n1, n2, container, parent, anchor) => {
587 | mountChildren(n2.children, container, parent, anchor);
588 | };
589 | const processText = (n1, n2, container) => {
590 | const { children } = n2;
591 | const textNode = (n2.el = document.createTextNode(children));
592 | container.append(textNode);
593 | };
594 | function processElement(n1, n2, container, parent, anchor) {
595 | if (!n1) {
596 | mountElement(n2, container, parent, anchor);
597 | }
598 | else {
599 | patchElement(n1, n2, container, parent, anchor);
600 | }
601 | }
602 | function mountElement(vnode, container, parent, anchor) {
603 | const { type, props, children, shapeFlag } = vnode;
604 | // 创建对应的el
605 | // vnode -> element -> div
606 | const el = (vnode.el = hostCreateElement(type));
607 | // 处理props => 普通属性 和 注册事件
608 | for (const key in props) {
609 | hostPatchProp(el, key, null, props[key]);
610 | }
611 | // 处理children --> string, Array
612 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
613 | el.innerText = children;
614 | }
615 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
616 | mountChildren(children, el, parent, anchor);
617 | }
618 | // 插入
619 | hostInsert(el, container, anchor);
620 | }
621 | function patchElement(n1, n2, container, parent, anchor) {
622 | console.log("n1", n1);
623 | console.log("n2", n2);
624 | const oldProps = n1.props;
625 | const newProps = n2.props;
626 | // 需要把 el 挂载到新的 vnode
627 | // n1: 旧的 => n2: 新的
628 | const el = (n2.el = n1.el);
629 | // 对比props
630 | patchProps(el, oldProps, newProps);
631 | // 对比children
632 | patchChildren(n1, n2, el, parent, anchor);
633 | }
634 | function patchProps(el, oldProps, newProps) {
635 | console.log('oldProps', oldProps);
636 | console.log('newProps', newProps);
637 | // 当不相同的时候才会去判断
638 | if (oldProps !== newProps) {
639 | // 遍历新值
640 | for (const key in newProps) {
641 | const prevProp = oldProps[key];
642 | const nextProp = newProps[key];
643 | if (prevProp !== newProps) {
644 | // 对比属性值,如果不相同则需要进行更新
645 | hostPatchProp(el, key, prevProp, nextProp);
646 | }
647 | }
648 | // 遍历旧值
649 | for (const key in oldProps) {
650 | const prevProp = oldProps[key];
651 | if (!(key in newProps)) {
652 | // 判断新props没有的,没有的就去掉null
653 | hostPatchProp(el, key, prevProp, null);
654 | }
655 | }
656 | }
657 | }
658 | function patchChildren(n1, n2, container, parent, anchor) {
659 | // 获取shapeFlags 来判断类型
660 | const prevShapeFlag = n1.shapeFlag;
661 | const shapeFlag = n2.shapeFlag;
662 | // 新节点是 Text 时候
663 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
664 | // 老节点是 Children
665 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
666 | // 清空老的 Array子节点
667 | unmountChildren(n1.children);
668 | }
669 | // 新老节点都不相同, 新节点需要是Text
670 | if (n1.children !== n2.children) {
671 | // 插入新的 Text节点
672 | hostSetElementText(container, n2.children);
673 | }
674 | }
675 | else {
676 | // 新节点是 Children 时候
677 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
678 | // 老节点是 Text
679 | hostSetElementText(container, "");
680 | mountChildren(n2.children, container, parent, anchor);
681 | }
682 | else {
683 | // 老节点是 Children
684 | patchKeyedChildren(n1.children, n2.children, container, parent, anchor);
685 | }
686 | }
687 | }
688 | function patchKeyedChildren(c1, c2, container, parent, anchor) {
689 | let i = 0;
690 | let e1 = c1.length - 1;
691 | let e2 = c2.length - 1;
692 | function isSomeVNodeType(n1, n2) {
693 | return n1.type === n2.type && n1.key === n2.key;
694 | }
695 | // 1. 左侧
696 | while (i <= e1 && i <= e2) {
697 | const n1 = c1[i];
698 | const n2 = c2[i];
699 | if (isSomeVNodeType(n1, n2)) {
700 | patch(n1, n2, container, parent, anchor);
701 | }
702 | else {
703 | break;
704 | }
705 | i++;
706 | }
707 | console.log('左侧之后i', i);
708 | // 2. 右侧
709 | while (e1 >= i && e2 >= i) {
710 | const n1 = c1[e1];
711 | const n2 = c2[e2];
712 | if (isSomeVNodeType(n1, n2)) {
713 | patch(n1, n2, container, parent, anchor);
714 | }
715 | else {
716 | break;
717 | }
718 | e1--;
719 | e2--;
720 | }
721 | console.log('右侧之后e1', e1);
722 | console.log('右侧之后e2', e2);
723 | // 3.新的比老的长, 新的进行创建
724 | // i > 旧的 证明旧的已经diff完了
725 | // 1 <= 新的 属于diff后需要新增的范围
726 | // 左侧 -> 新增
727 | // a b
728 | // a b c d
729 | // i = 2 > e1 = 2
730 | // i = 2 <= e2 = 3
731 | // 左侧 -> 删除
732 | // a b c
733 | // a b
734 | // i = 2 > e1 = 3
735 | // i = 2 <= e2 = 2
736 | // 右侧
737 | // a b
738 | // c d a b
739 | // i = 0 > e1 = -1
740 | // i = 0 <= e2 = 1
741 | if (i > e1) {
742 | if (i <= e2) {
743 | const nextProp = e2 + 1;
744 | const anchor = nextProp < c2.length ? c2[nextProp].el : null;
745 | while (i <= e2) {
746 | patch(null, c2[i], container, parent, anchor);
747 | i++;
748 | }
749 | }
750 | }
751 | else if (i > e2) {
752 | // 4. i值大于新节点长度e2, 进行remove
753 | while (i <= e1) {
754 | hostRemove(c1[i].el);
755 | i++;
756 | }
757 | }
758 | else {
759 | // 5. 中间进行对比
760 | let s1 = i;
761 | let s2 = i;
762 | let moved = false;
763 | let newIndexForMax = 0;
764 | // 新节点需要对比的节点数
765 | let toBePatched = e2 - s2 + 1;
766 | let patched = 0;
767 | // 新节点获取key值
768 | const keyToNewIndexMap = new Map();
769 | for (let j = s2; j <= e2; j++) {
770 | const nextChild = c2[j];
771 | keyToNewIndexMap.set(nextChild.key, j);
772 | }
773 | // 进行移动
774 | // 定一个长度为 新节点需要对比的节点数量
775 | const newIndexToOldIndexMap = new Array(toBePatched);
776 | for (let i = 0; i < toBePatched; i++) {
777 | newIndexToOldIndexMap[i] = 0;
778 | }
779 | // 旧节点进行循环
780 | for (let index = s1; index <= e1; index++) {
781 | const pervChild = c1[index];
782 | const pervKey = pervChild.key;
783 | let nextIndex;
784 | // 旧节点长度比旧节点的要长 且已经不需要对比了 直接删除
785 | if (patched >= toBePatched) {
786 | hostRemove(pervChild.el);
787 | continue;
788 | }
789 | if (pervKey !== null) {
790 | // 获取新节点的对应的keyindex
791 | nextIndex = keyToNewIndexMap.get(pervKey);
792 | }
793 | else {
794 | for (let j = s2; j <= e2; j++) {
795 | if (isSomeVNodeType(pervChild, c2[j])) {
796 | nextIndex = j;
797 | break;
798 | }
799 | }
800 | }
801 | if (nextIndex !== undefined) {
802 | // 判断是否需要移动
803 | if (nextIndex >= newIndexForMax) {
804 | newIndexForMax = nextIndex;
805 | }
806 | else {
807 | moved = true;
808 | }
809 | // +1 的原因是 在使用最长递增子序列算法时候,0是用于判断
810 | newIndexToOldIndexMap[nextIndex - s2] = index + 1;
811 | patch(pervChild, c2[nextIndex], container, parent, null);
812 | patched++;
813 | }
814 | else {
815 | // 旧的节点 不存在新的里面,需要删除
816 | hostRemove(pervChild.el);
817 | }
818 | }
819 | // 得到了印射表newIndexToOldIndexMap后,进行移动处理
820 | const newIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
821 | let j = newIndexSequence.length - 1; // 索引 -1
822 | // 为了稳定的锚点,需要倒序
823 | for (let i = toBePatched - 1; i >= 0; i--) {
824 | const nextIndex = i + s2;
825 | const nextChild = c2[nextIndex];
826 | const nextAnchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null;
827 | if (newIndexToOldIndexMap[i] === 0) {
828 | // 当印射表里面等于 0 相当于 没有新的节点 没有需要移动的而且是不存在的,这时候需要添加一个节点
829 | patch(null, nextChild, container, parent, nextAnchor);
830 | }
831 | else if (moved) {
832 | if (i !== newIndexSequence[j]) {
833 | console.log("需要移动位置");
834 | hostInsert(nextChild, container, nextAnchor);
835 | }
836 | else {
837 | j--;
838 | }
839 | }
840 | }
841 | }
842 | }
843 | function unmountChildren(children) {
844 | for (const key in children) {
845 | hostRemove(children[key].el);
846 | }
847 | }
848 | function mountChildren(vnodes = [], container, parent, anchor) {
849 | vnodes.forEach(element => {
850 | patch(null, element, container, parent, anchor);
851 | });
852 | }
853 | const mountComponent = (n2, container, parent, anchor) => {
854 | console.log('parent', parent);
855 | // 创建组件实例, 收集数据
856 | const instance = (n2.component = createComponentInstance(n2, parent));
857 | // 初始化 props, slots, setup
858 | setupComponent(instance);
859 | // 进行拆箱
860 | setupRenderEffect(instance, n2, container, anchor);
861 | };
862 | const setupRenderEffect = (instance, n2, container, anchor) => {
863 | // 依赖收集
864 | instance.update = effect(() => {
865 | const { isMounted } = instance;
866 | if (!isMounted) {
867 | // 是否初始化
868 | // 指定代理对象 this.xxx
869 | const { proxy } = instance;
870 | const subTree = instance.render.call(proxy);
871 | // subTree 虚拟节点树 n2 tree
872 | // n2 -> patch
873 | // n2 -> element -> mountElement
874 | patch(null, subTree, container, instance, anchor);
875 | // vnode节点存起来
876 | instance.subTree = subTree;
877 | // 完成了所有的patch后
878 | n2.el = subTree.el;
879 | // 初始化结束后,转换状态
880 | instance.isMounted = true;
881 | }
882 | else {
883 | console.log('update');
884 | const { proxy, next, vnode } = instance;
885 | // 更新props
886 | // 需要获取vnode, next-> 下次更新虚拟节点,vnode之前的虚拟节点
887 | if (next) {
888 | next.el = vnode.el;
889 | updateComponentPreRender(instance, next);
890 | }
891 | // 当前(新)节点
892 | const subTree = instance.render.call(proxy);
893 | // 旧节点
894 | const prevSubTree = instance.subTree;
895 | // vnode节点存起来
896 | instance.subTree = subTree;
897 | // 新节点 和 旧节点 进行对比
898 | patch(prevSubTree, subTree, container, instance, anchor);
899 | }
900 | }, {
901 | scheduler: () => {
902 | console.log('update - scheduler');
903 | // 将effect任务 加入到微任务里,再异步执行微任务
904 | queueJob(instance.update);
905 | }
906 | });
907 | };
908 | const updateComponentPreRender = (instance, nextVNode) => {
909 | instance.vnode = nextVNode;
910 | instance.props = nextVNode.props;
911 | instance.next = null;
912 | };
913 | return {
914 | createApp: createAppAPI(render)
915 | };
916 | };
917 | /**
918 | * 最长子序列算法LIS
919 | * @param arr
920 | * @returns
921 | */
922 | function getSequence(arr) {
923 | const p = arr.slice();
924 | const result = [0];
925 | let i, j, u, v, c;
926 | const len = arr.length;
927 | for (i = 0; i < len; i++) {
928 | const arrI = arr[i];
929 | if (arrI !== 0) {
930 | j = result[result.length - 1];
931 | if (arr[j] < arrI) {
932 | p[i] = j;
933 | result.push(i);
934 | continue;
935 | }
936 | u = 0;
937 | v = result.length - 1;
938 | while (u < v) {
939 | c = (u + v) >> 1;
940 | if (arr[result[c]] < arrI) {
941 | u = c + 1;
942 | }
943 | else {
944 | v = c;
945 | }
946 | }
947 | if (arrI < arr[result[u]]) {
948 | if (u > 0) {
949 | p[i] = result[u - 1];
950 | }
951 | result[u] = i;
952 | }
953 | }
954 | }
955 | u = result.length;
956 | v = result[u - 1];
957 | while (u-- > 0) {
958 | result[u] = v;
959 | v = p[v];
960 | }
961 | return result;
962 | }
963 |
964 | const createElement = (type) => {
965 | return document.createElement(type);
966 | };
967 | const patchProp = (el, key, preValue, nextValue) => {
968 | const isOn = (key) => /^on[A-Z]/.test(key);
969 | if (isOn(key)) {
970 | const event = key.slice(2).toLowerCase();
971 | el.addEventListener(event, nextValue);
972 | }
973 | else {
974 | if (nextValue === null || nextValue === undefined) {
975 | // 如果是 null 或者 undefined,直接remove
976 | el.removeAttribute(key);
977 | }
978 | else {
979 | // 正常设置
980 | el.setAttribute(key, nextValue);
981 | }
982 | }
983 | };
984 | const setElementText = (el, text) => {
985 | el.textContent = text;
986 | };
987 | const remove = (child) => {
988 | const parent = child.parentNode;
989 | if (parent) {
990 | parent.removeChild(child);
991 | }
992 | };
993 | const insert = (child, parent, anchor) => {
994 | parent.insertBefore(child, anchor);
995 | };
996 | const renderer = createRenderer({
997 | createElement,
998 | patchProp,
999 | insert,
1000 | remove,
1001 | setElementText
1002 | });
1003 | function createApp(...args) {
1004 | return renderer.createApp(...args);
1005 | }
1006 |
1007 | exports.createApp = createApp;
1008 | exports.createElement = createElement;
1009 | exports.createRenderer = createRenderer;
1010 | exports.createTextVNode = createTextVNode;
1011 | exports.getCurrentInstance = getCurrentInstance;
1012 | exports.h = h;
1013 | exports.inject = inject;
1014 | exports.insert = insert;
1015 | exports.isRef = isRef;
1016 | exports.nextTick = nextTick;
1017 | exports.patchProp = patchProp;
1018 | exports.provide = provide;
1019 | exports.proxyRef = proxyRef;
1020 | exports.ref = ref;
1021 | exports.remove = remove;
1022 | exports.renderSlot = renderSlot;
1023 | exports.setElementText = setElementText;
1024 | exports.unRef = unRef;
1025 |
--------------------------------------------------------------------------------
/lib/mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 判断是否是对象
3 | * @param target
4 | * @returns
5 | */
6 | const isObject = (target) => {
7 | return target !== null && typeof target === 'object';
8 | };
9 | const extend = Object.assign;
10 | const hasChanged = (oldValue, newValue) => {
11 | return !Object.is(oldValue, newValue);
12 | };
13 | const isString = (val) => typeof val === 'string';
14 | const isOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
15 | // add -> Add
16 | const capitalize = (str) => {
17 | return str.charAt(0).toUpperCase() + str.slice(1);
18 | };
19 | // add -> Add -> onAdd
20 | const toHandlerKey = (str) => {
21 | return str ? "on" + capitalize(str) : "";
22 | };
23 | // add-count -> addCount -> AddCount -> onAddCount
24 | // 自定义事件是驼峰写法
25 | const camelize = (str) => {
26 | return str.replace(/-(\w)/g, (match, c) => {
27 | return c ? c.toUpperCase() : "";
28 | });
29 | };
30 |
31 | const Fragment = Symbol('Fragment');
32 | const Text = Symbol('Text');
33 | const createVNode = (type, props, children) => {
34 | const vnode = {
35 | type,
36 | props,
37 | shapeFlag: getShapeFlags(type),
38 | key: props && props.key,
39 | children,
40 | el: null
41 | };
42 | // 使用二进制来进行判断
43 | if (isString(children)) {
44 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
45 | }
46 | else {
47 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
48 | }
49 | // 初始化slot
50 | // 组件 + children => object
51 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
52 | if (isObject(children)) {
53 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
54 | }
55 | }
56 | return vnode;
57 | };
58 | const createTextVNode = (str) => {
59 | return createVNode(Text, {}, str);
60 | };
61 | function getShapeFlags(type) {
62 | return isString(type) ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */;
63 | }
64 |
65 | const h = (type, props, children) => {
66 | return createVNode(type, props, children);
67 | };
68 |
69 | const renderSlot = ($slots, name, props) => {
70 | // return vnode
71 | // return createVNode("div", {}, $slots)
72 | const slot = $slots[name];
73 | if (slot) {
74 | if (typeof slot === 'function') {
75 | return createVNode(Fragment, {}, slot(props));
76 | }
77 | }
78 | };
79 |
80 | // target -> key -> dep
81 | const targetKeyMap = new Map();
82 | let activeEffect;
83 | // 是否应该收集
84 | let shouldTrack = false;
85 | class ReactiveEffect {
86 | constructor(fn, scheduler) {
87 | this.scheduler = scheduler;
88 | this.deps = [];
89 | this.state = true;
90 | this.fn = fn;
91 | this.scheduler = scheduler;
92 | }
93 | run() {
94 | // 判断是不是被stop
95 | if (!this.state) {
96 | return this.fn();
97 | }
98 | // 如果不是被stop,可被收集依赖
99 | // shouldTrack 打开
100 | shouldTrack = true;
101 | activeEffect = this;
102 | const runner = this.fn();
103 | // shouldTrack 是全局,所以需要被reset
104 | shouldTrack = false;
105 | return runner;
106 | }
107 | stop() {
108 | if (this.state) {
109 | if (this.onStop) {
110 | this.onStop();
111 | }
112 | cleanDeps(this);
113 | this.state = false;
114 | }
115 | }
116 | }
117 | /**
118 | * 清除所有依赖dep
119 | * @param effect
120 | */
121 | const cleanDeps = (effect) => {
122 | effect.deps.forEach((dep) => {
123 | dep.delete(effect);
124 | });
125 | };
126 | /**
127 | * 收集依赖
128 | * target -> key -> dep
129 | * @param target
130 | * @param key
131 | * @returns
132 | */
133 | const track = (target, key) => {
134 | // 判断是否可以被收集依赖
135 | // 1. shouldTrack 是否放通
136 | // 2. activeEffect 在run时候是否有被赋值
137 | if (!isTracking())
138 | return;
139 | let depMap;
140 | let dep;
141 | if (!targetKeyMap.has(target)) {
142 | depMap = new Map();
143 | targetKeyMap.set(target, depMap);
144 | }
145 | depMap = targetKeyMap.get(target);
146 | if (!depMap.has(key)) {
147 | dep = new Set();
148 | depMap.set(key, dep);
149 | }
150 | dep = depMap.get(key);
151 | trackEffect(dep);
152 | };
153 | const trackEffect = (dep) => {
154 | if (dep.has(activeEffect))
155 | return;
156 | dep.add(activeEffect);
157 | activeEffect.deps.push(dep);
158 | };
159 | /**
160 | * 触发依赖
161 | * @param target
162 | * @param key
163 | */
164 | const trigger = (target, key) => {
165 | let depsMap = targetKeyMap.get(target);
166 | let dep = depsMap.get(key);
167 | triggerEffect(dep);
168 | };
169 | const triggerEffect = (dep) => {
170 | for (const effect of dep) {
171 | if (effect.scheduler) {
172 | effect.scheduler();
173 | }
174 | else {
175 | effect.run();
176 | }
177 | }
178 | };
179 | const effect = (fn, options = {}) => {
180 | const reactiveEffect = new ReactiveEffect(fn, options.scheduler);
181 | reactiveEffect.onStop = options.onStop;
182 | reactiveEffect.run();
183 | const runner = reactiveEffect.run.bind(reactiveEffect);
184 | runner.effect = reactiveEffect;
185 | return runner;
186 | };
187 | /**
188 | * 可以被收集依赖
189 | * @returns
190 | */
191 | const isTracking = () => {
192 | return shouldTrack && activeEffect != undefined;
193 | };
194 |
195 | const createGetter = (isReadOnly = false, isShallowReadonly = false, isShallowReactive = false) => {
196 | return (target, key) => {
197 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
198 | return !isReadOnly;
199 | }
200 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
201 | return isReadOnly;
202 | }
203 | const res = Reflect.get(target, key);
204 | // shallowReadonly
205 | // shallow 只需要获取表面的那层数据
206 | if (isShallowReadonly) {
207 | return res;
208 | }
209 | if (isObject(res) && !isShallowReactive) {
210 | return isReadOnly ? readonly(res) : reactive(res);
211 | }
212 | // 收集依赖
213 | if (!isReadOnly) {
214 | track(target, key);
215 | }
216 | return res;
217 | };
218 | };
219 | const createSetter = () => {
220 | return (target, key, value) => {
221 | const res = Reflect.set(target, key, value);
222 | // 触发依赖
223 | trigger(target, key);
224 | return res;
225 | };
226 | };
227 | const get = createGetter();
228 | const set = createSetter();
229 | const readonlyGet = createGetter(true);
230 | const shallowReadonlyGet = createGetter(true, true);
231 | const shallowReactiveGet = createGetter(false, false, true);
232 | const baseHandler = {
233 | get,
234 | set
235 | };
236 | const readonlyHandler = {
237 | get: readonlyGet,
238 | set(target, key, value) {
239 | throw new Error(`${target} 因为 readonly模式,所以无法修改`);
240 | }
241 | };
242 | const shallowReadonlyHandler = extend({}, readonlyHandler, {
243 | get: shallowReadonlyGet
244 | });
245 | extend({}, baseHandler, {
246 | get: shallowReactiveGet
247 | });
248 |
249 | function reactive(params) {
250 | return createReactive(params, baseHandler);
251 | }
252 | const readonly = (params) => {
253 | return createReactive(params, readonlyHandler);
254 | };
255 | const shallowReadonly = (params) => {
256 | return createReactive(params, shallowReadonlyHandler);
257 | };
258 | const createReactive = (target, baseHandler) => {
259 | if (!isObject(target)) {
260 | console.warn(`Reactive target ${target} 只能是对象。`);
261 | return target;
262 | }
263 | return new Proxy(target, baseHandler);
264 | };
265 |
266 | class RefImpl {
267 | constructor(_value) {
268 | this.__v_isRef = true;
269 | this._value = convert(_value);
270 | this._rawValue = _value;
271 | this.dep = new Set();
272 | }
273 | get value() {
274 | trackRefEffect(this.dep);
275 | return this._value;
276 | }
277 | set value(newValue) {
278 | if (hasChanged(this._rawValue, newValue)) {
279 | this._rawValue = newValue;
280 | this._value = convert(newValue);
281 | triggerEffect(this.dep);
282 | }
283 | }
284 | }
285 | function trackRefEffect(dep) {
286 | if (isTracking()) {
287 | trackEffect(dep);
288 | }
289 | }
290 | function convert(newValue) {
291 | return isObject(newValue) ? reactive(newValue) : newValue;
292 | }
293 | const ref = (val) => {
294 | return new RefImpl(val);
295 | };
296 | const isRef = (ref) => {
297 | // 在RefImpl 里面给一个判断值
298 | return !!ref.__v_isRef;
299 | };
300 | // 判断是不是ref
301 | // -- 如果是ref,则直接ref.value
302 | // -- 如果不是ref,则直接返回ref
303 | const unRef = (ref) => {
304 | return isRef(ref) ? ref.value : ref;
305 | };
306 | const proxyRef = (proxyTarget) => {
307 | return new Proxy(proxyTarget, {
308 | get(target, key) {
309 | // 利用unRef判断获取值
310 | return unRef(Reflect.get(target, key));
311 | },
312 | set(target, key, value) {
313 | // 判断是不是ref 如果是ref,且返回的值不是ref,则使用.value来赋值
314 | // 否则直接赋值
315 | if (isRef(target[key]) && !isRef(value)) {
316 | return (target[key].value = value);
317 | }
318 | else {
319 | return Reflect.set(target, key, value);
320 | }
321 | }
322 | });
323 | };
324 |
325 | const emit = (instance, event, ...args) => {
326 | const { props } = instance;
327 | // 通过自定义事件名称 获取 事件
328 | const handler = props[toHandlerKey(camelize(event))];
329 | handler && handler(...args);
330 | };
331 |
332 | const initProps = (instance, props) => {
333 | instance.props = props;
334 | };
335 |
336 | const publicPropertiesMap = {
337 | $el: (instance) => instance.vnode.el,
338 | $slots: (instance) => instance.slots,
339 | $props: (instance) => instance.props
340 | };
341 | const publicInstanceProxyHandlers = {
342 | get({ _: instance }, key) {
343 | const { setupState, props } = instance;
344 | if (isOwn(setupState, key)) {
345 | return setupState[key];
346 | }
347 | else if (isOwn(props, key)) {
348 | return props[key];
349 | }
350 | const publicProperty = publicPropertiesMap[key];
351 | if (publicProperty) {
352 | return publicProperty(instance);
353 | }
354 | }
355 | };
356 |
357 | const initSlots = (instance, children) => {
358 | // 1. 单个
359 | // 2. array
360 | // 3. key->value object
361 | // instance.slots = Array.isArray(children) ? children : [children]
362 | const { vnode } = instance;
363 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
364 | normalizeObject(children, instance);
365 | }
366 | };
367 | function normalizeObject(children, instance) {
368 | const solts = {};
369 | for (const key in children) {
370 | const val = children[key];
371 | solts[key] = (props) => normalizeSlotValue(val(props));
372 | }
373 | instance.slots = solts;
374 | }
375 | function normalizeSlotValue(val) {
376 | return Array.isArray(val) ? val : [val];
377 | }
378 |
379 | let instance = null;
380 | const createComponentInstance = (vnode, parent) => {
381 | const componentInstance = {
382 | vnode,
383 | type: vnode.type,
384 | setupState: {},
385 | props: {},
386 | slots: {},
387 | isMounted: false,
388 | subTree: {},
389 | component: null,
390 | update: null,
391 | next: null,
392 | provides: parent ? parent.provides : {},
393 | parent,
394 | emit: () => { }
395 | };
396 | // 初始化emit
397 | // 使用bind 返回一个函数
398 | componentInstance.emit = emit.bind(null, componentInstance);
399 | return componentInstance;
400 | };
401 | const setupComponent = (instance) => {
402 | // TODO 后续处理 -> 只处理了普通的
403 | initProps(instance, instance.vnode.props);
404 | initSlots(instance, instance.vnode.children);
405 | // 处理初始化setup
406 | setupStatefulComponent(instance);
407 | };
408 | function setupStatefulComponent(instance) {
409 | const component = instance.type;
410 | // 组件代理对象 this.xx
411 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandlers);
412 | const { setup } = component;
413 | if (setup) {
414 | // 存储instance,用于getCurrentInstance
415 | setCurrentInstance(instance);
416 | const setupResult = setup(shallowReadonly(instance.props), {
417 | emit: instance.emit
418 | });
419 | setCurrentInstance(null);
420 | handleSetupResult(instance, setupResult);
421 | }
422 | }
423 | function handleSetupResult(instance, setupResult) {
424 | // setupResult 返回的值类型可能是 function | object
425 | // TODO Function先不处理, 先处理Object
426 | if (typeof setupResult === 'object') {
427 | // 通过proxyRef来解决Ref获取值
428 | instance.setupState = proxyRef(setupResult);
429 | }
430 | // 保证render有值
431 | finishComponentSetup(instance);
432 | }
433 | function finishComponentSetup(instance) {
434 | const component = instance.type;
435 | // 暂时默认都有render
436 | instance.render = component.render;
437 | }
438 | const getCurrentInstance = () => {
439 | return instance;
440 | };
441 | function setCurrentInstance(_instance) {
442 | instance = _instance;
443 | }
444 |
445 | // 场景1: 父组件 存, 子组件 取 ==> 存到provides里面
446 | // 场景2: 父组件 存, 子孙组件 取 ==> provides指定父类的provides
447 | // 场景3: 当在中间层组件中使用了 provide
448 | const provide = (key, value) => {
449 | // 存值
450 | const currentInstance = getCurrentInstance();
451 | if (currentInstance) {
452 | let { provides } = currentInstance;
453 | // init
454 | if (provides === currentInstance.parent.provides) {
455 | // 原型指向父类的provides, 创建一个新的
456 | provides = currentInstance.provides = Object.create(currentInstance.provides);
457 | }
458 | provides[key] = value;
459 | }
460 | };
461 | const inject = (key) => {
462 | // 取值
463 | const currentInstance = getCurrentInstance();
464 | if (currentInstance) {
465 | // 父类里面拿
466 | const { parent } = currentInstance;
467 | const { provides } = parent;
468 | return provides[key];
469 | }
470 | };
471 |
472 | const shouldUpdateComponent = (n1, n2) => {
473 | const { props: prevProps } = n1;
474 | const { props: nextProps } = n2;
475 | for (const key in prevProps) {
476 | if (prevProps[key] !== nextProps[key]) {
477 | return true;
478 | }
479 | }
480 | return false;
481 | };
482 |
483 | const createAppAPI = (render) => {
484 | return function (rootComponent) {
485 | return {
486 | mount(rootContainer) {
487 | // 将component转换成vnode
488 | // 后续所有操作都会基于vnode 来进行操作
489 | const vnode = createVNode(rootComponent);
490 | render(vnode, rootContainer);
491 | }
492 | };
493 | };
494 | };
495 |
496 | const queue = [];
497 | const promise = Promise.resolve();
498 | // 解决不要多次创建Promise
499 | let isExceute = false;
500 | const nextTick = (fn) => {
501 | return fn ? promise.then(fn) : promise;
502 | };
503 | const queueJob = (job) => {
504 | // 判断一下是否存在队列,如果存在就不加了
505 | if (!queue.includes(job)) {
506 | queue.push(job);
507 | }
508 | // 执行微任务
509 | excuteQueue();
510 | };
511 | function excuteQueue() {
512 | // MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
513 | if (isExceute)
514 | return;
515 | isExceute = true;
516 | nextTick(() => {
517 | isExceute = false;
518 | let job;
519 | while ((job = queue.shift())) {
520 | job & job();
521 | }
522 | });
523 | }
524 |
525 | const createRenderer = (options) => {
526 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText } = options;
527 | const render = (n2, container) => {
528 | // 调用patch
529 | patch(null, n2, container, null, null);
530 | };
531 | const patch = (n1, n2, container, parent, anchor) => {
532 | // 判断 是不是 element类型
533 | // 如果是Component,type => Object
534 | // 如果是element类型,type => div等标签
535 | const { shapeFlag, type } = n2;
536 | switch (type) {
537 | case Fragment: {
538 | processFragment(n1, n2, container, parent, anchor);
539 | break;
540 | }
541 | case Text: {
542 | processText(n1, n2, container);
543 | break;
544 | }
545 | default: {
546 | if (shapeFlag & 1 /* ELEMENT */) {
547 | // 处理Element
548 | processElement(n1, n2, container, parent, anchor);
549 | }
550 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
551 | // 处理组件
552 | processComponent(n1, n2, container, parent, anchor);
553 | }
554 | }
555 | }
556 | };
557 | const processComponent = (n1, n2, container, parent, anchor) => {
558 | // 挂载组件
559 | // 如果n1有值,证明是需要进行更新
560 | if (!n1) {
561 | mountComponent(n2, container, parent, anchor);
562 | }
563 | else {
564 | updateComponent(n1, n2);
565 | }
566 | };
567 | const updateComponent = (n1, n2) => {
568 | // 判断n1, n2的props是否相等
569 | // 相等才会进行组件更新
570 | const component = (n2.component = n1.component);
571 | if (shouldUpdateComponent(n1, n2)) {
572 | console.log('[Function:updateComponent]:组件更新n1', n1);
573 | console.log('[Function:updateComponent]:组件更新n2', n2);
574 | component.next = n2;
575 | component.update();
576 | }
577 | else {
578 | n2.el = n1.el;
579 | n2.vnode = n2;
580 | }
581 | };
582 | const processFragment = (n1, n2, container, parent, anchor) => {
583 | mountChildren(n2.children, container, parent, anchor);
584 | };
585 | const processText = (n1, n2, container) => {
586 | const { children } = n2;
587 | const textNode = (n2.el = document.createTextNode(children));
588 | container.append(textNode);
589 | };
590 | function processElement(n1, n2, container, parent, anchor) {
591 | if (!n1) {
592 | mountElement(n2, container, parent, anchor);
593 | }
594 | else {
595 | patchElement(n1, n2, container, parent, anchor);
596 | }
597 | }
598 | function mountElement(vnode, container, parent, anchor) {
599 | const { type, props, children, shapeFlag } = vnode;
600 | // 创建对应的el
601 | // vnode -> element -> div
602 | const el = (vnode.el = hostCreateElement(type));
603 | // 处理props => 普通属性 和 注册事件
604 | for (const key in props) {
605 | hostPatchProp(el, key, null, props[key]);
606 | }
607 | // 处理children --> string, Array
608 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
609 | el.innerText = children;
610 | }
611 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
612 | mountChildren(children, el, parent, anchor);
613 | }
614 | // 插入
615 | hostInsert(el, container, anchor);
616 | }
617 | function patchElement(n1, n2, container, parent, anchor) {
618 | console.log("n1", n1);
619 | console.log("n2", n2);
620 | const oldProps = n1.props;
621 | const newProps = n2.props;
622 | // 需要把 el 挂载到新的 vnode
623 | // n1: 旧的 => n2: 新的
624 | const el = (n2.el = n1.el);
625 | // 对比props
626 | patchProps(el, oldProps, newProps);
627 | // 对比children
628 | patchChildren(n1, n2, el, parent, anchor);
629 | }
630 | function patchProps(el, oldProps, newProps) {
631 | console.log('oldProps', oldProps);
632 | console.log('newProps', newProps);
633 | // 当不相同的时候才会去判断
634 | if (oldProps !== newProps) {
635 | // 遍历新值
636 | for (const key in newProps) {
637 | const prevProp = oldProps[key];
638 | const nextProp = newProps[key];
639 | if (prevProp !== newProps) {
640 | // 对比属性值,如果不相同则需要进行更新
641 | hostPatchProp(el, key, prevProp, nextProp);
642 | }
643 | }
644 | // 遍历旧值
645 | for (const key in oldProps) {
646 | const prevProp = oldProps[key];
647 | if (!(key in newProps)) {
648 | // 判断新props没有的,没有的就去掉null
649 | hostPatchProp(el, key, prevProp, null);
650 | }
651 | }
652 | }
653 | }
654 | function patchChildren(n1, n2, container, parent, anchor) {
655 | // 获取shapeFlags 来判断类型
656 | const prevShapeFlag = n1.shapeFlag;
657 | const shapeFlag = n2.shapeFlag;
658 | // 新节点是 Text 时候
659 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
660 | // 老节点是 Children
661 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
662 | // 清空老的 Array子节点
663 | unmountChildren(n1.children);
664 | }
665 | // 新老节点都不相同, 新节点需要是Text
666 | if (n1.children !== n2.children) {
667 | // 插入新的 Text节点
668 | hostSetElementText(container, n2.children);
669 | }
670 | }
671 | else {
672 | // 新节点是 Children 时候
673 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) {
674 | // 老节点是 Text
675 | hostSetElementText(container, "");
676 | mountChildren(n2.children, container, parent, anchor);
677 | }
678 | else {
679 | // 老节点是 Children
680 | patchKeyedChildren(n1.children, n2.children, container, parent, anchor);
681 | }
682 | }
683 | }
684 | function patchKeyedChildren(c1, c2, container, parent, anchor) {
685 | let i = 0;
686 | let e1 = c1.length - 1;
687 | let e2 = c2.length - 1;
688 | function isSomeVNodeType(n1, n2) {
689 | return n1.type === n2.type && n1.key === n2.key;
690 | }
691 | // 1. 左侧
692 | while (i <= e1 && i <= e2) {
693 | const n1 = c1[i];
694 | const n2 = c2[i];
695 | if (isSomeVNodeType(n1, n2)) {
696 | patch(n1, n2, container, parent, anchor);
697 | }
698 | else {
699 | break;
700 | }
701 | i++;
702 | }
703 | console.log('左侧之后i', i);
704 | // 2. 右侧
705 | while (e1 >= i && e2 >= i) {
706 | const n1 = c1[e1];
707 | const n2 = c2[e2];
708 | if (isSomeVNodeType(n1, n2)) {
709 | patch(n1, n2, container, parent, anchor);
710 | }
711 | else {
712 | break;
713 | }
714 | e1--;
715 | e2--;
716 | }
717 | console.log('右侧之后e1', e1);
718 | console.log('右侧之后e2', e2);
719 | // 3.新的比老的长, 新的进行创建
720 | // i > 旧的 证明旧的已经diff完了
721 | // 1 <= 新的 属于diff后需要新增的范围
722 | // 左侧 -> 新增
723 | // a b
724 | // a b c d
725 | // i = 2 > e1 = 2
726 | // i = 2 <= e2 = 3
727 | // 左侧 -> 删除
728 | // a b c
729 | // a b
730 | // i = 2 > e1 = 3
731 | // i = 2 <= e2 = 2
732 | // 右侧
733 | // a b
734 | // c d a b
735 | // i = 0 > e1 = -1
736 | // i = 0 <= e2 = 1
737 | if (i > e1) {
738 | if (i <= e2) {
739 | const nextProp = e2 + 1;
740 | const anchor = nextProp < c2.length ? c2[nextProp].el : null;
741 | while (i <= e2) {
742 | patch(null, c2[i], container, parent, anchor);
743 | i++;
744 | }
745 | }
746 | }
747 | else if (i > e2) {
748 | // 4. i值大于新节点长度e2, 进行remove
749 | while (i <= e1) {
750 | hostRemove(c1[i].el);
751 | i++;
752 | }
753 | }
754 | else {
755 | // 5. 中间进行对比
756 | let s1 = i;
757 | let s2 = i;
758 | let moved = false;
759 | let newIndexForMax = 0;
760 | // 新节点需要对比的节点数
761 | let toBePatched = e2 - s2 + 1;
762 | let patched = 0;
763 | // 新节点获取key值
764 | const keyToNewIndexMap = new Map();
765 | for (let j = s2; j <= e2; j++) {
766 | const nextChild = c2[j];
767 | keyToNewIndexMap.set(nextChild.key, j);
768 | }
769 | // 进行移动
770 | // 定一个长度为 新节点需要对比的节点数量
771 | const newIndexToOldIndexMap = new Array(toBePatched);
772 | for (let i = 0; i < toBePatched; i++) {
773 | newIndexToOldIndexMap[i] = 0;
774 | }
775 | // 旧节点进行循环
776 | for (let index = s1; index <= e1; index++) {
777 | const pervChild = c1[index];
778 | const pervKey = pervChild.key;
779 | let nextIndex;
780 | // 旧节点长度比旧节点的要长 且已经不需要对比了 直接删除
781 | if (patched >= toBePatched) {
782 | hostRemove(pervChild.el);
783 | continue;
784 | }
785 | if (pervKey !== null) {
786 | // 获取新节点的对应的keyindex
787 | nextIndex = keyToNewIndexMap.get(pervKey);
788 | }
789 | else {
790 | for (let j = s2; j <= e2; j++) {
791 | if (isSomeVNodeType(pervChild, c2[j])) {
792 | nextIndex = j;
793 | break;
794 | }
795 | }
796 | }
797 | if (nextIndex !== undefined) {
798 | // 判断是否需要移动
799 | if (nextIndex >= newIndexForMax) {
800 | newIndexForMax = nextIndex;
801 | }
802 | else {
803 | moved = true;
804 | }
805 | // +1 的原因是 在使用最长递增子序列算法时候,0是用于判断
806 | newIndexToOldIndexMap[nextIndex - s2] = index + 1;
807 | patch(pervChild, c2[nextIndex], container, parent, null);
808 | patched++;
809 | }
810 | else {
811 | // 旧的节点 不存在新的里面,需要删除
812 | hostRemove(pervChild.el);
813 | }
814 | }
815 | // 得到了印射表newIndexToOldIndexMap后,进行移动处理
816 | const newIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
817 | let j = newIndexSequence.length - 1; // 索引 -1
818 | // 为了稳定的锚点,需要倒序
819 | for (let i = toBePatched - 1; i >= 0; i--) {
820 | const nextIndex = i + s2;
821 | const nextChild = c2[nextIndex];
822 | const nextAnchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null;
823 | if (newIndexToOldIndexMap[i] === 0) {
824 | // 当印射表里面等于 0 相当于 没有新的节点 没有需要移动的而且是不存在的,这时候需要添加一个节点
825 | patch(null, nextChild, container, parent, nextAnchor);
826 | }
827 | else if (moved) {
828 | if (i !== newIndexSequence[j]) {
829 | console.log("需要移动位置");
830 | hostInsert(nextChild, container, nextAnchor);
831 | }
832 | else {
833 | j--;
834 | }
835 | }
836 | }
837 | }
838 | }
839 | function unmountChildren(children) {
840 | for (const key in children) {
841 | hostRemove(children[key].el);
842 | }
843 | }
844 | function mountChildren(vnodes = [], container, parent, anchor) {
845 | vnodes.forEach(element => {
846 | patch(null, element, container, parent, anchor);
847 | });
848 | }
849 | const mountComponent = (n2, container, parent, anchor) => {
850 | console.log('parent', parent);
851 | // 创建组件实例, 收集数据
852 | const instance = (n2.component = createComponentInstance(n2, parent));
853 | // 初始化 props, slots, setup
854 | setupComponent(instance);
855 | // 进行拆箱
856 | setupRenderEffect(instance, n2, container, anchor);
857 | };
858 | const setupRenderEffect = (instance, n2, container, anchor) => {
859 | // 依赖收集
860 | instance.update = effect(() => {
861 | const { isMounted } = instance;
862 | if (!isMounted) {
863 | // 是否初始化
864 | // 指定代理对象 this.xxx
865 | const { proxy } = instance;
866 | const subTree = instance.render.call(proxy);
867 | // subTree 虚拟节点树 n2 tree
868 | // n2 -> patch
869 | // n2 -> element -> mountElement
870 | patch(null, subTree, container, instance, anchor);
871 | // vnode节点存起来
872 | instance.subTree = subTree;
873 | // 完成了所有的patch后
874 | n2.el = subTree.el;
875 | // 初始化结束后,转换状态
876 | instance.isMounted = true;
877 | }
878 | else {
879 | console.log('update');
880 | const { proxy, next, vnode } = instance;
881 | // 更新props
882 | // 需要获取vnode, next-> 下次更新虚拟节点,vnode之前的虚拟节点
883 | if (next) {
884 | next.el = vnode.el;
885 | updateComponentPreRender(instance, next);
886 | }
887 | // 当前(新)节点
888 | const subTree = instance.render.call(proxy);
889 | // 旧节点
890 | const prevSubTree = instance.subTree;
891 | // vnode节点存起来
892 | instance.subTree = subTree;
893 | // 新节点 和 旧节点 进行对比
894 | patch(prevSubTree, subTree, container, instance, anchor);
895 | }
896 | }, {
897 | scheduler: () => {
898 | console.log('update - scheduler');
899 | // 将effect任务 加入到微任务里,再异步执行微任务
900 | queueJob(instance.update);
901 | }
902 | });
903 | };
904 | const updateComponentPreRender = (instance, nextVNode) => {
905 | instance.vnode = nextVNode;
906 | instance.props = nextVNode.props;
907 | instance.next = null;
908 | };
909 | return {
910 | createApp: createAppAPI(render)
911 | };
912 | };
913 | /**
914 | * 最长子序列算法LIS
915 | * @param arr
916 | * @returns
917 | */
918 | function getSequence(arr) {
919 | const p = arr.slice();
920 | const result = [0];
921 | let i, j, u, v, c;
922 | const len = arr.length;
923 | for (i = 0; i < len; i++) {
924 | const arrI = arr[i];
925 | if (arrI !== 0) {
926 | j = result[result.length - 1];
927 | if (arr[j] < arrI) {
928 | p[i] = j;
929 | result.push(i);
930 | continue;
931 | }
932 | u = 0;
933 | v = result.length - 1;
934 | while (u < v) {
935 | c = (u + v) >> 1;
936 | if (arr[result[c]] < arrI) {
937 | u = c + 1;
938 | }
939 | else {
940 | v = c;
941 | }
942 | }
943 | if (arrI < arr[result[u]]) {
944 | if (u > 0) {
945 | p[i] = result[u - 1];
946 | }
947 | result[u] = i;
948 | }
949 | }
950 | }
951 | u = result.length;
952 | v = result[u - 1];
953 | while (u-- > 0) {
954 | result[u] = v;
955 | v = p[v];
956 | }
957 | return result;
958 | }
959 |
960 | const createElement = (type) => {
961 | return document.createElement(type);
962 | };
963 | const patchProp = (el, key, preValue, nextValue) => {
964 | const isOn = (key) => /^on[A-Z]/.test(key);
965 | if (isOn(key)) {
966 | const event = key.slice(2).toLowerCase();
967 | el.addEventListener(event, nextValue);
968 | }
969 | else {
970 | if (nextValue === null || nextValue === undefined) {
971 | // 如果是 null 或者 undefined,直接remove
972 | el.removeAttribute(key);
973 | }
974 | else {
975 | // 正常设置
976 | el.setAttribute(key, nextValue);
977 | }
978 | }
979 | };
980 | const setElementText = (el, text) => {
981 | el.textContent = text;
982 | };
983 | const remove = (child) => {
984 | const parent = child.parentNode;
985 | if (parent) {
986 | parent.removeChild(child);
987 | }
988 | };
989 | const insert = (child, parent, anchor) => {
990 | parent.insertBefore(child, anchor);
991 | };
992 | const renderer = createRenderer({
993 | createElement,
994 | patchProp,
995 | insert,
996 | remove,
997 | setElementText
998 | });
999 | function createApp(...args) {
1000 | return renderer.createApp(...args);
1001 | }
1002 |
1003 | export { createApp, createElement, createRenderer, createTextVNode, getCurrentInstance, h, inject, insert, isRef, nextTick, patchProp, provide, proxyRef, ref, remove, renderSlot, setElementText, unRef };
1004 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-vue",
3 | "version": "1.0.0",
4 | "main": "lib/mini-vue.cjs.js",
5 | "module": "lib/mini-vue.esm.js",
6 | "repository": "https://gitee.com/jiashengBen/mini-vue.git",
7 | "author": "liangjs ",
8 | "license": "MIT",
9 | "scripts": {
10 | "test": "jest",
11 | "build": "rollup -c rollup.config.js"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "^7.17.7",
15 | "@babel/preset-env": "^7.16.11",
16 | "@babel/preset-typescript": "^7.16.7",
17 | "@rollup/plugin-typescript": "^8.3.1",
18 | "@types/jest": "^27.4.1",
19 | "babel-jest": "^27.5.1",
20 | "jest": "^27.5.1",
21 | "rollup": "^2.70.1",
22 | "tslib": "^2.3.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 |
2 | // rollup无法解析TS, 需要使用plugin
3 | import typescript from "@rollup/plugin-typescript"
4 | import pkg from "./package.json"
5 |
6 | export default {
7 | input: './src/index.ts',
8 | output: [
9 | // 输出两种模式
10 | // cjs --> node commonjs
11 | // esm --> es module
12 | {
13 | format: 'cjs',
14 | file: pkg.main
15 | },
16 | {
17 | format: 'es',
18 | file: pkg.module
19 | }
20 | ],
21 | plugins: [typescript()]
22 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './runtime-dom'
3 | export * from './reactivity'
--------------------------------------------------------------------------------
/src/reactivity/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { isObject, extend } from '../shared';
2 | import { track, trigger } from './effect';
3 | import { reactive, readonly } from './reactive';
4 |
5 | export const enum flags {
6 | IS_REACTIVE = '__v_isReactive',
7 | IS_READONLY = '__v_isReadonly'
8 | }
9 |
10 | const createGetter = (isReadOnly = false, isShallowReadonly = false, isShallowReactive = false) => {
11 | return (target, key) => {
12 |
13 | if (key === flags.IS_REACTIVE) {
14 | return !isReadOnly
15 | } else if (key === flags.IS_READONLY) {
16 | return isReadOnly
17 | }
18 |
19 | const res = Reflect.get(target, key)
20 |
21 | // shallowReadonly
22 | // shallow 只需要获取表面的那层数据
23 | if (isShallowReadonly) {
24 | return res
25 | }
26 |
27 | if (isObject(res) && !isShallowReactive) {
28 | return isReadOnly ? readonly(res) : reactive(res)
29 | }
30 |
31 | // 收集依赖
32 | if (!isReadOnly) {
33 | track(target, key)
34 | }
35 | return res
36 | }
37 | }
38 |
39 | const createSetter = () => {
40 | return (target, key, value) => {
41 | const res = Reflect.set(target, key, value)
42 | // 触发依赖
43 | trigger(target, key)
44 | return res
45 | }
46 | }
47 |
48 | const get = createGetter()
49 | const set = createSetter()
50 | const readonlyGet = createGetter(true)
51 | const shallowReadonlyGet = createGetter(true, true)
52 | const shallowReactiveGet = createGetter(false, false, true)
53 |
54 | export const baseHandler = {
55 | get,
56 | set
57 | }
58 |
59 | export const readonlyHandler = {
60 | get: readonlyGet,
61 | set(target, key, value) {
62 | throw new Error(`${target} 因为 readonly模式,所以无法修改`)
63 | }
64 | }
65 |
66 | export const shallowReadonlyHandler = extend({}, readonlyHandler, {
67 | get: shallowReadonlyGet
68 | });
69 |
70 | export const shallowReactiveHandler = extend({}, baseHandler, {
71 | get: shallowReactiveGet
72 | });
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl {
4 | private _effect: any;
5 | private _dirty: boolean = true;
6 | private _value: string = '';
7 | constructor(runner) {
8 | this._effect = new ReactiveEffect(runner, () => {
9 | this._dirty = true;
10 | });
11 | }
12 |
13 | public get value() : string {
14 | // 判断是不是第一次获取,如果是第一次获取则使用 dirty 进行缓存
15 | if (this._dirty) {
16 | this._dirty = false;
17 | this._value = this._effect.run();
18 | }
19 | return this._value;
20 | }
21 |
22 | }
23 |
24 | export const computed = (getterFn) => {
25 | return new ComputedRefImpl(getterFn)
26 | };
27 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | // target -> key -> dep
2 | const targetKeyMap = new Map();
3 | let activeEffect;
4 | // 是否应该收集
5 | let shouldTrack = false;
6 |
7 | export class ReactiveEffect {
8 | fn: Function;
9 | deps = []
10 | state = true
11 | onStop?: () => void
12 | constructor(fn: Function, public scheduler?: Function) {
13 | this.fn = fn
14 | this.scheduler = scheduler
15 | }
16 | run() {
17 | // 判断是不是被stop
18 | if (!this.state) {
19 | return this.fn()
20 | }
21 |
22 | // 如果不是被stop,可被收集依赖
23 | // shouldTrack 打开
24 | shouldTrack = true
25 | activeEffect = this
26 | const runner = this.fn()
27 |
28 | // shouldTrack 是全局,所以需要被reset
29 | shouldTrack = false
30 |
31 | return runner
32 | }
33 | stop() {
34 | if (this.state) {
35 | if (this.onStop) {
36 | this.onStop()
37 | }
38 | cleanDeps(this)
39 | this.state = false
40 | }
41 |
42 | }
43 | }
44 |
45 | /**
46 | * 清除所有依赖dep
47 | * @param effect
48 | */
49 | const cleanDeps = (effect) => {
50 | effect.deps.forEach((dep: any) => {
51 | dep.delete(effect)
52 | })
53 | };
54 |
55 | /**
56 | * 收集依赖
57 | * target -> key -> dep
58 | * @param target
59 | * @param key
60 | * @returns
61 | */
62 | export const track = (target, key) => {
63 | // 判断是否可以被收集依赖
64 | // 1. shouldTrack 是否放通
65 | // 2. activeEffect 在run时候是否有被赋值
66 | if(!isTracking()) return
67 |
68 | let depMap;
69 | let dep;
70 | if (!targetKeyMap.has(target)) {
71 | depMap = new Map()
72 | targetKeyMap.set(target, depMap)
73 | }
74 | depMap = targetKeyMap.get(target)
75 | if (!depMap.has(key)) {
76 | dep = new Set()
77 | depMap.set(key, dep)
78 | }
79 | dep = depMap.get(key)
80 | trackEffect(dep)
81 | };
82 |
83 | export const trackEffect = (dep) => {
84 | if(dep.has(activeEffect)) return;
85 | dep.add(activeEffect)
86 | activeEffect.deps.push(dep)
87 | };
88 |
89 | /**
90 | * 触发依赖
91 | * @param target
92 | * @param key
93 | */
94 | export const trigger = (target, key) => {
95 | let depsMap = targetKeyMap.get(target)
96 | let dep = depsMap.get(key)
97 | triggerEffect(dep)
98 | };
99 |
100 | export const triggerEffect = (dep) => {
101 | for (const effect of dep) {
102 | if (effect.scheduler) {
103 | effect.scheduler()
104 | } else {
105 | effect.run()
106 | }
107 | }
108 | };
109 |
110 |
111 | export const effect = (fn: Function, options: any = {}) => {
112 | const reactiveEffect = new ReactiveEffect(fn, options.scheduler);
113 | reactiveEffect.onStop = options.onStop
114 | reactiveEffect.run();
115 | const runner: any = reactiveEffect.run.bind(reactiveEffect);
116 | runner.effect = reactiveEffect
117 | return runner
118 | };
119 |
120 | export const stop = (runner) => {
121 | runner.effect.stop()
122 | };
123 |
124 | /**
125 | * 可以被收集依赖
126 | * @returns
127 | */
128 | export const isTracking = () => {
129 | return shouldTrack && activeEffect != undefined
130 | };
131 |
132 |
133 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export * from './ref'
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 |
2 | import { isObject } from '../shared';
3 | import { baseHandler, flags, readonlyHandler, shallowReadonlyHandler, shallowReactiveHandler } from './baseHandlers';
4 |
5 | type Proxy = ProxyConstructor
6 |
7 | export const isReactive = (target) => {
8 | return !!target[flags.IS_REACTIVE]
9 | };
10 |
11 | export const isReadonly = (target) => {
12 | return !!target[flags.IS_READONLY]
13 | };
14 |
15 | export const isProxy = (params: any) => {
16 | // 判断是reactive 或者 判断是isReadOnly
17 | return isReactive(params) || isReadonly(params)
18 | };
19 |
20 | export function reactive(params: any) {
21 | return createReactive(params, baseHandler)
22 | };
23 |
24 | export const readonly = (params: any) => {
25 | return createReactive(params, readonlyHandler)
26 | };
27 |
28 | export const shallowReadonly = (params: any) => {
29 | return createReactive(params, shallowReadonlyHandler)
30 | };
31 |
32 | export const shallowReactive = (params: any) => {
33 | return createReactive(params, shallowReactiveHandler)
34 | }
35 |
36 | const createReactive = (target, baseHandler) => {
37 | if (!isObject(target)) {
38 | console.warn(`Reactive target ${target} 只能是对象。`)
39 | return target
40 | }
41 | return new Proxy(target, baseHandler)
42 | }
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "../shared";
2 | import { isTracking, trackEffect, triggerEffect } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | class RefImpl {
6 | _value: any;
7 | public _rawValue;
8 | public dep;
9 | private __v_isRef: boolean = true;
10 | constructor(_value: any) {
11 | this._value = convert(_value)
12 | this._rawValue = _value
13 | this.dep = new Set()
14 | }
15 |
16 | get value() {
17 | trackRefEffect(this.dep)
18 | return this._value
19 | }
20 |
21 | set value(newValue) {
22 | if (hasChanged(this._rawValue, newValue)) {
23 | this._rawValue = newValue
24 | this._value = convert(newValue)
25 | triggerEffect(this.dep)
26 | }
27 | }
28 | }
29 |
30 | function trackRefEffect(dep) {
31 | if (isTracking()) {
32 | trackEffect(dep)
33 | }
34 | }
35 |
36 | function convert(newValue) {
37 | return isObject(newValue) ? reactive(newValue) : newValue
38 | }
39 |
40 | export const ref = (val: any) => {
41 | return new RefImpl(val)
42 | };
43 |
44 | export const isRef = (ref) => {
45 | // 在RefImpl 里面给一个判断值
46 | return !!ref.__v_isRef
47 | };
48 |
49 | // 判断是不是ref
50 | // -- 如果是ref,则直接ref.value
51 | // -- 如果不是ref,则直接返回ref
52 | export const unRef = (ref) => {
53 | return isRef(ref) ? ref.value : ref
54 | };
55 |
56 | export const proxyRef = (proxyTarget) => {
57 | return new Proxy(proxyTarget, {
58 | get(target, key) {
59 | // 利用unRef判断获取值
60 | return unRef(Reflect.get(target, key))
61 | },
62 | set(target, key, value) {
63 | // 判断是不是ref 如果是ref,且返回的值不是ref,则使用.value来赋值
64 | // 否则直接赋值
65 | if(isRef(target[key]) && !isRef(value)) {
66 | return (target[key].value = value);
67 | } else {
68 | return Reflect.set(target, key, value)
69 | }
70 | }
71 | })
72 | };
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../computed";
2 | import { reactive } from "../reactive";
3 |
4 | describe('computed', () => {
5 |
6 | it('simple', () => {
7 |
8 | const foo = reactive({ num: 1 })
9 | const bar = computed(() => {
10 | return foo.num
11 | })
12 |
13 | expect(bar.value).toBe(1);
14 | });
15 |
16 | it('should computed lazy', () => {
17 | const foo = reactive({ num: 1 })
18 |
19 | const runner = jest.fn(() => {
20 | return foo.num
21 | })
22 |
23 | const bar = computed(runner)
24 |
25 | expect(runner).not.toHaveBeenCalled();
26 |
27 | expect(bar.value).toBe(1);
28 | expect(runner).toHaveBeenCalledTimes(1);
29 |
30 | // again computed
31 | expect(bar.value).toBe(1);
32 | expect(runner).toHaveBeenCalledTimes(1);
33 |
34 | foo.num = 2 // trigger
35 | expect(runner).toHaveBeenCalledTimes(1);
36 |
37 | // 因为trigger后,改变了,所以再次被调用了
38 | // 所以被调用次数为 2
39 | expect(bar.value).toBe(2);
40 | expect(runner).toHaveBeenCalledTimes(2);
41 |
42 | // again computed
43 | expect(bar.value).toBe(2);
44 | expect(runner).toHaveBeenCalledTimes(2);
45 |
46 | })
47 |
48 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from '../reactive';
2 | import { effect, stop } from '../effect';
3 |
4 | describe("effect", () => {
5 | it('reactive effect', function () {
6 | const user = reactive({
7 | age: 10
8 | });
9 |
10 | let nextAge;
11 | effect(() => {
12 | nextAge = user.age + 1
13 | })
14 |
15 | expect(nextAge).toBe(11)
16 |
17 | user.age++
18 | expect(nextAge).toBe(12)
19 |
20 | });
21 |
22 | it('effect runner', function () {
23 | // effect(fn) => function(runner) => fn => return
24 | let foo = 10;
25 | const runner = effect(() => {
26 | foo++
27 | return "foo"
28 | })
29 |
30 | expect(foo).toBe(11)
31 | const str = runner()
32 | expect(foo).toBe(12)
33 | expect(str).toBe('foo')
34 |
35 | })
36 |
37 | it('effect scheduler', () => {
38 | let foo = reactive({ age: 10 });
39 | let run: any;
40 | let bar: unknown;
41 |
42 | const scheduler = jest.fn(() => {
43 | run = runner
44 | })
45 |
46 | const runner = effect(() => {
47 | bar = foo.age
48 | }, { scheduler })
49 |
50 | // scheduler 没有被调用
51 | expect(scheduler).not.toHaveBeenCalled()
52 | expect(bar).toBe(10)
53 | // 进行数据变更 update
54 | foo.age++
55 | // scheduler 被调用一次
56 | expect(scheduler).toHaveBeenCalledTimes(1)
57 | run()
58 | expect(bar).toBe(11)
59 | })
60 |
61 | it('effect stop', () => {
62 |
63 | const car = reactive({ name: 'benz' })
64 | let name;
65 |
66 | const runner = effect(() => {
67 | name = car.name
68 | })
69 |
70 | car.name = 'BMW'
71 | expect(name).toBe('BMW')
72 | stop(runner)
73 |
74 | // 这里直接触发 set
75 | // 只是触发了trigger,需要触发依赖
76 | // 之前已经执行了stop,所以已经没有了任何的依赖
77 | // name 还是等于BMW
78 | car.name = 'Audi'
79 | expect(name).toBe('BMW')
80 |
81 | // 这里触发了 get set
82 | // 执行了一个get,就要进行了一次收集依赖
83 | // 之前调用stop的清除依赖已经失效了,被收集起来
84 | // 所以在执行 set 的时候,就会触发依赖 执行effect runner
85 | // 这里需要对stop进行优化
86 | car.name = car.name + '2'
87 | expect(name).toBe('BMW')
88 |
89 | runner()
90 | expect(name).toBe('Audi2')
91 |
92 | })
93 |
94 | it('effect onStop', () => {
95 |
96 | const obj = reactive({
97 | bar: 1
98 | })
99 |
100 | let foo;
101 |
102 | const onStop = jest.fn(() => {
103 | foo = 10
104 | })
105 |
106 | const runner = effect(() => {
107 | foo = obj.bar
108 | }, { onStop })
109 |
110 | stop(runner)
111 | expect(onStop).toBeCalledTimes(1)
112 | expect(foo).toBe(10)
113 |
114 |
115 |
116 | })
117 |
118 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/lis.spec.ts:
--------------------------------------------------------------------------------
1 | function getSequence(arr: number[]): number[] {
2 | const p = arr.slice();
3 | const result = [0];
4 | let i, j, u, v, c;
5 | const len = arr.length;
6 | for (i = 0; i < len; i++) {
7 | const arrI = arr[i];
8 | if (arrI !== 0) {
9 | j = result[result.length - 1];
10 | if (arr[j] < arrI) {
11 | p[i] = j;
12 | result.push(i);
13 | continue;
14 | }
15 | u = 0;
16 | v = result.length - 1;
17 | while (u < v) {
18 | c = (u + v) >> 1;
19 | if (arr[result[c]] < arrI) {
20 | u = c + 1;
21 | } else {
22 | v = c;
23 | }
24 | }
25 | if (arrI < arr[result[u]]) {
26 | if (u > 0) {
27 | p[i] = result[u - 1];
28 | }
29 | result[u] = i;
30 | }
31 | }
32 | }
33 | u = result.length;
34 | v = result[u - 1];
35 | while (u-- > 0) {
36 | result[u] = v;
37 | v = p[v];
38 | }
39 | return result;
40 | }
41 |
42 | describe('lis', () => {
43 | it('test', () => {
44 | const arr = getSequence([5, 0, 3, 4])
45 | });
46 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive, isReactive, readonly, isReadonly, isProxy } from '../reactive'
2 |
3 | describe('reactive', () => {
4 | it('bar', () => {
5 | const original = { name: 'ben' }
6 | const observed = reactive(original)
7 | expect(observed).not.toBe(original)
8 | expect(observed.name).toBe('ben')
9 | })
10 |
11 | it('isReactive', () => {
12 | const original = { foo: 1 }
13 | const observed = reactive(original)
14 | expect(isReactive(observed)).toBe(true)
15 | expect(isReactive(original)).toBe(false)
16 | })
17 |
18 | it('isReadonly', () => {
19 | const original = { foo: 1 }
20 | const readonlyed = readonly(original)
21 | const observed = reactive(original)
22 | expect(isReadonly(readonlyed)).toBe(true)
23 | expect(isReadonly(observed)).toBe(false)
24 | expect(isReadonly(original)).toBe(false)
25 | })
26 |
27 | it('nested reactive', () => {
28 | const obj = {
29 | key: 'value',
30 | name: {
31 | foo: 1
32 | },
33 | arr: [{ bar: 2 }]
34 | }
35 | const observed = reactive(obj)
36 | expect(isReactive(observed)).toBe(true)
37 | expect(isReactive(observed.name)).toBe(true)
38 | })
39 |
40 | it('isProxy', () => {
41 | const original = { name: 'ben' }
42 | const observed = reactive(original)
43 | const isReadObserved = readonly(original)
44 |
45 | const proxy = new Proxy(original, {})
46 |
47 | expect(isProxy(observed)).toBe(true)
48 | expect(isProxy(isReadObserved)).toBe(true)
49 |
50 | // because use isReactive or isReadonly
51 | expect(isProxy(proxy)).toBe(false)
52 | })
53 |
54 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, readonly } from '../reactive'
2 |
3 | describe('readonly', () => {
4 | it('bar', () => {
5 | const original = { name: 'ben', address: { room: '202' } }
6 | const observed = readonly(original)
7 | expect(observed).not.toBe(original)
8 | expect(observed.name).toBe('ben')
9 | expect(isReadonly(observed.address)).toBe(true)
10 | })
11 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect"
2 | import { isReactive, reactive } from "../reactive"
3 | import { isRef, proxyRef, ref, unRef } from "../ref"
4 |
5 | describe('refs', () => {
6 |
7 | it('ref example', () => {
8 | const count = ref(0)
9 | expect(count.value).toBe(0)
10 | })
11 |
12 | it('ref should be reactive', () => {
13 | const count = ref(1)
14 | let num;
15 | let callEffectCount = 0
16 | expect(count.value).toBe(1)
17 |
18 | effect(() => {
19 | callEffectCount++
20 | num = count.value
21 | })
22 |
23 | expect(count.value).toBe(1)
24 | expect(num).toBe(1)
25 | expect(callEffectCount).toBe(1)
26 |
27 | count.value = 2
28 | expect(count.value).toBe(2)
29 | expect(num).toBe(2)
30 | expect(callEffectCount).toBe(2)
31 |
32 | count.value = 2
33 | expect(num).toBe(2)
34 | expect(callEffectCount).toBe(2)
35 | })
36 |
37 | it('ref should be reactive obj', () => {
38 | const count = ref({ foo: 1 })
39 | let num;
40 | let callEffectCount = 0
41 |
42 | effect(() => {
43 | callEffectCount++
44 | num = count.value.foo
45 | })
46 |
47 | expect(num).toBe(1)
48 | expect(callEffectCount).toBe(1)
49 | count.value.foo = 2
50 | expect(num).toBe(2)
51 |
52 | })
53 |
54 | it.skip('class test', () => {
55 | class rf {
56 | _value: any
57 | name: any
58 | constructor(name, value) {
59 | this.name = name
60 | this._value = value
61 | }
62 | public set value(v: string) {
63 | this.name = v;
64 | }
65 | public get value(): string {
66 | return this.name
67 | }
68 | }
69 | const person = new rf('jack', 1)
70 | expect(person._value).toBe(1)
71 | expect(person.name).toBe('jack')
72 | expect(person.value).toBe('jack')
73 | person.name = 'ben'
74 | expect(person.name).toBe('ben')
75 | expect(person.value).toBe('ben')
76 | })
77 |
78 | it('isRef present', () => {
79 | const refNum = ref(1)
80 | const num = 1
81 | const observed = reactive({
82 | foo: 1
83 | })
84 | const refObj = ref({
85 | bar: 1
86 | })
87 | expect(isRef(refNum)).toBe(true);
88 | expect(isRef(num)).toBe(false);
89 | expect(isRef(observed)).toBe(false);
90 | expect(isRef(refObj)).toBe(true);
91 | expect(isRef(refObj.value)).toBe(false)
92 | expect(isReactive(refObj.value)).toBe(true)
93 | });
94 |
95 | it('unRef present', () => {
96 | const refNum = ref(1)
97 | expect(refNum.value).toBe(1);
98 | expect(unRef(1)).toBe(1);
99 | expect(unRef(refNum)).toBe(1);
100 | });
101 |
102 | it('proxyRef present', () => {
103 | // vue3-data step() { return { ref } }
104 | // vue3-template 不用.value
105 | // 是因为使用了proxyRef
106 | const user = {
107 | age: ref(10),
108 | name: 'Ben'
109 | }
110 |
111 | const proxyRefUser = proxyRef(user)
112 |
113 | expect(user.age.value).toBe(10);
114 | expect(proxyRefUser.age).toBe(10);
115 | expect(proxyRefUser.name).toBe('Ben');
116 |
117 | proxyRefUser.age = 1
118 | expect(proxyRefUser.age).toBe(1);
119 | expect(user.age.value).toBe(1);
120 |
121 | proxyRefUser.age = ref(2)
122 | expect(proxyRefUser.age).toBe(2);
123 | expect(user.age.value).toBe(2)
124 |
125 | user.name = '1'
126 | expect(proxyRefUser.name).toBe('1');
127 |
128 | // const customer = {
129 | // age: 1
130 | // }
131 | // const proxyCustomer = proxyRef(customer)
132 | // proxyCustomer.name = 1
133 | // expect(proxyCustomer.name).toBe(1);
134 | // expect(user.name).toBe(1);
135 |
136 |
137 |
138 | });
139 |
140 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive, isReadonly, shallowReactive } from '../reactive'
2 |
3 | describe('shallowReactive', () => {
4 | it('shallow readonly', () => {
5 | const original = { name: 'ben', address: { room: '202' } }
6 | const observed = shallowReactive(original)
7 |
8 | expect(observed).not.toBe(original)
9 | expect(observed.name).toBe('ben')
10 | expect(isReadonly(observed.name)).toBe(false)
11 | expect(isReactive(observed.address)).toBe(false)
12 | expect(isReadonly(observed.address)).toBe(false)
13 |
14 | // observed.name = 'boli'
15 | // expect(observed.name).toBe('boli')
16 |
17 | })
18 |
19 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReactive, isReadonly, shallowReadonly } from '../reactive'
2 |
3 | describe('shallowReadonly', () => {
4 | it('shallow readonly', () => {
5 | const original = { name: 'ben', address: { room: '202' } }
6 | const observed = shallowReadonly(original)
7 |
8 | expect(observed).not.toBe(original)
9 | expect(observed.name).toBe('ben')
10 | expect(isReadonly(observed)).toBe(true)
11 | expect(isReadonly(observed.name)).toBe(false)
12 | expect(isReactive(observed.address)).toBe(false)
13 |
14 | // console.warn = jest.fn()
15 | // expect(console.warn).toHaveBeenCalled()
16 | // expect(observed.name).toBe('ben')
17 |
18 | })
19 |
20 | })
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | // 场景1: 父组件 存, 子组件 取 ==> 存到provides里面
4 | // 场景2: 父组件 存, 子孙组件 取 ==> provides指定父类的provides
5 | // 场景3: 当在中间层组件中使用了 provide
6 |
7 | export const provide = (key, value) => {
8 |
9 | // 存值
10 |
11 | const currentInstance: any = getCurrentInstance()
12 |
13 | if (currentInstance) {
14 | let { provides } = currentInstance
15 |
16 | // init
17 | if (provides === currentInstance.parent.provides) {
18 | // 原型指向父类的provides, 创建一个新的
19 | provides = currentInstance.provides = Object.create(currentInstance.provides)
20 | }
21 |
22 | provides[key] = value
23 | }
24 | };
25 |
26 | export const inject = (key) => {
27 |
28 | // 取值
29 |
30 | const currentInstance: any = getCurrentInstance()
31 |
32 | if (currentInstance) {
33 | // 父类里面拿
34 | const { parent } = currentInstance
35 | const { provides } = parent
36 |
37 | return provides[key]
38 | }
39 | };
40 |
41 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRef } from "../reactivity";
2 | import { shallowReadonly } from "../reactivity/reactive";
3 | import { emit } from "./componentEmits";
4 | import { initProps } from "./componentProps";
5 | import { publicInstanceProxyHandlers } from "./componentPublicInstance";
6 | import { initSlots } from "./componentSlots";
7 |
8 | let instance = null
9 | export const createComponentInstance = (vnode, parent) => {
10 |
11 | const componentInstance = {
12 | vnode,
13 | type: vnode.type,
14 | setupState: {},
15 | props: {},
16 | slots: {},
17 | isMounted: false,
18 | subTree: {},
19 | component: null,
20 | update: null,
21 | next: null, // 存储新的虚拟节点
22 | provides: parent ? parent.provides : {},
23 | parent,
24 | emit: () => { }
25 | }
26 |
27 | // 初始化emit
28 | // 使用bind 返回一个函数
29 | componentInstance.emit = emit.bind(null, componentInstance) as any
30 |
31 | return componentInstance
32 | };
33 |
34 | export const setupComponent = (instance) => {
35 | // TODO 后续处理 -> 只处理了普通的
36 | initProps(instance, instance.vnode.props)
37 | initSlots(instance, instance.vnode.children)
38 |
39 | // 处理初始化setup
40 | setupStatefulComponent(instance)
41 |
42 | };
43 |
44 | export function setupStatefulComponent(instance: any) {
45 |
46 | const component = instance.type
47 |
48 | // 组件代理对象 this.xx
49 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandlers)
50 |
51 | const { setup } = component
52 |
53 | if (setup) {
54 | // 存储instance,用于getCurrentInstance
55 | setCurrentInstance(instance)
56 | const setupResult = setup(shallowReadonly(instance.props), {
57 | emit: instance.emit
58 | })
59 | setCurrentInstance(null)
60 | handleSetupResult(instance, setupResult)
61 | }
62 |
63 |
64 | }
65 |
66 | function handleSetupResult(instance, setupResult) {
67 | // setupResult 返回的值类型可能是 function | object
68 | // TODO Function先不处理, 先处理Object
69 | if (typeof setupResult === 'object') {
70 | // 通过proxyRef来解决Ref获取值
71 | instance.setupState = proxyRef(setupResult)
72 | }
73 |
74 | // 保证render有值
75 | finishComponentSetup(instance)
76 |
77 | }
78 |
79 | function finishComponentSetup(instance: any) {
80 | const component = instance.type
81 | // 暂时默认都有render
82 | instance.render = component.render
83 | }
84 |
85 | export const getCurrentInstance = () => {
86 | return instance
87 | };
88 |
89 | function setCurrentInstance(_instance) {
90 | instance = _instance
91 | };
92 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmits.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "../shared";
2 |
3 | export const emit = (instance, event, ...args) => {
4 |
5 | const { props } = instance
6 |
7 | // 通过自定义事件名称 获取 事件
8 | const handler = props[toHandlerKey(camelize(event))]
9 |
10 | handler && handler(...args)
11 | };
12 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 |
2 | export const initProps = (instance, props) => {
3 | instance.props = props
4 | };
5 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { isOwn } from "../shared";
2 |
3 | const publicPropertiesMap = {
4 | $el: (instance) => instance.vnode.el,
5 | $slots: (instance) => instance.slots,
6 | $props: (instance) => instance.props
7 | }
8 |
9 | export const publicInstanceProxyHandlers = {
10 | get({ _: instance }, key) {
11 | const { setupState, props } = instance
12 |
13 | if (isOwn(setupState, key)) {
14 | return setupState[key]
15 | } else if (isOwn(props, key)) {
16 | return props[key]
17 | }
18 |
19 | const publicProperty = publicPropertiesMap[key]
20 | if (publicProperty) {
21 | return publicProperty(instance)
22 | }
23 |
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/shapeFlags";
2 |
3 | export const initSlots = (instance, children) => {
4 | // 1. 单个
5 | // 2. array
6 | // 3. key->value object
7 | // instance.slots = Array.isArray(children) ? children : [children]
8 |
9 | const { vnode } = instance
10 | if (vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN) {
11 | normalizeObject(children, instance);
12 | }
13 | };
14 |
15 | function normalizeObject(children: any, instance: any) {
16 | const solts = {};
17 | for (const key in children) {
18 | const val = children[key];
19 | solts[key] = (props) => normalizeSlotValue(val(props));
20 | }
21 | instance.slots = solts;
22 | }
23 |
24 | function normalizeSlotValue(val) {
25 | return Array.isArray(val) ? val : [val]
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdate.ts:
--------------------------------------------------------------------------------
1 |
2 | export const shouldUpdateComponent = (n1, n2) => {
3 | const { props: prevProps } = n1
4 | const { props: nextProps } = n2
5 |
6 | for (const key in prevProps) {
7 | if(prevProps[key] !== nextProps[key]) {
8 | return true
9 | }
10 | }
11 |
12 | return false
13 |
14 | };
15 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export const createAppAPI = (render) => {
4 | return function (rootComponent) {
5 | return {
6 | mount(rootContainer) {
7 | // 将component转换成vnode
8 | // 后续所有操作都会基于vnode 来进行操作
9 | const vnode = createVNode(rootComponent)
10 | render(vnode, rootContainer)
11 | }
12 | }
13 | };
14 | }
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export const h = (type, props?, children?) => {
4 | return createVNode(type, props, children)
5 | };
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlot.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export const renderSlot = ($slots, name, props) => {
4 | // return vnode
5 | // return createVNode("div", {}, $slots)
6 | const slot = $slots[name]
7 |
8 | if (slot) {
9 | if (typeof slot === 'function') {
10 | return createVNode(Fragment, {}, slot(props))
11 | }
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 |
2 | export { h } from './h';
3 | export { renderSlot } from "./helpers/renderSlot";
4 | export { createTextVNode } from "./vnode";
5 | export { getCurrentInstance } from "./component";
6 | export { provide, inject } from "./apiInject";
7 | export { createRenderer } from './renderer';
8 | export { nextTick } from './scheduler';
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect";
2 | import { ShapeFlags } from "../shared/shapeFlags";
3 | import { createComponentInstance, setupComponent } from "./component";
4 | import { shouldUpdateComponent } from "./componentUpdate";
5 | import { createAppAPI } from "./createApp";
6 | import { queueJob } from "./scheduler";
7 | import { Fragment, Text } from "./vnode";
8 |
9 | export const createRenderer = (options: any) => {
10 |
11 | const {
12 | createElement: hostCreateElement,
13 | patchProp: hostPatchProp,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText
17 | } = options
18 |
19 | const render = (n2, container) => {
20 | // 调用patch
21 | patch(null, n2, container, null, null)
22 | };
23 |
24 | const patch = (n1, n2, container, parent, anchor) => {
25 |
26 | // 判断 是不是 element类型
27 | // 如果是Component,type => Object
28 | // 如果是element类型,type => div等标签
29 | const { shapeFlag, type } = n2
30 |
31 | switch (type) {
32 | case Fragment: {
33 | processFragment(n1, n2, container, parent, anchor)
34 | break;
35 | }
36 | case Text: {
37 | processText(n1, n2, container)
38 | break;
39 | }
40 | default: {
41 | if (shapeFlag & ShapeFlags.ELEMENT) {
42 | // 处理Element
43 | processElement(n1, n2, container, parent, anchor)
44 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
45 | // 处理组件
46 | processComponent(n1, n2, container, parent, anchor)
47 | }
48 | }
49 | }
50 | };
51 |
52 | const processComponent = (n1, n2, container, parent, anchor) => {
53 | // 挂载组件
54 | // 如果n1有值,证明是需要进行更新
55 | if (!n1) {
56 | mountComponent(n2, container, parent, anchor)
57 | } else {
58 | updateComponent(n1, n2)
59 | }
60 | };
61 |
62 | const updateComponent = (n1, n2) => {
63 | // 判断n1, n2的props是否相等
64 | // 相等才会进行组件更新
65 | const component = (n2.component = n1.component)
66 | if (shouldUpdateComponent(n1, n2)) {
67 | console.log('[Function:updateComponent]:组件更新n1', n1)
68 | console.log('[Function:updateComponent]:组件更新n2', n2)
69 | component.next = n2
70 | component.update()
71 | } else {
72 | n2.el = n1.el
73 | n2.vnode = n2
74 | }
75 | }
76 |
77 | const processFragment = (n1, n2, container, parent, anchor) => {
78 | mountChildren(n2.children, container, parent, anchor)
79 | };
80 |
81 | const processText = (n1, n2, container) => {
82 | const { children } = n2
83 | const textNode = (n2.el = document.createTextNode(children))
84 | container.append(textNode)
85 | }
86 |
87 | function processElement(n1, n2, container, parent, anchor) {
88 | if (!n1) {
89 | mountElement(n2, container, parent, anchor)
90 | } else {
91 | patchElement(n1, n2, container, parent, anchor)
92 | }
93 | }
94 |
95 | function mountElement(vnode, container, parent, anchor) {
96 | const { type, props, children, shapeFlag } = vnode
97 |
98 | // 创建对应的el
99 | // vnode -> element -> div
100 | const el = (vnode.el = hostCreateElement(type))
101 |
102 | // 处理props => 普通属性 和 注册事件
103 | for (const key in props) {
104 | hostPatchProp(el, key, null, props[key])
105 | }
106 |
107 | // 处理children --> string, Array
108 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
109 | el.innerText = children
110 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
111 | mountChildren(children, el, parent, anchor)
112 | }
113 |
114 | // 插入
115 | hostInsert(el, container, anchor)
116 | }
117 |
118 | function patchElement(n1, n2, container, parent, anchor) {
119 | console.log("n1", n1)
120 | console.log("n2", n2)
121 |
122 | const oldProps = n1.props
123 | const newProps = n2.props
124 |
125 | // 需要把 el 挂载到新的 vnode
126 | // n1: 旧的 => n2: 新的
127 | const el = (n2.el = n1.el);
128 |
129 | // 对比props
130 | patchProps(el, oldProps, newProps)
131 |
132 | // 对比children
133 | patchChildren(n1, n2, el, parent, anchor)
134 |
135 | }
136 |
137 | function patchProps(el, oldProps, newProps) {
138 | console.log('oldProps', oldProps)
139 | console.log('newProps', newProps)
140 |
141 | // 当不相同的时候才会去判断
142 | if (oldProps !== newProps) {
143 | // 遍历新值
144 | for (const key in newProps) {
145 | const prevProp = oldProps[key];
146 | const nextProp = newProps[key];
147 | if (prevProp !== newProps) {
148 | // 对比属性值,如果不相同则需要进行更新
149 | hostPatchProp(el, key, prevProp, nextProp)
150 | }
151 | }
152 |
153 | // 遍历旧值
154 | for (const key in oldProps) {
155 | const prevProp = oldProps[key]
156 | if (!(key in newProps)) {
157 | // 判断新props没有的,没有的就去掉null
158 | hostPatchProp(el, key, prevProp, null)
159 | }
160 | }
161 | }
162 | }
163 |
164 | function patchChildren(n1, n2, container, parent, anchor) {
165 | // 获取shapeFlags 来判断类型
166 | const prevShapeFlag = n1.shapeFlag
167 | const shapeFlag = n2.shapeFlag
168 |
169 | // 新节点是 Text 时候
170 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
171 | // 老节点是 Children
172 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
173 | // 清空老的 Array子节点
174 | unmountChildren(n1.children)
175 | }
176 | // 新老节点都不相同, 新节点需要是Text
177 | if (n1.children !== n2.children) {
178 | // 插入新的 Text节点
179 | hostSetElementText(container, n2.children)
180 | }
181 | } else {
182 | // 新节点是 Children 时候
183 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
184 | // 老节点是 Text
185 | hostSetElementText(container, "")
186 | mountChildren(n2.children, container, parent, anchor)
187 | } else {
188 | // 老节点是 Children
189 | patchKeyedChildren(n1.children, n2.children, container, parent, anchor)
190 | }
191 | }
192 | }
193 |
194 | function patchKeyedChildren(c1, c2, container, parent, anchor) {
195 | let i = 0;
196 | let e1 = c1.length - 1;
197 | let e2 = c2.length - 1;
198 |
199 | function isSomeVNodeType(n1, n2) {
200 | return n1.type === n2.type && n1.key === n2.key
201 | }
202 |
203 | // 1. 左侧
204 | while (i <= e1 && i <= e2) {
205 | const n1 = c1[i]
206 | const n2 = c2[i]
207 |
208 | if (isSomeVNodeType(n1, n2)) {
209 | patch(n1, n2, container, parent, anchor)
210 | } else {
211 | break;
212 | }
213 | i++
214 | }
215 | console.log('左侧之后i', i)
216 |
217 | // 2. 右侧
218 | while (e1 >= i && e2 >= i) {
219 | const n1 = c1[e1]
220 | const n2 = c2[e2]
221 | if (isSomeVNodeType(n1, n2)) {
222 | patch(n1, n2, container, parent, anchor)
223 | } else {
224 | break;
225 | }
226 | e1--;
227 | e2--;
228 | }
229 | console.log('右侧之后e1', e1)
230 | console.log('右侧之后e2', e2)
231 |
232 | // 3.新的比老的长, 新的进行创建
233 | // i > 旧的 证明旧的已经diff完了
234 | // 1 <= 新的 属于diff后需要新增的范围
235 |
236 | // 左侧 -> 新增
237 | // a b
238 | // a b c d
239 | // i = 2 > e1 = 2
240 | // i = 2 <= e2 = 3
241 |
242 | // 左侧 -> 删除
243 | // a b c
244 | // a b
245 | // i = 2 > e1 = 3
246 | // i = 2 <= e2 = 2
247 |
248 | // 右侧
249 | // a b
250 | // c d a b
251 | // i = 0 > e1 = -1
252 | // i = 0 <= e2 = 1
253 | if (i > e1) {
254 | if (i <= e2) {
255 | const nextProp = e2 + 1
256 | const anchor = nextProp < c2.length ? c2[nextProp].el : null
257 | while (i <= e2) {
258 | patch(null, c2[i], container, parent, anchor)
259 | i++
260 | }
261 | }
262 | } else if (i > e2) {
263 | // 4. i值大于新节点长度e2, 进行remove
264 | while (i <= e1) {
265 | hostRemove(c1[i].el)
266 | i++
267 | }
268 | } else {
269 |
270 | // 5. 中间进行对比
271 |
272 | let s1 = i;
273 | let s2 = i;
274 | let moved = false;
275 | let newIndexForMax = 0;
276 |
277 | // 新节点需要对比的节点数
278 | let toBePatched = e2 - s2 + 1;
279 | let patched = 0;
280 |
281 | // 新节点获取key值
282 | const keyToNewIndexMap = new Map()
283 | for (let j = s2; j <= e2; j++) {
284 | const nextChild = c2[j]
285 | keyToNewIndexMap.set(nextChild.key, j)
286 | }
287 |
288 | // 进行移动
289 | // 定一个长度为 新节点需要对比的节点数量
290 | const newIndexToOldIndexMap = new Array(toBePatched)
291 | for (let i = 0; i < toBePatched; i++) {
292 | newIndexToOldIndexMap[i] = 0
293 | }
294 |
295 | // 旧节点进行循环
296 | for (let index = s1; index <= e1; index++) {
297 |
298 | const pervChild = c1[index]
299 | const pervKey = pervChild.key
300 | let nextIndex
301 |
302 | // 旧节点长度比旧节点的要长 且已经不需要对比了 直接删除
303 | if (patched >= toBePatched) {
304 | hostRemove(pervChild.el)
305 | continue
306 | }
307 |
308 | if (pervKey !== null) {
309 | // 获取新节点的对应的keyindex
310 | nextIndex = keyToNewIndexMap.get(pervKey)
311 | } else {
312 | for (let j = s2; j <= e2; j++) {
313 | if (isSomeVNodeType(pervChild, c2[j])) {
314 | nextIndex = j
315 | break
316 | }
317 | }
318 | }
319 |
320 | if (nextIndex !== undefined) {
321 |
322 | // 判断是否需要移动
323 | if (nextIndex >= newIndexForMax) {
324 | newIndexForMax = nextIndex
325 | } else {
326 | moved = true
327 | }
328 |
329 | // +1 的原因是 在使用最长递增子序列算法时候,0是用于判断
330 | newIndexToOldIndexMap[nextIndex - s2] = index + 1
331 | patch(pervChild, c2[nextIndex], container, parent, null)
332 | patched++
333 | } else {
334 | // 旧的节点 不存在新的里面,需要删除
335 | hostRemove(pervChild.el)
336 | }
337 | }
338 |
339 | // 得到了印射表newIndexToOldIndexMap后,进行移动处理
340 | const newIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []
341 | let j = newIndexSequence.length - 1; // 索引 -1
342 |
343 | // 为了稳定的锚点,需要倒序
344 | for (let i = toBePatched - 1; i >= 0; i--) {
345 |
346 | const nextIndex = i + s2
347 | const nextChild = c2[nextIndex]
348 | const nextAnchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null
349 |
350 | if (newIndexToOldIndexMap[i] === 0) {
351 | // 当印射表里面等于 0 相当于 没有新的节点 没有需要移动的而且是不存在的,这时候需要添加一个节点
352 | patch(null, nextChild, container, parent, nextAnchor)
353 | } else if (moved) {
354 | if (i !== newIndexSequence[j]) {
355 | console.log("需要移动位置")
356 | hostInsert(nextChild, container, nextAnchor)
357 | } else {
358 | j--
359 | }
360 | }
361 |
362 | }
363 |
364 | }
365 | }
366 |
367 | function unmountChildren(children) {
368 | for (const key in children) {
369 | hostRemove(children[key].el)
370 | }
371 | }
372 |
373 | function mountChildren(vnodes = [], container, parent, anchor) {
374 | vnodes.forEach(element => {
375 | patch(null, element, container, parent, anchor)
376 | });
377 | }
378 |
379 | const mountComponent = (n2, container, parent, anchor) => {
380 | console.log('parent', parent)
381 | // 创建组件实例, 收集数据
382 | const instance = (n2.component = createComponentInstance(n2, parent))
383 |
384 | // 初始化 props, slots, setup
385 | setupComponent(instance)
386 |
387 | // 进行拆箱
388 | setupRenderEffect(instance, n2, container, anchor)
389 |
390 | };
391 |
392 | const setupRenderEffect = (instance, n2, container, anchor) => {
393 | // 依赖收集
394 | instance.update = effect(() => {
395 | const { isMounted } = instance
396 | if (!isMounted) {
397 | // 是否初始化
398 | // 指定代理对象 this.xxx
399 | const { proxy } = instance
400 |
401 | const subTree = instance.render.call(proxy)
402 |
403 | // subTree 虚拟节点树 n2 tree
404 | // n2 -> patch
405 | // n2 -> element -> mountElement
406 | patch(null, subTree, container, instance, anchor)
407 |
408 | // vnode节点存起来
409 | instance.subTree = subTree
410 |
411 | // 完成了所有的patch后
412 | n2.el = subTree.el
413 |
414 | // 初始化结束后,转换状态
415 | instance.isMounted = true
416 |
417 | } else {
418 | console.log('update')
419 |
420 | const { proxy, next, vnode } = instance
421 |
422 | // 更新props
423 | // 需要获取vnode, next-> 下次更新虚拟节点,vnode之前的虚拟节点
424 | if (next) {
425 | next.el = vnode.el
426 | updateComponentPreRender(instance, next)
427 | }
428 |
429 | // 当前(新)节点
430 | const subTree = instance.render.call(proxy)
431 |
432 | // 旧节点
433 | const prevSubTree = instance.subTree
434 |
435 | // vnode节点存起来
436 | instance.subTree = subTree
437 |
438 | // 新节点 和 旧节点 进行对比
439 | patch(prevSubTree, subTree, container, instance, anchor)
440 |
441 | }
442 |
443 | }, {
444 | scheduler: () => {
445 | console.log('update - scheduler')
446 | // 将effect任务 加入到微任务里,再异步执行微任务
447 | queueJob(instance.update)
448 | }
449 | })
450 | };
451 |
452 | const updateComponentPreRender = (instance, nextVNode) => {
453 | instance.vnode = nextVNode
454 | instance.props = nextVNode.props
455 | instance.next = null
456 | }
457 |
458 | return {
459 | createApp: createAppAPI(render)
460 | }
461 |
462 | }
463 |
464 | /**
465 | * 最长子序列算法LIS
466 | * @param arr
467 | * @returns
468 | */
469 | function getSequence(arr: number[]): number[] {
470 | const p = arr.slice();
471 | const result = [0];
472 | let i, j, u, v, c;
473 | const len = arr.length;
474 | for (i = 0; i < len; i++) {
475 | const arrI = arr[i];
476 | if (arrI !== 0) {
477 | j = result[result.length - 1];
478 | if (arr[j] < arrI) {
479 | p[i] = j;
480 | result.push(i);
481 | continue;
482 | }
483 | u = 0;
484 | v = result.length - 1;
485 | while (u < v) {
486 | c = (u + v) >> 1;
487 | if (arr[result[c]] < arrI) {
488 | u = c + 1;
489 | } else {
490 | v = c;
491 | }
492 | }
493 | if (arrI < arr[result[u]]) {
494 | if (u > 0) {
495 | p[i] = result[u - 1];
496 | }
497 | result[u] = i;
498 | }
499 | }
500 | }
501 | u = result.length;
502 | v = result[u - 1];
503 | while (u-- > 0) {
504 | result[u] = v;
505 | v = p[v];
506 | }
507 | return result;
508 | }
509 |
510 |
511 |
512 |
513 |
514 |
515 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 |
2 | const queue: any[] = []
3 | const promise = Promise.resolve()
4 |
5 | // 解决不要多次创建Promise
6 | let isExceute = false
7 |
8 | export const nextTick = (fn) => {
9 | return fn ? promise.then(fn) : promise
10 | }
11 |
12 | export const queueJob = (job) => {
13 | // 判断一下是否存在队列,如果存在就不加了
14 | if (!queue.includes(job)) {
15 | queue.push(job)
16 | }
17 | // 执行微任务
18 | excuteQueue()
19 | };
20 |
21 | function excuteQueue() {
22 | // MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
23 | if (isExceute) return
24 | isExceute = true
25 | nextTick(() => {
26 | isExceute = false
27 | let job;
28 | while ((job = queue.shift())) {
29 | job & job()
30 | }
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { isObject, isString } from "../shared";
2 | import { ShapeFlags } from "../shared/shapeFlags";
3 |
4 | export const Fragment = Symbol('Fragment')
5 | export const Text = Symbol('Text')
6 |
7 | export const createVNode = (type, props?, children?) => {
8 |
9 | const vnode = {
10 | type,
11 | props,
12 | shapeFlag: getShapeFlags(type),
13 | key: props && props.key,
14 | children,
15 | el: null
16 | }
17 |
18 | // 使用二进制来进行判断
19 | if (isString(children)) {
20 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN
21 | } else {
22 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN
23 | }
24 |
25 | // 初始化slot
26 | // 组件 + children => object
27 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
28 | if (isObject(children)) {
29 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN
30 | }
31 | }
32 |
33 | return vnode
34 |
35 | };
36 |
37 | export const createTextVNode = (str: string) => {
38 | return createVNode(Text, {}, str)
39 | };
40 |
41 |
42 | function getShapeFlags(type) {
43 | return isString(type) ? ShapeFlags.ELEMENT : ShapeFlags.STATEFUL_COMPONENT;
44 | }
45 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { createRenderer } from '../runtime-core/index.js'
3 |
4 | export const createElement = (type) => {
5 | return document.createElement(type)
6 | };
7 |
8 | export const patchProp = (el, key, preValue, nextValue) => {
9 | const isOn = (key: string) => /^on[A-Z]/.test(key)
10 | if (isOn(key)) {
11 | const event = key.slice(2).toLowerCase()
12 | el.addEventListener(event, nextValue)
13 | } else {
14 | if (nextValue === null || nextValue === undefined) {
15 | // 如果是 null 或者 undefined,直接remove
16 | el.removeAttribute(key)
17 | } else {
18 | // 正常设置
19 | el.setAttribute(key, nextValue)
20 | }
21 | }
22 | };
23 |
24 | export const setElementText = (el, text) => {
25 | el.textContent = text
26 | };
27 |
28 | export const remove = (child) => {
29 | const parent = child.parentNode
30 | if (parent) {
31 | parent.removeChild(child)
32 | }
33 | }
34 |
35 | export const insert = (child, parent, anchor) => {
36 | parent.insertBefore(child, anchor)
37 | };
38 |
39 | const renderer: any = createRenderer({
40 | createElement,
41 | patchProp,
42 | insert,
43 | remove,
44 | setElementText
45 | })
46 |
47 | export function createApp (...args) {
48 | return renderer.createApp(...args)
49 | }
50 |
51 | export * from '../runtime-core'
52 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 判断是否是对象
4 | * @param target
5 | * @returns
6 | */
7 | export const isObject = (target) => {
8 | return target !== null && typeof target === 'object'
9 | };
10 |
11 | export const extend = Object.assign
12 |
13 | export const hasChanged = (oldValue, newValue) => {
14 | return !Object.is(oldValue, newValue)
15 | }
16 |
17 | export const isString = (val) => typeof val === 'string'
18 |
19 | export const isOwn = (val: any, key: string) => Object.prototype.hasOwnProperty.call(val, key)
20 |
21 | // add -> Add
22 | export const capitalize = (str: string) => {
23 | return str.charAt(0).toUpperCase() + str.slice(1)
24 | }
25 |
26 | // add -> Add -> onAdd
27 | export const toHandlerKey = (str: string) => {
28 | return str ? "on" + capitalize(str) : ""
29 | }
30 |
31 | // add-count -> addCount -> AddCount -> onAddCount
32 | // 自定义事件是驼峰写法
33 | export const camelize = (str: string) => {
34 | return str.replace(/-(\w)/g, (match, c: string) => {
35 | return c ? c.toUpperCase() : ""
36 | })
37 | }
--------------------------------------------------------------------------------
/src/shared/shapeFlags.ts:
--------------------------------------------------------------------------------
1 |
2 | export const enum ShapeFlags {
3 | ELEMENT = 1,
4 | STATEFUL_COMPONENT = 1 << 1,
5 | TEXT_CHILDREN = 1 << 2,
6 | ARRAY_CHILDREN = 1 << 3,
7 | SLOT_CHILDREN = 1 << 4
8 | };
9 |
10 | // 二进制运算符
11 | // 1 --> 0001 --> 1
12 | // 1 >> 1 --> 0010 --> 2
13 | // 1 >> 2 --> 0100 --> 4
14 | // 1 >> 3 --> 1000 --> 8
15 |
16 | // 修改
17 | // 0001 | 1000 ==> 1001
18 | // 0001 | 0100 ==> 0101
19 |
20 | // 查找
21 | // 1001 & 0001 ==> 0001
--------------------------------------------------------------------------------
/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", "es2016"], /* 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 |
--------------------------------------------------------------------------------