├── .gitignore
├── README.MD
├── babel.config.js
├── example
├── apiInject
│ ├── App.js
│ └── index.html
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── createApp
│ ├── App.js
│ └── index.html
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── customRenderer
│ ├── App.js
│ ├── index.html
│ ├── main.js
│ └── pixi.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── index.html
│ └── main.js
├── package.json
├── rollup.config.js
├── src
├── index.ts
├── resctivite
│ ├── baseHandler.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── render.ts
│ ├── vnode.ts
│ └── vue.dt.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── ShapeFlages.ts
│ └── shared.ts
├── tsconfig.json
└── vueSetupFlow
├── INIT_VUE_FLOW.MD
├── 实现Fragment、Text.MD
├── 实现组件的emit.MD
├── 实现自定义渲染器.MD
├── 更新组件的渲染流程.MD
├── 注册事件.MD
├── 组件代理对象.MD
└── 组件插槽.MD
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | lib
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | 配置开发环境
2 | - 1 yarn init 初始化环境
3 | - 2 yarn add typescript 添加ts
4 | - 3 npx tsc --init 初始化ts配置文件
5 | - 4 yarn add jest @types/jest --dev 添加jest测试环境 识别ts文件
6 | - 5 yarn add --dev babel-jest @babel/core @babel/preset-env
7 | - yarn add --dev @babel/preset-typescript 添加babel.config.js文件配置
8 |
9 | - [✓] 数据响应式功能已实现
10 | - [x] 虚拟节点转dom元素
11 | - [x] 虚拟节点diff 计算差异化更新
12 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}],
4 | '@babel/preset-typescript',
5 | ],
6 | };
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | // 组件 provide 和 inject 功能
2 | import { h, provide, inject } from "../../lib/guide-mini-vue.esm.js";
3 |
4 | const Provider = {
5 | name: "Provider",
6 | setup() {
7 | provide("foo", "fooVal");
8 | provide("bar", "barVal");
9 | },
10 | render() {
11 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]);
12 | },
13 | };
14 |
15 | const ProviderTwo = {
16 | name: "ProviderTwo",
17 | setup() {
18 | provide("foo", "fooTwo");
19 | const foo = inject("foo");
20 |
21 | return {
22 | foo,
23 | };
24 | },
25 | render() {
26 | return h("div", {}, [
27 | h("p", {}, `ProviderTwo foo:${this.foo}`),
28 | h(Consumer),
29 | ]);
30 | },
31 | };
32 |
33 | const Consumer = {
34 | name: "Consumer",
35 | setup() {
36 | const foo = inject("foo");
37 | const bar = inject("bar");
38 | // const baz = inject("baz", "bazDefault");
39 | const baz = inject("baz", () => "bazDefault");
40 | return {
41 | foo,
42 | bar,
43 | baz,
44 | };
45 | },
46 |
47 | render() {
48 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`);
49 | },
50 | };
51 |
52 |
53 |
54 | export default {
55 | name: "App",
56 | setup() {},
57 | render() {
58 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
59 | },
60 | };
61 |
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots} from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | const foo = {
5 | name: "foo",
6 | render() {
7 | window.slots = this.$slots
8 | const p = h('p', {}, 'P')
9 | // 传入单个渲染元素
10 | // return h('div', {}, [p, this.$slots])
11 |
12 | // 传入数组类型子元素
13 | // 因为patch时 只会渲染虚拟节点 并不会处理数组类型的数据 可以使用h函数包裹创建一个新的节点
14 | // return h('div', {}, [p, h('div', {}, this.$slots)])
15 |
16 | // 使用renderSlots辅助函数
17 | // return h('div', {}, [p, renderSlots(this.$slots, 'header')])
18 |
19 | // 使用具名插槽
20 | // return h('div', {}, [renderSlots(this.$slots, 'navigator'), renderSlots(this.$slots, 'header'), p, renderSlots(this.$slots, 'footer')])
21 |
22 | // 作用域插槽
23 | const age = 40
24 | return h('div', {}, [renderSlots(this.$slots, 'navigator', { age }), renderSlots(this.$slots, 'header'), p, renderSlots(this.$slots, 'footer')])
25 | },
26 | setup() {
27 | return {
28 | tag: '454'
29 | }
30 | }
31 | }
32 |
33 | export const App = {
34 | name: "App",
35 | render() {
36 | // 传入单个渲染元素
37 | // return h('div', {}, [ h('div', {class: 'red '}, 'App'), h(foo, {}, h('h1', {}, 'h1') ) ])
38 |
39 | // 传入数组类型子元素
40 | // return h(
41 | // 'div',
42 | // {},
43 | // [
44 | // h(
45 | // 'div',
46 | // { class: 'red ' },
47 | // 'App'
48 | // ),
49 | // h(
50 | // foo
51 | // , {}
52 | // ,
53 | // h(
54 | // 'h1'
55 | // ,
56 | // {},
57 | // 'h1'
58 | // ),
59 | // )
60 | // ]
61 | // )
62 |
63 | // 具名插槽
64 | // const app = h("div", {}, "App");
65 | // return h('div', {}, [app, h(foo, {}, {
66 | // navigator: h('nav', {}, 'nav'),
67 | // header: h('h1',{}, 'header'),
68 | // footer: h('h1',{}, 'footer'),
69 | // })])
70 |
71 | // 作用域插槽
72 | const app = h("div", {}, "App");
73 | return h('div', {}, [app, h(foo, {}, {
74 | navigator: ({ age }) => h('nav', {}, 'nav' + age),
75 | header: () => h('h1',{}, 'header'),
76 | footer: () => h('h1',{}, 'footer'),
77 | })])
78 |
79 | },
80 | setup() {
81 | return {
82 | };
83 | },
84 | };
85 |
86 | // export const App = {
87 | // name: "App",
88 | // render() {
89 | // // return h("div", {}, [
90 | // // h("div", {}, "App"),
91 | // // h(Foo, {
92 | // // onAdd(a, b) {
93 | // // console.log("onAdd", a, b);
94 | // // },
95 | // // onAddFoo() {
96 | // // console.log("onAddFoo");
97 | // // },
98 | // // }),
99 | // // ]);
100 | // return h('div', {}, [h(foo, {}, [ h('h1', {}, 'h1'), h('h2', {}, 'h2') ])])
101 | // },
102 | // setup() {
103 | // return {
104 | // };
105 | // },
106 | // };
107 |
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: 'Foo',
5 | setup(props, { emit }) {
6 | const emitAdd = () => {
7 | console.log("emit add");
8 | emit("add",1,2);
9 | emit("add-foo", emit, props);
10 | };
11 |
12 | return {
13 | emitAdd,
14 | };
15 | },
16 | render() {
17 | const btn = h(
18 | "button",
19 | {
20 | onClick: this.emitAdd,
21 | },
22 | "emitAdd"
23 | );
24 |
25 | const foo = h("p", {}, "foo");
26 | return h("div", {}, [foo, btn, h('ul', {}, [h('li', {}, 'klop')])]);
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVNode} from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | // Fragment 以及 Text
5 | export const App = {
6 | name: "App",
7 | render() {
8 | const app = h("div", {}, "App");
9 |
10 | const header = (props) => {
11 | return [ h('div', {}, 'header' + props.count), createTextVNode('hhaah')]
12 | }
13 | const footer = (props) => h('div', {}, 'footer')
14 | const hj = (prosp) => h('h1', { class: 'red' }, '[]')
15 |
16 | // 单个节点
17 | // const foo = h(Foo, {}, header );
18 | // 支持传入数组
19 | // const foo = h(Foo, {}, [header, footer] )
20 |
21 | // 实现具名插槽
22 | // 作用域插槽
23 | const foo = h(Foo, {}, {
24 | footer: footer,
25 | header: header,
26 | hj: hj
27 | });
28 |
29 | return h("div", {}, [app, foo]);
30 | },
31 |
32 | setup() {
33 | return {};
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | setup() {
5 | return {};
6 | },
7 | render() {
8 | const count = 1
9 | const foo = h("p", {}, "foo");
10 | return h("div", {}, [renderSlots(this.$slots,'hj') ,renderSlots(this.$slots, 'header', { count }), foo, renderSlots(this.$slots, 'footer')]);
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/createApp/App.js:
--------------------------------------------------------------------------------
1 |
2 | import { h, renderSlots } from '../../lib/guide-mini-vue.esm.js'
3 | const foo = {
4 | name: "foo",
5 | setup (props, { emit }) {
6 | const add = () => {
7 | emit('add', 1, 2)
8 | }
9 | return {
10 | th: 'BU',
11 | add
12 | }
13 | },
14 | render () {
15 | // 2 可以通过this.xxx 获取props 数据
16 | return h('button', {
17 | onClick: this.add,
18 | }, 'button')
19 | }
20 | }
21 | const foo1 = {
22 | name: "foo",
23 | setup (props, { emit }) {
24 | const add = () => {
25 | emit('add-foo', 4, 5 )
26 | }
27 | return {
28 | th: 'BU',
29 | add
30 | }
31 | },
32 | render () {
33 | // 2 可以通过this.xxx 获取props 数据
34 | return h('button', {
35 | onClick: this.add,
36 | }, 'foo1')
37 | }
38 | }
39 |
40 |
41 | /** c组件插槽 */
42 | // 1 可以通过this$slots获取到传入的虚拟节点 ==》 ok
43 | // 2 使用一个 renderSolts 函数来创建节点
44 | const APP1 = {
45 | name: 'APP1',
46 | setup () {
47 | },
48 | render () {
49 | const app1 = h('div', {}, 'APP1' )
50 | return h('div', {}, [app1, renderSlots(this.$slots)])
51 | }
52 | }
53 | window.self = null
54 | export default {
55 | name: "App",
56 | setup(props) {
57 | return {
58 | }
59 | },
60 | props: {
61 | mag: '哈哈嘿嘿',
62 | },
63 | render() {
64 | window.self = this
65 | // 测试单个元素
66 | // return h("div",
67 | // { id: '45', class: '' },
68 | // [
69 | // h('div', { id: 'op1' }, '我是div1'),
70 | // h('div', { id: 'op2' }, '我是div2'),
71 | // h(foo, { count: 1 })
72 | // ]
73 | // );
74 |
75 | // 测试组件代理对象
76 | // return h('div', { }, 'hi' + this.msg + this.jk )
77 |
78 |
79 | // 测试 $el
80 | // return h('div', { }, 'hi' + this.msg)
81 |
82 | // return h('div', {}, [h(foo, {
83 | // onAdd: (a,b) => {
84 | // console.log('onAdd',a,b)
85 | // },
86 |
87 | // }), h(foo1, { onAddFoo: (a,b) => {
88 | // console.log('onAddFoo', a,b)
89 | // } }, [ h('div', {}, 'div') ])] )
90 |
91 |
92 |
93 |
94 | return h('div', {}, [h(APP1, {}, h('h1', {}, 'h1'))])
95 |
96 | },
97 | };
98 |
--------------------------------------------------------------------------------
/example/createApp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]);
8 | },
9 | setup() {
10 | console.log(getCurrentInstance())
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name:"Foo",
5 | setup() {
6 | let instance = getCurrentInstance()
7 | return {
8 | text: instance.vnode.shapeflag,
9 | ...instance
10 | }
11 | },
12 | render() {
13 | console.log(this)
14 | return h("div", {}, "foo" + this.text);
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-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("rect", { x: this.x, y: this.y });
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/example/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | console.log(createRenderer)
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | import ArrayToText from "./ArrayToText.js";
4 | import TextToText from "./TextToText.js";
5 | import TextToArray from "./TextToArray.js";
6 | import ArrayToArray from "./ArrayToArray.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {},
11 |
12 | render() {
13 | return h("div", { tId: 1 }, [
14 | h("p", {}, "主页"),
15 | // 老的是 array 新的是 text
16 | // h(ArrayToText),
17 | // 老的是 text 新的是 text
18 | // h(TextToText),
19 | // 老的是 text 新的是 array
20 | // h(TextToArray)
21 | // 老的是 array 新的是 array
22 | h(ArrayToArray),
23 | ]);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "B" }, "B"),
12 | // h("p", { key: "C" }, "C"),
13 | // ];
14 | // const nextChildren = [
15 | // h("p", { key: "A" }, "A"),
16 | // h("p", { key: "B" }, "B"),
17 | // h("p", { key: "D" }, "D"),
18 | // h("p", { key: "E" }, "E"),
19 | // ];
20 |
21 | // 2. 右侧的对比
22 | // a (b c)
23 | // d e (b c)
24 | // const prevChildren = [
25 | // h("p", { key: "A" }, "A"),
26 | // h("p", { key: "B" }, "B"),
27 | // h("p", { key: "C" }, "C"),
28 | // ];
29 | // const nextChildren = [
30 | // h("p", { key: "D" }, "D"),
31 | // h("p", { key: "E" }, "E"),
32 | // h("p", { key: "B" }, "B"),
33 | // h("p", { key: "C" }, "C"),
34 | // ];
35 |
36 | // 3. 新的比老的长
37 | // 创建新的
38 | // 左侧
39 | // (a b)
40 | // (a b) c
41 | // i = 2, e1 = 1, e2 = 2
42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
43 | // const nextChildren = [
44 | // h("p", { key: "A" }, "A"),
45 | // h("p", { key: "B" }, "B"),
46 | // h("p", { key: "C" }, "C"),
47 | // h("p", { key: "D" }, "D"),
48 | // ];
49 |
50 | // 右侧
51 | // (a b)
52 | // c (a b)
53 | // i = 0, e1 = -1, e2 = 0
54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
55 | // const nextChildren = [
56 | // h("p", { key: "C" }, "C"),
57 | // h("p", { key: "A" }, "A"),
58 | // h("p", { key: "B" }, "B"),
59 | // ];
60 |
61 | // 4. 老的比新的长
62 | // 删除老的
63 | // 左侧
64 | // (a b) c
65 | // (a b)
66 | // i = 2, e1 = 2, e2 = 1
67 | // const prevChildren = [
68 | // h("p", { key: "A" }, "A"),
69 | // h("p", { key: "B" }, "B"),
70 | // h("p", { key: "C" }, "C"),
71 | // ];
72 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
73 |
74 | // 右侧
75 | // a (b c)
76 | // (b c)
77 | // i = 0, e1 = 0, e2 = -1
78 |
79 | // const prevChildren = [
80 | // h("p", { key: "A" }, "A"),
81 | // h("p", { key: "B" }, "B"),
82 | // h("p", { key: "C" }, "C"),
83 | // ];
84 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
85 |
86 | // 5. 对比中间的部分
87 | // 删除老的 (在老的里面存在,新的里面不存在)
88 | // 5.1
89 | // a,b,(c,d),f,g
90 | // a,b,(e,c),f,g
91 | // D 节点在新的里面是没有的 - 需要删除掉
92 | // C 节点 props 也发生了变化
93 |
94 | // const prevChildren = [
95 | // h("p", { key: "A" }, "A"),
96 | // h("p", { key: "B" }, "B"),
97 | // h("p", { key: "C", id: "c-prev" }, "C"),
98 | // h("p", { key: "D" }, "D"),
99 | // h("p", { key: "F" }, "F"),
100 | // h("p", { key: "G" }, "G"),
101 | // ];
102 |
103 | // const nextChildren = [
104 | // h("p", { key: "A" }, "A"),
105 | // h("p", { key: "B" }, "B"),
106 | // h("p", { key: "E" }, "E"),
107 | // h("p", { key: "C", id:"c-next" }, "C"),
108 | // h("p", { key: "F" }, "F"),
109 | // h("p", { key: "G" }, "G"),
110 | // ];
111 |
112 | // 5.1.1
113 | // a,b,(c,e,d),f,g
114 | // a,b,(e,c),f,g
115 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
116 | // const prevChildren = [
117 | // h("p", { key: "A" }, "A"),
118 | // h("p", { key: "B" }, "B"),
119 | // h("p", { key: "C", id: "c-prev" }, "C"),
120 | // h("p", { key: "E" }, "E"),
121 | // h("p", { key: "D" }, "D"),
122 | // h("p", { key: "F" }, "F"),
123 | // h("p", { key: "G" }, "G"),
124 | // ];
125 |
126 | // const nextChildren = [
127 | // h("p", { key: "A" }, "A"),
128 | // h("p", { key: "B" }, "B"),
129 | // h("p", { key: "E" }, "E"),
130 | // h("p", { key: "C", id:"c-next" }, "C"),
131 | // h("p", { key: "F" }, "F"),
132 | // h("p", { key: "G" }, "G"),
133 | // ];
134 |
135 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
136 |
137 | // 2.1
138 | // a,b,(c,d,e),f,g
139 | // a,b,(e,c,d),f,g
140 | // 最长子序列: [1,2]
141 |
142 | const prevChildren = [
143 | h("p", { key: "A" }, "A"),
144 | h("p", { key: "B" }, "B"),
145 | h("p", { key: "C" }, "C"),
146 | h("p", { key: "D" }, "D"),
147 | h("p", { key: "E" }, "E"),
148 | h("p", { key: "F" }, "F"),
149 | h("p", { key: "G" }, "G"),
150 | ];
151 |
152 | const nextChildren = [
153 | h("p", { key: "A" }, "A"),
154 | h("p", { key: "B" }, "B"),
155 | h("p", { key: "E" }, "E"),
156 | h("p", { key: "C" }, "C"),
157 | h("p", { key: "D" }, "D"),
158 | h("p", { key: "F" }, "F"),
159 | h("p", { key: "G" }, "G"),
160 | ];
161 |
162 | // 3. 创建新的节点
163 | // a,b,(c,e),f,g
164 | // a,b,(e,c,d),f,g
165 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
166 | // const prevChildren = [
167 | // h("p", { key: "A" }, "A"),
168 | // h("p", { key: "B" }, "B"),
169 | // h("p", { key: "C" }, "C"),
170 | // h("p", { key: "E" }, "E"),
171 | // h("p", { key: "F" }, "F"),
172 | // h("p", { key: "G" }, "G"),
173 | // ];
174 |
175 | // const nextChildren = [
176 | // h("p", { key: "A" }, "A"),
177 | // h("p", { key: "B" }, "B"),
178 | // h("p", { key: "E" }, "E"),
179 | // h("p", { key: "C" }, "C"),
180 | // h("p", { key: "D" }, "D"),
181 | // h("p", { key: "F" }, "F"),
182 | // h("p", { key: "G" }, "G"),
183 | // ];
184 |
185 | // 综合例子
186 | // a,b,(c,d,e,z),f,g
187 | // a,b,(d,c,y,e),f,g
188 |
189 | // const prevChildren = [
190 | // h("p", { key: "A" }, "A"),
191 | // h("p", { key: "B" }, "B"),
192 | // h("p", { key: "C" }, "C"),
193 | // h("p", { key: "D" }, "D"),
194 | // h("p", { key: "E" }, "E"),
195 | // h("p", { key: "Z" }, "Z"),
196 | // h("p", { key: "F" }, "F"),
197 | // h("p", { key: "G" }, "G"),
198 | // ];
199 |
200 | // const nextChildren = [
201 | // h("p", { key: "A" }, "A"),
202 | // h("p", { key: "B" }, "B"),
203 | // h("p", { key: "D" }, "D"),
204 | // h("p", { key: "C" }, "C"),
205 | // h("p", { key: "Y" }, "Y"),
206 | // h("p", { key: "E" }, "E"),
207 | // h("p", { key: "F" }, "F"),
208 | // h("p", { key: "G" }, "G"),
209 | // ];
210 |
211 | // fix c 节点应该是 move 而不是删除之后重新创建的
212 | // const prevChildren = [
213 | // h("p", { key: "A" }, "A"),
214 | // h("p", {}, "C"),
215 | // h("p", { key: "B" }, "B"),
216 | // h("p", { key: "D" }, "D"),
217 | // ];
218 |
219 | // const nextChildren = [
220 | // h("p", { key: "A" }, "A"),
221 | // h("p", { key: "B" }, "B"),
222 | // h("p", {}, "C"),
223 | // h("p", { key: "D" }, "D"),
224 | // ];
225 |
226 | export default {
227 | name: "ArrayToArray",
228 | setup() {
229 | const isChange = ref(false);
230 | window.isChange = isChange;
231 |
232 | return {
233 | isChange,
234 | };
235 | },
236 | render() {
237 | const self = this;
238 |
239 | return self.isChange === true
240 | ? h("div", {}, nextChildren)
241 | : h("div", {}, prevChildren);
242 | },
243 | };
244 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
5 | const nextChildren = "newChildren";
6 | const prevChildren = [
7 | h("div", {}, "A"), h("div", {}, "B"),
8 | h("div", {}, "A"), h("div", {}, "B"),
9 | h("div", {}, "A"), h("div", {}, "B"),
10 | h("div", {}, "A"), h("div", {}, "B"),
11 | h("div", {}, "A"), h("div", {}, "B"),
12 | // h("div", {}, "A"), h("div", {}, "B"),
13 | // h("div", {}, "A"), h("div", {}, "B"),
14 | // h("div", {}, "A"), h("div", {}, "B"),
15 | // h("div", {}, "A"), h("div", {}, "B"),
16 | // h("div", {}, "A"), h("div", {}, "B"),
17 | // h("div", {}, "A"), h("div", {}, "B"),
18 | // h("div", {}, "A"), h("div", {}, "B"),
19 | // h("div", {}, "A"), h("div", {}, "B"),
20 | // h("div", {}, "A"), h("div", {}, "B"),
21 | // h("div", {}, "A"), h("div", {}, "B"),
22 | // h("div", {}, "A"), h("div", {}, "B"),
23 | // h("div", {}, "A"), h("div", {}, "B"),
24 | ];
25 |
26 | export default {
27 | name: "ArrayToText",
28 | setup() {
29 | const isChange = ref(false);
30 | window.isChange = isChange;
31 |
32 | return {
33 | isChange,
34 | };
35 | },
36 | render() {
37 | const self = this;
38 |
39 | return self.isChange === true
40 | ? h("div", {}, nextChildren)
41 | : h("div", {}, prevChildren);
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "TextToArray",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = "newChild";
7 |
8 | export default {
9 | name: "TextToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | setup() {
7 | const count = ref(0);
8 | window.count = count
9 | const onClick = () => {
10 | console.log(count)
11 | count.value++;
12 | };
13 |
14 | const props = ref({
15 | foo: "foo",
16 | bar: "bar",
17 | });
18 | const onChangePropsDemo1 = () => {
19 | props.value.foo = "new-foo";
20 | };
21 |
22 | const onChangePropsDemo2 = () => {
23 | props.value.foo = undefined;
24 | };
25 |
26 | const onChangePropsDemo3 = () => {
27 | props.value = {
28 | foo: "foo",
29 | };
30 | };
31 |
32 | return {
33 | count,
34 | onClick,
35 | onChangePropsDemo1,
36 | onChangePropsDemo2,
37 | onChangePropsDemo3,
38 | props,
39 | };
40 | },
41 | render() {
42 | return h(
43 | "div",
44 | {
45 | id: "root",
46 | ...this.props,
47 | },
48 | [
49 | h("div", {}, "count:" + this.count),
50 | h(
51 | "button",
52 | {
53 | onClick: this.onClick,
54 | },
55 | "click"
56 | ),
57 | h(
58 | "button",
59 | {
60 | onClick: this.onChangePropsDemo1,
61 | },
62 | "changeProps - 值改变了 - 修改"
63 | ),
64 |
65 | h(
66 | "button",
67 | {
68 | onClick: this.onChangePropsDemo2,
69 | },
70 | "changeProps - 值变成了 undefined - 删除"
71 | ),
72 |
73 | h(
74 | "button",
75 | {
76 | onClick: this.onChangePropsDemo3,
77 | },
78 | "changeProps - key 在新的里面没有了 - 删除"
79 | ),
80 | ]
81 | );
82 | },
83 | };
84 |
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-mini-vue",
3 | "version": "1.0.0",
4 | "main": "lib/guide-mini-vue.cjs.js",
5 | "module": "lib/guide-mini-vue.esm.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.17.5",
13 | "@babel/preset-env": "^7.16.11",
14 | "@babel/preset-typescript": "^7.16.7",
15 | "@types/jest": "^27.4.1",
16 | "babel-jest": "^27.5.1",
17 | "jest": "^27.5.1",
18 | "typescript": "^4.6.2",
19 | "rollup": "^2.57.0",
20 | "tslib": "^2.3.1",
21 | "@rollup/plugin-typescript": "^8.2.5"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from "./package.json";
2 | import typescript from "@rollup/plugin-typescript";
3 | export default {
4 | input: "./src/index.ts",
5 | output: [
6 | // 1. cjs -> commonjs
7 | // 2. esm
8 | {
9 | format: "cjs",
10 | file: pkg.main,
11 | },
12 | {
13 | format: "es",
14 | file: pkg.module,
15 | },
16 | ],
17 |
18 | plugins: [typescript()],
19 | };
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './resctivite'
2 | export * from './runtime-dom'
--------------------------------------------------------------------------------
/src/resctivite/baseHandler.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "../shared/shared"
2 | import { track, trigger } from "./effect"
3 | import { isReactive, reactive, readonly } from "./reactive"
4 | import { isRef } from "./ref"
5 |
6 | export const enum ReactiveFlags {
7 | IS_REACTIVE = "__v_isReactive",
8 | IS_READONLY = "__v_isReadonly",
9 | }
10 |
11 | const get = createGetter()
12 | const set = createSetter()
13 | const readonlyGet = createGetter(true)
14 | const showllGet = createGetter(true, true)
15 |
16 |
17 | /**
18 | * 创建Proxy get处理函数
19 | * @isReadonly 只读数据 默认为false 只读数据不需要进行依赖收集
20 | */
21 | export function createGetter (isReadonly = false, IsShowll = false) {
22 |
23 | return function get (target: object, key: any) {
24 |
25 | if (key == ReactiveFlags.IS_READONLY) {
26 | return isReadonly
27 | } else if (key == ReactiveFlags.IS_REACTIVE) {
28 | return !isReadonly
29 | }
30 |
31 | let res = Reflect.get(target, key)
32 |
33 | /**
34 | * 浅代理只代理第一层属性
35 | */
36 | if (IsShowll) {
37 | return res
38 | }
39 |
40 | /**
41 | * 如果访问的数据是object类型
42 | * 就根据当前isReadonly的状态代理不同的数据类型
43 | */
44 | if (isObject(res)) {
45 | return isReadonly ? readonly(res) : reactive(res);
46 | }
47 |
48 | /* 如果是只读数据 就不需要依赖收集 因为在数据变化后不需要执行副作用函数* */
49 | if (!isReadonly) {
50 | track(target, key)
51 | }
52 |
53 | return res
54 | }
55 |
56 | }
57 |
58 | /**
59 | * 创建Proxy set处理函数
60 | */
61 | export function createSetter () {
62 | return function set (target: object, key: any, value: any) {
63 | let res = Reflect.set(target, key, value)
64 | trigger(target, key)
65 |
66 | return res
67 | }
68 | }
69 |
70 |
71 | /**
72 | * reactive get set
73 | */
74 | export const mutableHandlers = {
75 | get,
76 | set,
77 | }
78 |
79 | /**
80 | * readonly get set
81 | */
82 | export const readonlyHandlers = {
83 | get: readonlyGet,
84 | set (target: object, key: any, value: any) {
85 | console.warn(
86 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`,
87 | target
88 | );
89 | return true
90 | }
91 | }
92 |
93 |
94 | /**
95 | * proxyRefsHandlers get set
96 | */
97 | export const proxyRefsHandlers = {
98 | get (target: object, key: any) {
99 | let res = Reflect.get(target, key)
100 | return isRef(res) ? res.value : res
101 | },
102 | set (target: object, key: any, value: any) {
103 |
104 | if (isRef(target[key] && !isRef(value))) {
105 | return (target[key].value = value)
106 | } else {
107 | let res = Reflect.set(target, key,value)
108 | return res
109 | }
110 |
111 | }
112 | }
113 |
114 | /**
115 | * shallowReadonlyHandlers get set
116 | */
117 | export const shallowReadonlyHandlers = {
118 | get: showllGet,
119 | set (target: object, key: any, value: any) {
120 | console.warn(
121 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`,
122 | target
123 | );
124 | return true
125 | }
126 | }
--------------------------------------------------------------------------------
/src/resctivite/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect"
2 |
3 |
4 | export class ComputedImpl {
5 |
6 | public _value
7 |
8 | private _dirty: boolean = true;
9 |
10 | public effect: ReactiveEffect
11 |
12 | constructor(getter) {
13 |
14 | this.effect = new ReactiveEffect(getter, () => {
15 | /**
16 | * 通过scheduler参数 在数据变化后执行这个传入的函数修改this._dirty 的状态标记为true
17 | */
18 | if (!this._dirty) {
19 | this._dirty = true
20 | }
21 |
22 | })
23 |
24 | }
25 |
26 | get value () {
27 | /**
28 | * @ 通过this._dirty 标记是否初始化过this._value 的值
29 | * @ 读取过后将状态标记为false 后续依赖的数据没有变化就不再给this._value 重新赋值 直接将之前读取的返沪即可
30 | */
31 | if (this._dirty) {
32 | this._dirty = false
33 | this._value = this.effect.run()
34 | }
35 | return this._value
36 | }
37 |
38 | }
39 |
40 | export function computed (fn: any) {
41 | const computed = new ComputedImpl(fn)
42 | return computed
43 | }
--------------------------------------------------------------------------------
/src/resctivite/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared/shared"
2 |
3 | type anyFn = () => any
4 |
5 | /** effect 函数的返回值 */
6 | type effectFn = {
7 | (): anyFn,
8 | /** effect实例 */
9 | effect: ReactiveEffect
10 | }
11 |
12 | /**
13 | * 存储响应式数据的副作用容器
14 | * @ targetMap Map数据结构 全局响应式对象的容器
15 | * @-- object Map数据结构 响应式对象
16 | * @-- key Set数据结构 响应式对象的key 通过key值找到对应的依赖
17 | */
18 | const targetMap: Map