├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── README.md
├── babel.config.js
├── docs
└── README.md
├── example
├── apiInject
│ ├── App.js
│ └── index.html
├── compiler-base
│ ├── App.js
│ ├── index.html
│ └── main.js
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlots
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── mian.js
├── componentUpdate
│ ├── App.js
│ ├── Child.js
│ ├── index.html
│ └── main.js
├── getCurrentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── helloworld
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── nextTicker
│ ├── App.js
│ ├── index.html
│ └── main.js
├── text.js
│ └── index.html
├── update
│ ├── App.js
│ ├── index.html
│ └── main.js
└── updateChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
├── lib
├── mini-vue.cjs.js
└── mini-vue.esm.js
├── mark.md
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── rollup.config.js
├── src
├── compiler-core
│ ├── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── compile.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transform.ts
│ │ ├── transforms
│ │ │ ├── tarnsformElement.ts
│ │ │ ├── transformExpression.ts
│ │ │ └── transfromText.ts
│ │ └── utils.ts
│ └── tests
│ │ ├── __snapshots__
│ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
├── index.ts
├── reactivity
│ ├── baseHandler.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── index.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ ├── shallowReactive.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── apiInject.ts
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── componentUpdateUtils.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── renderer.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── index.ts
│ ├── shapeFlags.ts
│ ├── text.ts
│ └── toDisplayString.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "pwa-chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:8080",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5502
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini-vue3.0 学习
2 |
3 | 通过 mini-vue 的学习 来理解 vue3 的 reactivity、compiler-core、 runtime-core
4 |
5 | ## 已经实现过的模块
6 |
7 | ## reactivity
8 |
9 | - [x] reactive
10 | - [x] readonly
11 | - [x] shallowReadonly
12 | - [x] shallowReactive
13 | - [x] isReactive
14 | - [x] isReadonly
15 | - [x] isProxy
16 | - [x] ref
17 | - [x] isRef
18 | - [x] unRef
19 | - [x] proxyRef
20 | - [x] computed
21 | - [x] effect
22 |
23 | ## runtime-core
24 |
25 | - [x] 初始化 Component 主流程
26 | - [x] 初始化 Element 主流程
27 | - [x] shapeFlags
28 | - [x] 组件道理对象 Proxy
29 | - [x] 注册事件
30 | - [x] 组件 props
31 | - [x] 组件 emit
32 | - [x] 组件 slots
33 | - [ ] getCurrentInstance
34 | - [x] 更新 element
35 |
36 | ## runtime-dom
37 |
38 | - [ ] custom renderer
39 |
40 | ## compiler
41 |
42 | - [ ] 解析插值
43 | - [ ] 解析 element
44 | - [ ] 解析 text 功能
45 | - [ ] 解析 三种联合类型功能
46 | - [ ] parse 的实现
47 | 未完待续
48 |
49 | ## 掘金文章
50 |
51 | 配合《Vue.js 设计和实现》的总结输出
52 |
53 | - [Vue3 源码学习(1)--框架设计概览 ](https://juejin.cn/post/7074111898894991390/)
54 | - [Vue3 源码学习(2)--响应式系统(1)](https://juejin.cn/post/7074496267061035038/)
55 | - [Vue3 源码学习(3)--响应式系统(2)](https://juejin.cn/post/7074847535621210126/)
56 | - [Vue3 源码学习(4)--响应式系统(3)](https://juejin.cn/post/7075139625592815624/)
57 | - [Vue3 源码学习(4)--响应式系统(3)](https://juejin.cn/post/7075139625592815624)
58 | - [Vue3 源码学习(5)--runtime-core(1)--初始化(1)](https://juejin.cn/post/7079687116841549855)
59 | - [Vue3 源码学习(6)--runtime-core(2)--初始化(2)](https://juejin.cn/post/7082212664067227679)
60 | - [vue3 源码学习(6) -- runtime-core(3):更新 element(1)](https://juejin.cn/post/7083065686150348836/)
61 | - [vue3 源码学习(6) -- runtime-core(3):更新 element(2):双端 diff 算法](https://juejin.cn/post/7083459283458719757)
62 | - [Vue3 源码学习(5)--响应式系统(3)](https://juejin.cn/post/7075139625592815624)
63 | - [Vue3 源码学习(6)--runtime-core(1)--初始化(1)](https://juejin.cn/post/7079687116841549855)
64 | - [Vue3 源码学习(7)--runtime-core(2)--初始化(2)](https://juejin.cn/post/7082212664067227679)
65 | - [vue3 源码学习(8) -- runtime-core(3):更新 element(1)](https://juejin.cn/post/7083065686150348836/)
66 | - [vue3 源码学习(9) -- runtime-core(4):更新 element(2):双端 diff 算法](https://juejin.cn/post/7083459283458719757)
67 | 未完待续
68 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
3 | };
4 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # 一些关于 vue3 源码方面的文章输出
2 |
3 | 源码初学者,请多关照!!!!!
4 |
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | // 组件 provide 和 inject 功能
2 | import { h, provide, inject } from "../../lib/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", {}, [h("p", {}, `ProviderTwo foo:${this.foo}`), h(Consumer)]);
27 | },
28 | };
29 |
30 | const Consumer = {
31 | name: "Consumer",
32 | setup() {
33 | const foo = inject("foo");
34 | const bar = inject("bar");
35 | // const baz = inject("baz", "bazDefault"); //默认值
36 | const baz = inject("baz", () => "bazDefault");
37 |
38 | return {
39 | foo,
40 | bar,
41 | baz,
42 | };
43 | },
44 |
45 | render() {
46 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`);
47 | },
48 | };
49 |
50 | export default {
51 | name: "App",
52 | setup() {},
53 | render() {
54 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
55 | },
56 | };
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from "../../lib/mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 | template: `hi,{{count}}
`,
6 | setup() {
7 | const count = (window.count = ref(1));
8 | return {
9 | count,
10 | };
11 | },
12 | };
--------------------------------------------------------------------------------
/example/compiler-base/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/compiler-base/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | export const App = {
4 | name: "App",
5 |
6 | render() {
7 | //emit
8 |
9 | return h("div", {}, [
10 | h("div", {}, "App"),
11 | h(Foo, {
12 | //props
13 | //emit
14 | onAdd(num1, num2) {
15 | console.log("onAdd", num1, num2);
16 | },
17 | onAddFoo() {
18 | console.log("onAddFoo");
19 | },
20 | }),
21 | ]);
22 | },
23 |
24 | setup() {
25 | return {};
26 | },
27 | };
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | setup(props, { emit }) {
5 | const emitAdd = () => {
6 | console.log("emit add");
7 | emit("add", 1, 2);
8 | emit("add-foo");
9 | };
10 | return {
11 | emitAdd,
12 | };
13 | },
14 |
15 | render() {
16 | const btn = h("button", { onClick: this.emitAdd }, "emitAdd");
17 | const foo = h("p", {}, "foo");
18 | return h("div", {}, [foo, btn]);
19 | },
20 | };
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.getElementById("app");
5 | // console.log(rootContainer);
6 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/componentSlots/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextVnode } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | export const App = {
4 | name: "App",
5 |
6 | render() {
7 | const app = h("div", {}, "App"); //vnode
8 |
9 | //怎么使用插槽 ---- 把对应的内容放到对应的组件的children中
10 | //希望children里面的p标签能够在foo组件中渲染出来
11 |
12 | //传入单值
13 | // const foo = h(Foo, {}, h("p", {}, "123"));
14 |
15 |
16 | // 传入数组
17 | // const foo = h(Foo, {}, [h("p", {}, "123"), h("p", {}, '456')]);
18 |
19 | //具名插槽 需要知道key value 所以用到obj
20 | // const foo = h(
21 | // Foo, {}, {
22 | // header: h("p", {}, "header"),
23 | // footer: h("p", {}, "footer"),
24 | // }
25 | // );
26 |
27 | //作用域插槽
28 | const foo = h(
29 | Foo, {}, {
30 | // header: ({ age }) => h("p", {}, "header" + age),
31 | header: ({ age }) => h("p", {}, "header" + age),
32 |
33 | footer: () => [h("p", {}, "footer"), createTextVnode("你好呀!")]
34 | // footer: () => h("p", {}, "footer")
35 | }
36 | ); //vnode
37 | return h("div", {}, [app, foo]);
38 | },
39 | setup() {
40 | return {};
41 | },
42 | };
--------------------------------------------------------------------------------
/example/componentSlots/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/mini-vue.esm.js";
2 |
3 |
4 | //插槽本质 就是将App.js中 Foo组件的children const foo = h(Foo, {}, h("p", {}, "123"));
5 | // 插入到 Foo组件中 element vnode subtree中children
6 | export const Foo = {
7 | name: "Foo",
8 | render() {
9 | const foo = h("p", {}, "foo");
10 |
11 | //Foo .vnode .children
12 | // console.log(this);
13 | // console.log("+++++++++:" +
14 | // JSON.stringify(this.$slots));
15 | // children -> vnode
16 | // 内部到处renderSlots
17 | console.log('slots:', this.$slots);
18 | // return h("div", {}, [foo, h("div", {}, this.$slots)]);
19 | // return h("div", {}, [foo, h("div", {}, renderSlots(this.$slots))]);
20 |
21 |
22 | const age = 18
23 | // renderSlots
24 | //1、获取到要渲染的元素
25 | //2、获取到渲染的位置
26 |
27 | // return h("div", {}, [renderSlots(this.$slots, "header", { age }), foo, renderSlots(this.$slots, "footer")]);
28 |
29 | return h("div", {}, [renderSlots(this.$slots, "header", { age }), foo, renderSlots(this.$slots, "footer")]);
30 | },
31 |
32 | setup() {
33 | return {};
34 | },
35 | };
--------------------------------------------------------------------------------
/example/componentSlots/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/example/componentSlots/mian.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.getElementById("app");
5 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | //组件更新:
2 | // 首先更新组建的数据 props等
3 | // 其次调用组件的render函数 利用effect 的返回值是runner函数
4 | // 更新时 检测组件是否需要更新
5 |
6 | import { h, ref } from '../../lib/mini-vue.esm.js'
7 | import Child from './Child.js'
8 | export const App = {
9 | name: 'App',
10 |
11 | setup() {
12 | const msg = ref('123')
13 | const count = ref(1)
14 | window.msg = msg
15 |
16 | const changeChildProps = () => {
17 | msg.value = '456'
18 | }
19 |
20 | const changeCount = () => {
21 | count.value++
22 | }
23 |
24 | return {
25 | msg,
26 | count,
27 | changeChildProps,
28 | changeCount,
29 | }
30 | },
31 | render() {
32 | return h('div', {}, [
33 | h('div', {}, '你好'),
34 | h('button', { onClick: this.changeChildProps }, 'change child props'),
35 | h(Child, { msg: this.msg }), //重点在这
36 | h('button', { onClick: this.changeCount }, 'change self count'),
37 | h('p', {}, 'count: ' + this.count),
38 | ])
39 | },
40 | }
41 |
42 | /**
43 | * 组建的更新
44 | *
45 | * 组件的渲染是一个开箱的过程,在props发生变化的时候就再次执行render函数
46 | * 执行render函数就会创建新的subTree(vnode) 然后在进行patch(prevSubTree,subTree)
47 | * 所以 在进行processComponent中 就要进行判断 是否有prevSubTree
48 | * 无 ---> mountComponent
49 | * 有 ---> patchComponent
50 | *
51 | * 更新component 需要先更新数据(组件的props)
52 | *
53 | *
54 | */
55 |
--------------------------------------------------------------------------------
/example/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js"
2 | export default {
3 | name: "Child",
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h("div", {}, [
7 | h("div", {}, "child - props - msg: " + this.$props.msg)
8 | ]);
9 | },
10 | };
--------------------------------------------------------------------------------
/example/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/mini-vue.esm.js'
2 | import { App } from './app.js'
3 |
4 | const rootContainer = document.getElementById("app")
5 |
6 | console.log(rootContainer);
7 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/getCurrentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 |
7 | render() {
8 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]);
9 | },
10 |
11 | setup() {
12 | const instance = getCurrentInstance();
13 | console.log("App:", instance);
14 | },
15 | };
--------------------------------------------------------------------------------
/example/getCurrentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 |
6 | render() {
7 | return h("div", {}, "foo");
8 | },
9 |
10 | setup() {
11 | const instance = getCurrentInstance();
12 | console.log("Foo:", instance);
13 | return {};
14 | },
15 | };
--------------------------------------------------------------------------------
/example/getCurrentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/getCurrentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.getElementById("app");
5 |
6 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/helloworld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | window.self = null;
5 | export const App = {
6 | name: "APP",
7 | // .vue
8 | // ->render
9 |
10 | //render
11 |
12 | render() {
13 | window.self = this;
14 | return h(
15 | "div",
16 | {
17 | id: "root",
18 | class: ["red", "hard"],
19 | onClick: () => {
20 | console.log("嘿嘿嘿");
21 | },
22 | onMousedown: (e) => {
23 | console.log(e.target);
24 | },
25 | },
26 | // "hi" + this.msg
27 | //this.$el -> get 当前component的 root element
28 | //string
29 | // "hi mini-vue"
30 | //array
31 | // [
32 | // h("p", { class: "red" }, "hi"),
33 | // h("p", { class: "hard" }, [h("span", { id: "aaa" }, "hihaha")]),
34 | // ]
35 |
36 | [h("div", {}, "hi" + this.msg), h(Foo, { count: 1 })]
37 | );
38 | },
39 |
40 | setup() {
41 | //composition api
42 | return {
43 | msg: "mini-vue 哈哈哈",
44 | };
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/example/helloworld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup(props) {
6 | //props.count
7 | console.log(props);
8 | // props.count++;
9 | //props readonly
10 | },
11 | render() {
12 | return h("div", {}, "foo : " + this.count);
13 | },
14 | };
--------------------------------------------------------------------------------
/example/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/example/helloworld/main.js:
--------------------------------------------------------------------------------
1 | // vue3
2 |
3 | import { createApp } from "../../lib/mini-vue.esm.js";
4 | import { App } from "./App.js";
5 |
6 | const rootContainer = document.getElementById("app");
7 | console.log(rootContainer);
8 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/nextTicker/App.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | ref,
4 | getCurrentInstance,
5 | nextTick,
6 | } from "../../lib/mini-vue.esm.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {
11 | const count = ref(1);
12 | const instance = getCurrentInstance();
13 |
14 | function onClick() {
15 | for (let i = 0; i < 100; i++) {
16 | console.log("update");
17 | count.value = i;
18 | }
19 |
20 | debugger;
21 | console.log(instance);
22 | nextTick(() => {
23 | console.log(instance);
24 | });
25 |
26 | // await nextTick()
27 | // console.log(instance)
28 | }
29 |
30 | return {
31 | onClick,
32 | count,
33 | };
34 | },
35 | render() {
36 | const button = h("button", { onClick: this.onClick }, "update");
37 | const p = h("p", {}, "count:" + this.count);
38 |
39 | return h("div", {}, [button, p]);
40 | },
41 | };
--------------------------------------------------------------------------------
/example/nextTicker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/nextTicker/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/text.js/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | render() {
7 | // window.self = this;
8 | // console.log(this.count); // count refImpl对象 count
9 | return h("div", { id: "root", ...this.props }, [
10 | h("button", { onClick: this.onClick }, "click"),
11 | h("div", {}, "count" + this.count), // render函数当作依赖,用effect包裹 ==》触发render==》 依赖收集
12 |
13 | //props
14 |
15 | h("button", { onClick: this.onChangePropsDemo1 }, "changeProps - foo的值改变了"),
16 | h("button", { onClick: this.onChangePropsDemo2 }, "changeProps - foo的值改变了为undefined"),
17 | h("button", { onClick: this.onChangePropsDemo3 }, "changeProps - bar没有了"),
18 | ]);
19 | },
20 | setup() {
21 | const count = ref(0);
22 |
23 | const onClick = () => {
24 | count.value++;
25 | };
26 |
27 | //props
28 | const props = ref({
29 | foo: "foo",
30 | bar: "bar",
31 | });
32 |
33 | const onChangePropsDemo1 = () => {
34 | props.value.foo = "new-foo";
35 | };
36 | const onChangePropsDemo2 = () => {
37 | props.value.foo = "undefined";
38 | };
39 | const onChangePropsDemo3 = () => {
40 | props.value = {
41 | foo: "foo",
42 | };
43 | };
44 | return {
45 | count,
46 | onClick,
47 | props,
48 | onChangePropsDemo1,
49 | onChangePropsDemo2,
50 | onChangePropsDemo3,
51 | };
52 | },
53 | };
54 |
55 |
56 |
57 | /**
58 | * 思考1: 什么时候进行更新
59 | * 响应式对象发生改变,render函数结果就会变化 导致vnode对象发生变化
60 | * 所以 我们要怎么获取到之前和之后两个vnode呢?
61 | *
62 | * setupRenderEffect函数中的 subTree就是组件render函数的结果 即当前组件的vnode n1
63 | *
64 | *
65 | * 那么 怎么获取响应式对象改变后的subTree结果呢?
66 | * 通过之前实现的effect函数包裹setupRenderEffect中的逻辑,
67 | * 当响应式对象发生变化的时候,就会重新执行包裹的逻辑,从而重新执行组件的render函数,生成新的vnode n2
68 | *
69 | *然后进行n1 和 n2 之间的比较 patch(n1,n2,container,parentInstance)-> processElement ->patchElement
70 | *
71 | *
72 | * 更新逻辑 : 可以看作是两个vnode对象之间的对比 n1 n2
73 | *
74 | * 三个方面进行对比
75 | * type
76 | * 但是普通修改render函数 只会修改内部的props和 children 所以一般type不会改变,如果type改变了 会直接当作新的element直接挂在
77 | *
78 | * props patchProps
79 | * 1、之前的值和现在的值不一样了 修改了值
80 | * 遍历新的props,通过key获取到 newProps[key] 和 oldProps[key]
81 | * newProps[key] != oldProps[key] 修改props的值(通过patchProp函数)
82 | * hostPatchProp(el, key, oldProps[key], newProps[key])
83 | *
84 | *
85 | * 2、新的值null || undefined 删除
86 | * 同上面方法,因为新的props的key值没有发生变化
87 | *
88 | *
89 | * 3、之前的值在新的里面没有了 删除
90 | * 新的props值少了,证明新的props中的key与原来props的key不一样了,新的props的key值少了
91 | * 这时候我们需要遍历老的props中key,查找当前key在新的props中是否存在
92 | * newProps[key] === 'undefined' 删除当前key hostPatchProp(el, key, oldProps[key], null)
93 | *
94 | *
95 | *
96 | * children
97 | * children 的类型 包括 string 和 array 两种类型
98 | * 所以在进行children对比的时候就会产生四种情况
99 | * oldChildren newChildren
100 | * string string
101 | * string array
102 | * array string
103 | * array array
104 | * 在updateChildren 的app.js中详细总结
105 | */
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.getElementById("app");
5 |
6 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/example/updateChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from '../../lib/mini-vue.esm.js'
2 |
3 | import ArrayToText from './ArrayToText.js'
4 | import TextToArray from './TextToArray.js'
5 | import TextToText from './TextToText.js'
6 | import ArrayToArray from './ArrayToArray.js'
7 | export const App = {
8 | name: 'App',
9 |
10 | render() {
11 | return h('div', { tId: 1 }, [
12 | h('p', {}, '主页'),
13 | // h('button', { onClick: this.changeCount }, 'change self count'),
14 |
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 | setup() {
27 | // const count = ref('false')
28 | // window.count = count
29 | // const changeCount = () => {
30 | // count = !this.count.value
31 | // }
32 | // return {
33 | // changeCount,
34 | // count,
35 | // }
36 | },
37 | }
38 |
39 | /**
40 | * children的更新逻辑 patchChildren(n1,n2,container,parentComponent)
41 | *
42 | * 新节点children类型 shapeFlag = n2.shapeFlag
43 | * 老节点children类型 prevShapeFlag = n1.shapeFlag
44 | * 老节点children c1 = n1.children
45 | * 新节点children c2 = n2.children
46 | *
47 | *
48 | * 1、 array to text
49 | * 首先我们需要判断新老children的类型是text 还是 array类型
50 | * 通过vnode.shapeFlag来判断
51 | *
52 | * 通过 shapeFlag & ShapeFlags.text_children 判断为text类型
53 | * 再判断 prevShapeFlag & ShapeFlags.array_children 判断老节点是否为array类型
54 | *
55 | * 删除老节点children
56 | * 添加新节点children
57 | *
58 | *
59 | * 2 text to text
60 | * 通过shapeFlag 和 prevShapeFlag 判断都为text类型
61 | *
62 | * 然后判断 n1.children != n2.children
63 | * 直接修改el.textContent = n2.children
64 | *
65 | *
66 | * 3、 text to array
67 | * shapeFlag & ShapeFlags.array_children 判断为array的类型
68 | * prevShapeFlag & ShapeFlags.text_children 判断为text类型
69 | *
70 | * 清空老节点 setElementText(container, "");
71 | * 添加新节点 遍历c2 然后patch(null,v,container,parentComponent)
72 | *
73 | *
74 | * 4、 array to array 最复杂滴
75 | * 分为 无key 和 有key 两种情况
76 | * 4.1无key
77 | * 4.2 有key
78 | *
79 | * 详细见array to array
80 | */
81 |
--------------------------------------------------------------------------------
/example/updateChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | //TODO
2 | //老的是 array
3 | //新的是 array
4 |
5 | import { h, ref } from '../../lib/mini-vue.esm.js'
6 |
7 | // 1、左侧的对比
8 | // (a b) c
9 | //(a,b) d e
10 |
11 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
12 |
13 | // const nextChildren = [
14 | // h("p", { key: "A" }, "A"),
15 | // h("p", { key: "B" }, "B"),
16 | // h("p", { key: "D" }, "D"),
17 | // h("p", { key: "E" }, "E"),
18 | // ];
19 |
20 | //2、右侧对比
21 | // a (b c)
22 | //d e (b c)
23 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
24 | // const nextChildren = [
25 | // h("p", { key: "D" }, "D"),
26 | // h("p", { key: "E" }, "E"),
27 | // h("p", { key: "B" }, "B"),
28 | // h("p", { key: "C" }, "C"),
29 | // ];
30 |
31 | //3、新的比老的长
32 | //(a b)
33 | //(a b) c
34 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
35 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
36 |
37 | //(a b)
38 | //c (a b)
39 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
40 | // const nextChildren = [h("p", { key: "C" }, "C"), h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
41 |
42 | //4、老的比新的长
43 | //(a b) c
44 | //(a b)
45 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
46 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
47 |
48 | //c (a b)
49 | //(a b)
50 | // const prevChildren = [h("p", { key: "C" }, "C"), h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
51 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
52 |
53 | //5、对比中间部分
54 | //删除老的(在老的里面存在,新的里面不存在)
55 | //5.1
56 | //a b (c d) f g
57 | //a b (e c) f g
58 | //D节点在新的里面没有的 -需要删除
59 | //C节点props也发生了变化
60 | // const prevChildren = [
61 | // h("p", { key: "A" }, "A"),
62 | // h("p", { key: "B" }, "B"),
63 | // h("p", { key: "C", id: "c-prev" }, "C"),
64 | // h("p", { key: "D" }, "D"),
65 | // h("p", { key: "F" }, "F"),
66 | // h("p", { key: "G" }, "G"),
67 | // ];
68 | // const nextChildren = [
69 | // h("p", { key: "A" }, "A"),
70 | // h("p", { key: "B" }, "B"),
71 | // h("p", { key: "E" }, "E"),
72 | // h("p", { key: "C", id: "c-next" }, "C"),
73 | // h("p", { key: "F" }, "F"),
74 | // h("p", { key: "G" }, "G"),
75 | // ];
76 | // 5.1.1
77 | // a,b,(c,e,d),f,g
78 | // a,b,(e,c),f,g
79 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
80 | // const prevChildren = [
81 | // h("p", { key: "A" }, "A"),
82 | // h("p", { key: "B" }, "B"),
83 | // h("p", { key: "C", id: "c-prev" }, "C"),
84 | // h("p", { key: "E" }, "E"),
85 | // h("p", { key: "D" }, "D"),
86 | // h("p", { key: "F" }, "F"),
87 | // h("p", { key: "G" }, "G"),
88 | // ];
89 |
90 | // const nextChildren = [
91 | // h("p", { key: "A" }, "A"),
92 | // h("p", { key: "B" }, "B"),
93 | // h("p", { key: "E" }, "E"),
94 | // h("p", { key: "C", id: "c-next" }, "C"),
95 | // h("p", { key: "F" }, "F"),
96 | // h("p", { key: "G" }, "G"),
97 | // ];
98 |
99 | // 5.2 移动 (节点存在于新的和老的里面,但是位置变了)
100 | // 2.1
101 | // a,b,(c,d,e),f,g
102 | // a,b,(e,c,d),f,g
103 | // 最长子序列: [1,2]
104 |
105 | // const prevChildren = [
106 | // h("p", { key: "A" }, "A"),
107 | // h("p", { key: "B" }, "B"),
108 | // h("p", { key: "C" }, "C"),
109 | // h("p", { key: "D" }, "D"),
110 | // h("p", { key: "E" }, "E"),
111 | // h("p", { key: "F" }, "F"),
112 | // h("p", { key: "G" }, "G"),
113 | // ];
114 |
115 | // const nextChildren = [
116 | // h("p", { key: "A" }, "A"),
117 | // h("p", { key: "B" }, "B"),
118 | // h("p", { key: "E" }, "E"),
119 | // h("p", { key: "C" }, "C"),
120 | // h("p", { key: "D" }, "D"),
121 | // h("p", { key: "F" }, "F"),
122 | // h("p", { key: "G" }, "G"),
123 | // ];
124 | // 3. 创建新的节点
125 | // a,b,(c,e),f,g
126 | // a,b,(e,c,d),f,g
127 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建
128 | // const prevChildren = [
129 | // h("p", { key: "A" }, "A"),
130 | // h("p", { key: "B" }, "B"),
131 | // h("p", { key: "C" }, "C"),
132 | // h("p", { key: "E" }, "E"),
133 | // h("p", { key: "F" }, "F"),
134 | // h("p", { key: "G" }, "G"),
135 | // ];
136 |
137 | // const nextChildren = [
138 | // h("p", { key: "A" }, "A"),
139 | // h("p", { key: "B" }, "B"),
140 | // h("p", { key: "E" }, "E"),
141 | // h("p", { key: "C" }, "C"),
142 | // h("p", { key: "D" }, "D"),
143 | // h("p", { key: "F" }, "F"),
144 | // h("p", { key: "G" }, "G"),
145 | // ];
146 |
147 | // 综合例子
148 | // a,b,(c,d,e,z),f,g
149 | // a,b,(d,c,aky,y,e),f,g
150 |
151 | // const prevChildren = [
152 | // h('p', { key: 'A' }, 'A'),
153 | // h('p', { key: 'B' }, 'B'),
154 | // h('p', { key: 'C' }, 'C'),
155 | // h('p', { key: 'D' }, 'D'),
156 | // h('p', { key: 'E' }, 'E'),
157 | // h('p', { key: 'Z' }, 'Z'),
158 | // h('p', { key: 'F' }, 'F'),
159 | // h('p', { key: 'G' }, 'G'),
160 | // ]
161 |
162 | // const nextChildren = [
163 | // h('p', { key: 'A' }, 'A'),
164 | // h('p', { key: 'B' }, 'B'),
165 | // h('p', { key: 'D' }, 'D'),
166 | // h('p', { key: 'C' }, 'C'),
167 | // h('p', { key: 'aky' }, 'aky'),
168 | // h('p', { key: 'Y' }, 'Y'),
169 | // h('p', { key: 'E' }, 'E'),
170 | // h('p', { key: 'F' }, 'F'),
171 | // h('p', { key: 'G' }, 'G'),
172 | // ]
173 |
174 | const prevChildren = [
175 | h('p', { key: 'A' }, 'A'),
176 | h('p', { key: 'B' }, 'B'),
177 | h('p', { key: 'C' }, 'C'),
178 | h('p', { key: 'D' }, 'D'),
179 | h('p', { key: 'E' }, 'E'),
180 | h('p', { key: 'F' }, 'F'),
181 | h('p', { key: 'G' }, 'G'),
182 | ]
183 |
184 | const nextChildren = [
185 | h('p', { key: 'A' }, 'A'),
186 | h('p', { key: 'B' }, 'B'),
187 | h('p', { key: 'Z' }, 'Z'),
188 | h('p', { key: 'C' }, 'C'),
189 | h('p', { key: 'E' }, 'E'),
190 | h('p', { key: 'aky' }, 'aky'),
191 | h('p', { key: 'F' }, 'F'),
192 | h('p', { key: 'G' }, 'G'),
193 | ]
194 |
195 | //fix c节点应该是move 而不是移除后创建
196 |
197 | // const prevChildren = [
198 | // h("p", { key: "A" }, "A"),
199 | // h("p", {}, "C"),
200 |
201 | // h("p", { key: "B" }, "B"),
202 |
203 | // h("p", { key: "D" }, "D"),
204 |
205 | // ];
206 |
207 | // const nextChildren = [
208 | // h("p", { key: "A" }, "A"),
209 |
210 | // h("p", { key: "B" }, "B"),
211 | // h("p", {}, "C"),
212 |
213 | // h("p", { key: "D" }, "D"),
214 |
215 | // ];
216 | export default {
217 | name: 'ArrayToArray',
218 |
219 | setup() {
220 | const isChange = ref(false)
221 | window.isChange = isChange
222 | return {
223 | isChange,
224 | }
225 | },
226 |
227 | render() {
228 | const self = this
229 | return self.isChange === true ? h('div', {}, nextChildren) : h('div', {}, prevChildren)
230 | },
231 | }
232 |
233 | /**
234 | * 4.1 无key情况
235 | *
236 | * 获取新节点数组长度,获取就节点数组的长度
237 | * 取两个长度中较小的长度值
238 | * 从0位置开始一次进行比较 for(i=0;i<=commonLength; i++) 然后patch
239 | *
240 | * 逻辑本质就是:
241 | * 如果新节点个数 > 新节点个数,移除多余的节点
242 | * 如果旧节点个数 < 新节点个数,增加节点
243 | * 4.2 有key情况 (复杂的嘞)
244 | *
245 | * a,b(c,d,e,,z,y)f,g
246 | * a,b(d,c,y,e)f,g
247 | *
248 | * 新节点children c2
249 | * 旧节点children c1
250 | * 就节点children长度 l1 = c1.length
251 | * 新节点children长度 l2 = c2.length
252 | *
253 | * e1 = c1.length -1
254 | * e2 = c2.length -1
255 | * 三指针对比
256 | *
257 | * 1、左侧对比
258 | * 条件是:while( i<=e1 && i<=e2 )
259 | * n1 = c1[i] 是否与 n2 = c2[i] 相同 isSameVnode(n1,n2) (type和key相同 判断段isSame)
260 | * if(isSameVnode === true) patch(n1,n2,container,parentComponent)
261 | * else break 退出循环
262 | * i++
263 | *
264 | * 综合案例 此时 i=2时 退出循环 e1 = 9 ; e2 = 8
265 | *
266 | *
267 | * 2、右侧对比
268 | * 此时 先进行过左侧的对比了 此时左侧已经对比出不同的节点了,i的值确定了
269 | * 例如综合案例 i=确定了
270 | * 在进行右侧对比 e1=9 e2 =8
271 | * 条件依然是: while( i <=e1 && i<=e2)
272 | * n1 = c1[e1] n2 = c2[e2]
273 | * if(isSameVndoe(n1,n2)===true) patch(n1,n2,container,parentComponent)
274 | * else break 退出循环
275 | * e1 --
276 | * e2 --
277 | *
278 | * 综合案例 此时 i= 2 e1=7 e2 = 6
279 | *
280 | *
281 | * 3、新的比老的多 (两种情况)
282 | * eg a b a b
283 | * a b (c d) ( c d) a b
284 | *
285 | * 在左侧对比和右侧对比完成后 出现的一种情况
286 | * 如果 e1< i <= e2 证明新节点多与老节点
287 | * if( i > e1){
288 | * if( i <=e2) {
289 | * 新的比老的多,直接创建新的节点(多个一个甚至多个),
290 | * 并且把新节点创建在它指定的位置
291 | * }
292 | * }
293 | *
294 | *
295 | * 4、老的比新的多(同上两种情况)
296 | * 在左侧对比和右侧对比完成后 出现的一种情况
297 | * 如果 i >e2 i<=e1
298 | * else if(i>e2){
299 | * while(ie1 同时 i<=e2,证明 新节点比老节点长
343 | * 新建新的节点
344 | * if(i>e1){
345 | * if(i <=e2){
346 | * while(1<=e2){
347 | * 创建新节点
348 | * i++
349 | * }
350 | * }
351 | * }
352 | * }else if( i >e2){ 如果此时 i >e2 同时 i<=e1,证明 老节点比新节点长
353 | * 删除老节点
354 | * while( i <=e1){
355 | * 删除老节点
356 | * i++
357 | * }
358 | * 中间对比
359 | * }else {
360 | * s1 = i
361 | * s2 = i
362 | * patched = 0
363 | * toBePatch = e2-s2 +1
364 | * keyToNewIndexMap映射表建立
365 | *
366 | * for(let i=o;i<=e2;i++) keyToNewIndexMap.set(c2[i].key,i) 填充数据
367 | *
368 | * for (let i = s1; i <= e1; i++) {
369 | * prevChild = c1[i]
370 | *
371 | * if(patched >= toBePatched) remove(prevChild.el)
372 | * if(prevChid.ket !== null){
373 | * prevChild元素在新节点数组中的新索引 newIndex = keyToNewIndexMap.get(prevChid.key)
374 | * }else{
375 | * 遍历新节点数组 for(let j = s2; i <= e2; i++){
376 | * if(isSameVnode(prevChild,c2[j])) newIndex = j break
377 | * }
378 | * }
379 | * 更新和删除
380 | * if(newIndex) patch(prevChild,c2[newIndex],container,parentComponent,null) patched ++
381 | * else remove(prevChid.el) 删除
382 | *
383 | * }
384 | *
385 | *
386 | * }
387 | */
388 |
--------------------------------------------------------------------------------
/example/updateChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | //老的是array
2 | //新的是text
3 |
4 | import { h, ref } from "../../lib/mini-vue.esm.js";
5 |
6 | const nextChildren = "newChildren";
7 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
8 |
9 | export default {
10 | name: "ArrayToText",
11 |
12 | setup() {
13 | const isChange = ref(false);
14 | window.isChange = isChange;
15 |
16 | return {
17 | isChange,
18 | };
19 | },
20 | render() {
21 | const self = this;
22 | return self.isChange === true ?
23 | h("div", {}, nextChildren) :
24 | h("div", {}, prevChildren);
25 | },
26 | };
--------------------------------------------------------------------------------
/example/updateChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { h, ref } from "../../lib/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 ? h("div", {}, nextChildren) : h("div", {}, prevChildren);
22 | },
23 | };
--------------------------------------------------------------------------------
/example/updateChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { h, ref } from "../../lib/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 ? h("div", {}, nextChildren) : h("div", {}, prevChildren);
22 | },
23 | };
--------------------------------------------------------------------------------
/example/updateChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/updateChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/mini-vue.esm.js";
2 | import { App } from "./App.js";
3 | const rootContainer = document.getElementById("app");
4 | createApp(App).mount(rootContainer);
--------------------------------------------------------------------------------
/lib/mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | function toDisplayString(value) {
2 | return String(value);
3 | }
4 |
5 | const extend = Object.assign;
6 | const EMPTY_OBJ = {};
7 | function isObject(val) {
8 | return val !== null && typeof val === "object";
9 | }
10 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
11 | //add-foo ->addFoo
12 | const camelize = (str) => {
13 | return str.replace(/-(\w)/g, (_, c) => {
14 | return c ? c.toUpperCase() : "";
15 | });
16 | };
17 | //addFoo ->AddFoo
18 | const capitalize = (str) => {
19 | return str.charAt(0).toUpperCase() + str.slice(1);
20 | };
21 | // console.log(capitalize(event));
22 | // AddFoo -> toAddFoo
23 | const toHandlerKey = (str) => {
24 | return str ? "on" + str : "";
25 | };
26 | const isString = (value) => typeof value === "string";
27 |
28 | const Fragment = Symbol('Fragment');
29 | const Text = Symbol('Text');
30 | function createVNode(type, props, children) {
31 | const vnode = {
32 | type,
33 | props,
34 | children,
35 | key: props && props.key,
36 | shapeFlag: getShapeFlag(type),
37 | el: null,
38 | component: null,
39 | };
40 | //children
41 | if (typeof children === 'string') {
42 | vnode.shapeFlag = vnode.shapeFlag | 4 /* shapeFlags.text_children */;
43 | }
44 | else if (Array.isArray(children)) {
45 | vnode.shapeFlag = vnode.shapeFlag | 8 /* shapeFlags.array_children */;
46 | }
47 | // 组件 + children 为object类型
48 | if (vnode.shapeFlag & 2 /* shapeFlags.stateful_component */) {
49 | if (isObject(children)) {
50 | vnode.shapeFlag = vnode.shapeFlag | 16 /* shapeFlags.slot_children */;
51 | }
52 | }
53 | return vnode;
54 | }
55 | function createTextVnode(text) {
56 | return createVNode(Text, {}, text);
57 | }
58 | function getShapeFlag(type) {
59 | return typeof type === 'string' ? 1 /* shapeFlags.element */ : 2 /* shapeFlags.stateful_component */;
60 | }
61 |
62 | function h(type, props, children) {
63 | return createVNode(type, props, children);
64 | }
65 |
66 | function renderSlots(slots, name, props) {
67 | const slot = slots[name];
68 | if (slot) {
69 | if (typeof slot === "function") {
70 | return createVNode(Fragment, {}, slot(props));
71 | }
72 | }
73 | }
74 |
75 | //effect 第一个参数接受一个函数
76 | /**
77 | * 不给activeEffect添加类型, 单测会报错
78 | * 所以进行了代码优化
79 | */
80 | // let activeEffect: () => void;
81 | // export function effect(fn: () => void) {
82 | // activeEffect = fn;
83 | // fn(); //执行函数 ->触发了响应式对象的getter ->track
84 | // activeEffect = function () {};
85 | // }
86 | //工具函数
87 | function cleanupEffect(effect) {
88 | effect.deps.forEach((dep) => {
89 | dep.delete(effect);
90 | });
91 | //把 effect.deps清空
92 | effect.deps.length = 0;
93 | }
94 | //代码优化 面向对象思想
95 | let activeEffect;
96 | let shouldTrack = false; //用于记录是否应该收集依赖,防止调用stop后触发响应式对象的property的get的依赖收集 obj.foo ++
97 | class ReactiveEffect {
98 | constructor(fn, option) {
99 | this.deps = []; //用于保存与当前实例相关的响应式对象的 property 对应的 Set 实例 用于stop操作
100 | this.active = true; //用于记录当前实例状态,为 true 时未调用 stop 方法,否则已调用,防止重复调用 stop 方法
101 | this._fn = fn;
102 | this.scheduler = option === null || option === void 0 ? void 0 : option.scheduler;
103 | this.onStop = option === null || option === void 0 ? void 0 : option.onStop;
104 | // this.deps = [];
105 | // this.active = true;
106 | }
107 | //用于执行传入的函数
108 | run() {
109 | //stop的状态下(active =false) 直接执行fn 不收集依赖
110 | if (!this.active) {
111 | this.active = true;
112 | return this._fn();
113 | }
114 | //应该收集依赖
115 | shouldTrack = true;
116 | activeEffect = this;
117 | const res = this._fn();
118 | //重置
119 | shouldTrack = false;
120 | // 返回传入的函数执行的结果
121 | return res;
122 | }
123 | stop() {
124 | //删除effect active用于优化 多次调用stop也只清空一次
125 | if (this.active) {
126 | cleanupEffect(this);
127 | if (this.onStop) {
128 | this.onStop();
129 | }
130 | this.active = false;
131 | }
132 | }
133 | }
134 | //effect函数
135 | /**
136 | * @param fn 参数函数
137 | */
138 | function effect(fn, option = {}) {
139 | const _effect = new ReactiveEffect(fn, option);
140 | // Object.assign(_effect, option);
141 | if (option) {
142 | extend(_effect, option); //what this?
143 | }
144 | if (!option || !option.lazy) {
145 | _effect.run();
146 | }
147 | // _effect.run(); //实际上是调用执行了fn函数
148 | const runner = _effect.run.bind(_effect); //直接调用runnner
149 | runner.effect = _effect;
150 | return runner;
151 | }
152 | const targetMap = new WeakMap();
153 | // 进行依赖收集track
154 | function track(target, key) {
155 | // 若不应该收集依赖则直接返回
156 | // if (!shouldTrack || activeEffect === undefined) {
157 | // return;
158 | // }
159 | if (!isTracking())
160 | return;
161 | //1、先获取到key的依赖集合dep
162 | //所有对象的的以来集合targetMap -> 当前对象的依赖集合objMap -> 当前key的依赖集合
163 | let objMap = targetMap.get(target);
164 | // 如果没有初始化过 需要先初始化
165 | if (!objMap) {
166 | objMap = new Map();
167 | targetMap.set(target, objMap);
168 | }
169 | //同理 如果没有初始化过 需要先初始化
170 | let dep = objMap.get(key);
171 | if (!dep) {
172 | dep = new Set(); //依赖不会重复
173 | objMap.set(key, dep);
174 | }
175 | //d将依赖函数添加给dep
176 | // if (!activeEffect) return;
177 | // if(dep.has(activeEffect)) return
178 | // dep.add(activeEffect); ? 怎么获取到fn? 添加一个全局变量activeEffect
179 | // activeEffect?.deps.push(dep); ?
180 | trackEffect(dep);
181 | }
182 | //重构
183 | function trackEffect(dep) {
184 | //看看dep之前有没有添加过,添加过的话 就不添加了
185 | if (dep.has(activeEffect))
186 | return;
187 | dep.add(activeEffect);
188 | activeEffect.deps.push(dep);
189 | }
190 | //activeEffect可能为undefined 原因: 访问一个单纯的reactive对象,没有任何依赖的时候 activeEffect可能为undefined
191 | function isTracking() {
192 | return shouldTrack && activeEffect !== undefined;
193 | }
194 | console.log(targetMap.has(effect));
195 | //触发依赖trigger
196 | function trigger(target, key) {
197 | // console.log("触发依赖了");
198 | //1、先获取到key的依赖集合dep
199 | let objMap = targetMap.get(target);
200 | // console.log(objMap);
201 | let dep = objMap.get(key);
202 | console.log(objMap);
203 | //去执行dep里面的函数
204 | // dep.forEach((effect) => {
205 | // if (effect.scheduler) {
206 | // effect.scheduler();
207 | // } else {
208 | // effect.run();
209 | // }
210 | // });
211 | triggerEffect(dep);
212 | }
213 | // 重构
214 | function triggerEffect(dep) {
215 | dep.forEach((effect) => {
216 | if (effect.scheduler) {
217 | effect.scheduler();
218 | }
219 | else {
220 | effect.run();
221 | }
222 | });
223 | }
224 |
225 | // reactive和readonly对象复用代码的重构
226 | function createGetter(isReadonly = false, isShallow = false) {
227 | //true
228 | return function get(target, key) {
229 | //专门判断isReactive
230 | if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
231 | return !isReadonly;
232 | }
233 | //专门判断isReadonly
234 | else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
235 | return isReadonly;
236 | }
237 | const res = Reflect.get(target, key);
238 | //reactive对象的getter 进行依赖收集 //readonly对象不用进行依赖收集
239 | if (!isReadonly) {
240 | track(target, key);
241 | }
242 | //如果是shallowReadonly类型,就不用执行内部嵌套的响应式转换。也不用执行依赖收集
243 | if (isShallow) {
244 | return res;
245 | }
246 | //reactive、readonly对象嵌套的响应式转换
247 | if (isObject(res)) {
248 | //递归调用
249 | // isReadonly == true -> 表明是readonly对象 :是reactive对象
250 | return isReadonly ? readonly(res) : reactive(res);
251 | }
252 | return res;
253 | };
254 | }
255 | function createSetter() {
256 | //只有reactive对象能够调用setter
257 | return function set(target, key, newVal) {
258 | const res = Reflect.set(target, key, newVal);
259 | //触发依赖
260 | trigger(target, key);
261 | return res;
262 | };
263 | }
264 | // reactive对象getter和setter
265 | const get = createGetter();
266 | const set = createSetter();
267 | const mutableHandlers = {
268 | get,
269 | set,
270 | };
271 | // readonly对象的getter和setter
272 | const readonlyGetter = createGetter(true);
273 | const readonlyHandlers = {
274 | get: readonlyGetter,
275 | set: function (target, key, newVal) {
276 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
277 | return true;
278 | },
279 | };
280 | const shallowReadonlyGetter = createGetter(true, true);
281 | const shallowReadonlyHandlers = {
282 | get: shallowReadonlyGetter,
283 | set: function (target, key, newVal) {
284 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
285 | return true;
286 | },
287 | };
288 |
289 | // import { track, trigger } from "./effect";
290 | // import { track, trigger } from "./effect";
291 | /**
292 | * reative 和 readonly 的get和set重复代码较多,进行代码抽取重构
293 | *
294 | */
295 | // 工具函数
296 | function createReactiveObject(target, baseHandler) {
297 | if (!isObject(target)) {
298 | console.warn(`target${target}必须是一个对象`);
299 | return target;
300 | }
301 | return new Proxy(target, baseHandler);
302 | }
303 | //reactive函数
304 | // export function reactive(obj) {
305 | // return new Proxy(obj, {
306 | // // get(target, key) {
307 | // // //ToDo 收集依赖
308 | // // track(target, key);
309 | // // const res = Reflect.get(target, key); //返回属性的值
310 | // // return res;
311 | // // },
312 | // // set(target, key, newVal) {
313 | // // //返回Boolean 返回 true 代表属性设置成功。 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。
314 | // // const res = Reflect.set(target, key, newVal); //返回一个 Boolean 值表明是否成功设置属性。
315 | // // //ToDo 触发依赖依赖
316 | // // trigger(target, key);
317 | // // return res;
318 | // // },
319 | // get,
320 | // set,
321 | // });
322 | // }
323 | function reactive(obj) {
324 | return createReactiveObject(obj, mutableHandlers);
325 | }
326 | //readonly函数 只读不能修改
327 | // export function readonly(obj) {
328 | // return new Proxy(obj, {
329 | // // get(target, key) {
330 | // // return Reflect.get(target, key);
331 | // // },
332 | // // set(target, key, newValue) {
333 | // // console.warn(
334 | // // `target:${target} 对象是readonly对象,不能修改的属性 `,
335 | // // key,
336 | // // newValue
337 | // // );
338 | // // return true;
339 | // // },
340 | // get,
341 | // set,
342 | // });
343 | // }
344 | function readonly(obj) {
345 | return createReactiveObject(obj, readonlyHandlers);
346 | }
347 | //shallowReadonly
348 | //创建一个 proxy,使其自身的 property为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)
349 | // 自身property为readonly 内部嵌套不是readonly
350 | function shallowReadonly(obj) {
351 | return createReactiveObject(obj, shallowReadonlyHandlers);
352 | }
353 |
354 | function emit(instance, event, ...args) {
355 | console.log("emit" + event);
356 | // instance.props 有没有对应event的回调
357 | const { props } = instance;
358 | //tpp ->
359 | //先去写一个特定行为 -》 重构通用行为
360 | // const handler = props["onAdd"];
361 | //add-foo ->addFoo
362 | // const camelize = (str: string) => {
363 | // return str.replace(/-(\w)/g, (_, c: string) => {
364 | // return c ? c.toUpperCase() : "";
365 | // });
366 | // };
367 | // //addFoo ->AddFoo
368 | // const capitalize = (str: string) => {
369 | // return str.charAt(0).toUpperCase() + str.slice(1);
370 | // };
371 | // console.log(capitalize(event));
372 | // const str = capitalize(camelize(event));
373 | // AddFoo -> noAddFoo
374 | // const toHandlerKey = (str: string) => {
375 | // return str ? "on" + str : "";
376 | // };
377 | //add-foo -> addFoo
378 | let str = camelize(event);
379 | //addFoo -> AddFoo
380 | str = capitalize(str);
381 | const handlerName = toHandlerKey(str);
382 | const handler = props[handlerName];
383 | handler && handler(...args);
384 | }
385 |
386 | function initProps(instance, rawProps) {
387 | instance.props = rawProps || {};
388 | }
389 |
390 | const PublicPropertiesMap = {
391 | $el: (i) => i.vnode.el,
392 | $slots: (i) => i.slots,
393 | $props: (i) => i.props,
394 | };
395 | // * proxy的getter方法 get(target,key)
396 | const PublicInstanceProxyHandlers = {
397 | get({ _: instance }, key) {
398 | //这里的_:instance是解构
399 | // console.log("instance:", instance);
400 | //setupState
401 | const { setupState, props } = instance;
402 | // if (key in setupState) {
403 | // return setupState[key];
404 | // }
405 | if (hasOwn(setupState, key)) {
406 | return setupState[key];
407 | }
408 | else if (hasOwn(props, key)) {
409 | return props[key];
410 | }
411 | //key ->$el
412 | const publicGetter = PublicPropertiesMap[key];
413 | if (publicGetter) {
414 | return publicGetter(instance);
415 | }
416 | },
417 | };
418 |
419 | function initSlot(instance, children) {
420 | // instance.slots = Array.isArray(children) ? children : [children];
421 | //children Object
422 | // const slots = {};
423 | // for (const key in children) {
424 | // const value = children[key];
425 | // slots[key] = normalizeSlotsValue(value);
426 | // }
427 | //
428 | // const { vnode } = instance;
429 | // if (vnode.shapeFlag & shapeFlags.slot_children) {
430 | // console.log("isntance.slots:" + instance.slots);
431 | normalizeObjectSlots(children, instance.slots);
432 | // }
433 | // instance.slots = slots;
434 | }
435 | function normalizeObjectSlots(children, slots) {
436 | // console.log("isntance.slots:" + JSON.stringify(slots));\
437 | console.log("children", children);
438 | for (const key in children) {
439 | const value = children[key];
440 | slots[key] = (props) => normalizeSlotsValue(value(props));
441 | // slots[key] = (props) => normalizeSlotsValue(value);
442 | console.log("isntance.slots:" + slots);
443 | }
444 | }
445 | function normalizeSlotsValue(value) {
446 | return Array.isArray(value) ? value : [value];
447 | }
448 | /**
449 | * 父组件通过children 传递 slot 类型为对象类型
450 | *
451 | * {
452 | * key:(props)=>{h()}
453 | * }
454 | * 通过遍历将所有插槽通过 key value的形式 保存在instance.slot对象中 使用this.$slots可以访问得到
455 | * 在子组件中进行插槽渲染的时候 通过renderSlots(this.$slots,name,props)函数
456 | * 每一个slot 都是一个函数, renderSlots函数中会执行插槽函数 生成vnode对象或者vnode对象组成的数组result
457 | * 然后将生成的结果通过createVnode(Fragment,{},result)包裹成vnode对象
458 | *
459 | * 又因为 result是vnode对象或者vnode对象组成的数组,
460 | * createVnode 传入的children 只能是数组或者string
461 | * 所有如果是vnode对象则需要先包裹一个[]数组,normalizeSlotsValue就是这个职责
462 | *
463 | */
464 |
465 | // 创建当前组件实例对象
466 | function createComponentInstance(vnode, parent) {
467 | const component = {
468 | //初始化
469 | vnode,
470 | type: vnode.type,
471 | next: null,
472 | setupState: {},
473 | props: {},
474 | slots: {},
475 | provides: parent ? parent.provides : {},
476 | parent,
477 | emit: () => { },
478 | isMounted: false,
479 | subTree: {}, //! elemnt 树
480 | };
481 | component.emit = emit.bind(null, component); //绑定instance为this 并返回函数 这里emit是从外部引入的emit
482 | return component;
483 | }
484 | function setupComponent(instance) {
485 | // TODO
486 | // initProps()
487 | console.log(instance);
488 | // * 初始化props
489 | initProps(instance, instance.vnode.props);
490 | //! 初始化slot
491 | initSlot(instance, instance.vnode.children);
492 | setupStatefulComponent(instance);
493 | }
494 | function setupStatefulComponent(instance) {
495 | const Component = instance.type;
496 | //* 增加了代理对象
497 | //cxt
498 | console.log({ _: 123 });
499 | console.log({ _: instance });
500 | instance.proxy = new Proxy(//增加了代理对象
501 | { _: instance },
502 | // get(target, key) {
503 | // //setupState
504 | // const { setupState } = instance;
505 | // if (key in setupState) {
506 | // return setupState[key];
507 | // }
508 | // //key ->$el
509 | // if (key === "$el") {
510 | // return instance.vnode.el;
511 | // }
512 | // },
513 | PublicInstanceProxyHandlers);
514 | const { setup } = Component;
515 | if (setup) {
516 | // currentInstance = instance;
517 | setCurrentInstance(instance);
518 | const setupResult = setup(shallowReadonly(instance.props), {
519 | emit: instance.emit,
520 | });
521 | setCurrentInstance(null);
522 | handleSetupResult(instance, setupResult);
523 | }
524 | }
525 | function handleSetupResult(instance, setupResult) {
526 | // function Object
527 | // TODO function
528 | if (typeof setupResult === 'object') {
529 | instance.setupState = proxyRefs(setupResult); //setup返回值的ref对象 直接key访问,不用key.value
530 | }
531 | finishComponentSetup(instance);
532 | }
533 | function finishComponentSetup(instance) {
534 | const Component = instance.type;
535 | //如果用户不提供render函数 而是用的template
536 | if (compiler && !Component.render) {
537 | if (Component.template) {
538 | Component.render = compiler(Component.template);
539 | }
540 | }
541 | instance.render = Component.render;
542 | }
543 | //借助全局变量获取instccne
544 | let currentInstance = null;
545 | function getCurrentInstance() {
546 | return currentInstance;
547 | }
548 | function setCurrentInstance(instance) {
549 | currentInstance = instance;
550 | }
551 | let compiler;
552 | function registerRuntimeCompiler(_compiler) {
553 | compiler = _compiler;
554 | }
555 |
556 | function provide(key, value) {
557 | //存
558 | //key value 存在哪里???? 存在当前实例对象上
559 | //获取当前组件实例对象
560 | const currentInstance = getCurrentInstance();
561 | if (currentInstance) {
562 | let { provides } = currentInstance;
563 | const parentProvides = currentInstance.parent.provides;
564 | //init 不能每次都初始化,只有第一次初始化
565 | //判断初始化状态 当前组件的provides = parentProvides
566 | if (provides === parentProvides) {
567 | provides = currentInstance.provides = Object.create(parentProvides); //利用原型原型链的机制 来进行多层inject provides
568 | }
569 | provides[key] = value;
570 | }
571 | }
572 | function inject(key, defaultValue) {
573 | //取
574 | const currentInstance = getCurrentInstance();
575 | if (currentInstance) {
576 | const parentProvides = currentInstance.parent.provides;
577 | if (key in parentProvides) {
578 | return parentProvides[key];
579 | }
580 | else if (defaultValue) {
581 | if (typeof defaultValue === 'function') {
582 | return defaultValue();
583 | }
584 | return defaultValue;
585 | }
586 | }
587 | }
588 | /**
589 | *
590 | * 思路: provide提供数据 inject获取数据
591 | *
592 | *
593 | * 1、基础作用 父子间的provide和inject
594 | * provide(key,value)
595 | * provide在setup中使用, 我们通过getCurrentInstance 获取到当前组件实例,并将传入的参数通过key-value的形式保存到instance.provide上
596 | *
597 | *
598 | * inject(key)
599 | *
600 | * 在setup中使用, 通过getCurrentInstance 获取到组件实例,,通过instance.parent.provide来获取父组件的provide
601 | * 通过key 老获取到所需要的值
602 | *
603 | *
604 | * 2、跨层级的provide和inject
605 | *
606 | * 前面的实现只支持 父子间的provide和inject传递,更深层次的传递利用了原型和原型链的机制
607 | *
608 | * 父组件 parentProvide
609 | * 子组件 provide
610 | * 孙组件 inject
611 | *
612 | * provide currentInstance.provide = Object.create(parentProvide)
613 | *
614 | * 在孙组件中 使用inject(key) 查找key的value
615 | * 先找子组件提供的provide,找到就直接返回value,
616 | * 找不到就会通过原型链查找parentProvide上的key值,逐级向上直到找到值
617 | *
618 | * 3、init provide
619 | *
620 | * 在创建instance实例的时候, 我们设置 parent和 provide 的默认值、
621 | * parent = parentInstance
622 | * provide = parent ? parent.provide :{}
623 | * 所以 provide中我们获取的provide 和 parentProvide 在最初是一致的
624 | *
625 | * 在进行object.create()后 provide会变成一个空对象,其prototype 指向 parentProvide
626 | *
627 | * 然后在空对象上进行新值的添加,我们不能每次进行provide都进行一次object.create 这样,我们在同一个setup中 之前的多次的provide只会生效最后一次
628 | *
629 | * 所以我们只需要进行一次原型链的继承
630 | *
631 | * 而这一次就要在最初的时候进行, 就是provide = parentProvide
632 | */
633 |
634 | function shouldUpdateComponent(preVnode, nextVnode) {
635 | const { props: prevProps } = preVnode;
636 | const { props: nextProps } = nextVnode;
637 | for (const key in nextProps) {
638 | if (nextProps[key] !== prevProps[key]) {
639 | return true;
640 | }
641 | }
642 | return false;
643 | }
644 |
645 | // import { render } from "./renderer";
646 | function createAppAPI(render) {
647 | return function createApp(rootComponent) {
648 | return {
649 | mount(rootContainer) {
650 | const vnode = createVNode(rootComponent);
651 | render(vnode, rootContainer);
652 | },
653 | };
654 | };
655 | }
656 | // export function createApp(rootComponent) {
657 | // return {
658 | // mount(rootContainer) {
659 | // const vnode = createVNode(rootComponent);
660 | // render(vnode, rootContainer);
661 | // },
662 | // };
663 | // }
664 |
665 | const queue = [];
666 | let isFlushPending = false;
667 | function nextTick(fn) {
668 | return fn ? Promise.resolve().then(fn) : Promise.resolve();
669 | }
670 | function queueJobs(job) {
671 | if (!queue.includes(job)) {
672 | queue.push(job);
673 | }
674 | queueFlush();
675 | }
676 | function queueFlush() {
677 | if (isFlushPending)
678 | return;
679 | isFlushPending = true;
680 | // Promise.resolve().then(() => {
681 | // isFlushPending = false;
682 | // let job;
683 | // while ((job = queue.shift())) {
684 | // job && job();
685 | // }
686 | // });
687 | nextTick(flushJobs);
688 | }
689 | function flushJobs() {
690 | isFlushPending = false;
691 | let job;
692 | while ((job = queue.shift())) {
693 | job && job();
694 | }
695 | }
696 |
697 | function createRenderer(options) {
698 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
699 | function render(vnode, container) {
700 | // debugger;
701 | patch(null, vnode, container, null, null);
702 | }
703 | /**
704 | *
705 | * @param n1 老的vnode n1存在 更新逻辑 n1不存在 初始化逻辑
706 | * @param n2 新的vnode
707 | * @param container 容器
708 | * @param parentComponent 父组件 createInstance创建组件实例的时候 用得到
709 | */
710 | function patch(n1, n2, container, parentComponent, anchor) {
711 | //TODO 判断vnode是不是一个element
712 | //是element 就应该处理element
713 | //如何去区分是element类型和component类型 :vnode.type 来判断
714 | // console.log(vnode.type);
715 | // const { type, shapeFlag } = vnode;
716 | const { type, shapeFlag } = n2;
717 | //Fragment -> 只渲染children
718 | switch (type) {
719 | case Fragment:
720 | processFragment(n1, n2, container, parentComponent, anchor);
721 | break;
722 | case Text:
723 | processText(n1, n2, container);
724 | break;
725 | default:
726 | if (shapeFlag & 1 /* shapeFlags.element */) {
727 | // if (typeof vnode.type === "string") {
728 | // element类型
729 | processElement(n1, n2, container, parentComponent, anchor);
730 | // } else if (isObject(vnode.type)) {
731 | }
732 | else if (shapeFlag & 2 /* shapeFlags.stateful_component */) {
733 | // component类型
734 | processComponent(n1, n2, container, parentComponent, anchor);
735 | }
736 | }
737 | }
738 | function processFragment(n1, n2, container, parentComponent, anchor) {
739 | mountChildren(n2, container, parentComponent, anchor);
740 | }
741 | function processText(n1, n2, container) {
742 | const { children } = n2;
743 | const textNode = (n2.el = document.createTextNode(children));
744 | container.appendChild(textNode);
745 | }
746 | //element vnode.type为element类型
747 | function processElement(n1, n2, container, parentComponent, anchor) {
748 | //init 初始化
749 | if (!n1) {
750 | mountElement(n2, container, parentComponent, anchor);
751 | }
752 | else {
753 | //TODO UPDATE
754 | console.log('patchElement');
755 | patchElement(n1, n2, container, parentComponent, anchor);
756 | }
757 | }
758 | //挂载element
759 | function mountElement(vnode, container, parentComponent, anchor) {
760 | //跨平台渲染
761 | //canvas
762 | // new Element()
763 | //Dom平台
764 | // const el = document.createElement("div")
765 | // const el = (n2.el = document.createElement(n2.type));
766 | const el = (vnode.el = hostCreateElement(vnode.type));
767 | //props
768 | // el.setttribute("id", "root");
769 | const { props } = vnode;
770 | for (const key in props) {
771 | const val = props[key];
772 | // console.log(key);
773 | // // const isOn = (key) => /^on[A-Z]/.test(key);
774 | // // if(isOn(key)){
775 | // if (key.startsWith("on")) {
776 | // // console.log(key.split("on")[1]);
777 | // const event = key.slice(2).toLowerCase();
778 | // el.addEventListener(event, val);
779 | // } else {
780 | // el.setAttribute(key, val);
781 | // }
782 | hostPatchProp(el, key, null, val);
783 | }
784 | //children
785 | // el.textContent = "hi mini-vue";
786 | const { children, shapeFlag } = vnode;
787 | if (shapeFlag & 4 /* shapeFlags.text_children */) {
788 | // if (typeof children === "string") {
789 | //children为srting类型
790 | el.textContent = children;
791 | // } else if (Array.isArray(children)) {
792 | }
793 | else if (shapeFlag & 8 /* shapeFlags.array_children */) {
794 | //children 是数组类型
795 | // children.forEach((v) => {
796 | // patch(v, el);
797 | // });
798 | mountChildren(vnode, el, parentComponent, anchor);
799 | }
800 | //挂载要渲染的el
801 | // document.appendChild(el)
802 | // container.appendChild(el);
803 | // container.append(el);
804 | hostInsert(el, container, anchor);
805 | }
806 | function mountChildren(childrenVnode, container, parentComponent, anchor) {
807 | childrenVnode.children.forEach((v) => {
808 | patch(null, v, container, parentComponent, anchor);
809 | });
810 | }
811 | //更新element
812 | function patchElement(n1, n2, container, parentComponent, anchor) {
813 | console.log('n1:', n1);
814 | console.log('n2:', n2);
815 | //type
816 | //props
817 | const oldProps = n1.props || EMPTY_OBJ;
818 | const newProps = n2.props || EMPTY_OBJ;
819 | const el = (n2.el = n1.el);
820 | //1、key不变 value 改变
821 | //2、 value= undefined 、null ==> 删除key
822 | //3、 老的vnode 里的key 在新的element vnode不存在了 ==> 删除
823 | patchProps(el, oldProps, newProps);
824 | // children
825 | patchChildren(n1, n2, el, parentComponent, anchor);
826 | }
827 | // const EMPTY_OBJ = {}
828 | function patchProps(el, oldProps, newProps) {
829 | // debugger;
830 | if (oldProps !== newProps) {
831 | for (const key in newProps) {
832 | const prevProp = oldProps[key];
833 | const nextProp = newProps[key];
834 | if (prevProp !== nextProp) {
835 | hostPatchProp(el, key, prevProp, nextProp);
836 | }
837 | }
838 | //第三个场景
839 | if (oldProps !== EMPTY_OBJ) {
840 | for (const key in oldProps) {
841 | if (!(key in newProps)) {
842 | hostPatchProp(el, key, oldProps[key], null);
843 | }
844 | }
845 | }
846 | }
847 | }
848 | function patchChildren(n1, n2, container, parentComponent, anchor) {
849 | // ArrayToText
850 | //判断新节点的shapeFlag 判断 children是text还是array
851 | // const prevShapeFlag = n1.shapeFlag;
852 | const { shapeFlag: prevShapeFlag } = n1.shapeFlag;
853 | const { shapeFlag } = n2;
854 | const c1 = n1.children;
855 | const c2 = n2.children;
856 | if (shapeFlag & 4 /* shapeFlags.text_children */) {
857 | //新节点children为 text类型
858 | if (prevShapeFlag & 8 /* shapeFlags.array_children */) {
859 | //老节点children 为array类型
860 | //1、把老节点清空
861 | unmountedChildren(n1.children);
862 | //2、设置text
863 | hostSetElementText(container, c2);
864 | }
865 | else {
866 | //老节点children为 text类型
867 | if (c1 !== c2) {
868 | //设置text
869 | hostSetElementText(container, c2);
870 | }
871 | }
872 | }
873 | else {
874 | //新节点children为 array类型
875 | if (prevShapeFlag & 4 /* shapeFlags.text_children */) {
876 | //老节点children为text
877 | // 1、清空老节点
878 | hostSetElementText(container, '');
879 | // 2、设置新节点
880 | mountChildren(n2, container, parentComponent, anchor);
881 | }
882 | else {
883 | //老节点children 为array类型
884 | patchKeyChildren(c1, c2, container, parentComponent, anchor);
885 | }
886 | }
887 | }
888 | function unmountedChildren(children) {
889 | for (let i = 0; i < children.length; i++) {
890 | const el = children[i].el;
891 | //remove
892 | hostRemove(el);
893 | }
894 | }
895 | function patchKeyChildren(c1, c2, container, parentComponent, anchor) {
896 | // debugger;
897 | let i = 0;
898 | let e1 = c1.length - 1;
899 | let e2 = c2.length - 1;
900 | //1.左侧对比
901 | while (i <= e1 && i <= e2) {
902 | const n1 = c1[i];
903 | const n2 = c2[i];
904 | if (isSameVnodeType(n1, n2)) {
905 | patch(n1, n2, container, parentComponent, anchor);
906 | }
907 | else {
908 | break;
909 | }
910 | i++;
911 | }
912 | //2.右侧对比
913 | while (i <= e1 && i <= e2) {
914 | const n1 = c1[e1];
915 | const n2 = c2[e2];
916 | if (isSameVnodeType(n1, n2)) {
917 | patch(n1, n2, container, parentComponent, anchor);
918 | }
919 | else {
920 | break;
921 | }
922 | e1--;
923 | e2--;
924 | }
925 | //3.新的比老的多 添加到指定的位置
926 | if (i > e1) {
927 | if (i <= e2) {
928 | const nextPos = e2 + 1;
929 | // const anchor = i + 1 < c2.length ? c2[nextPos].el : null; 有bug
930 | const anchor = nextPos < c2.length ? c2[nextPos].el : null;
931 | while (i <= e2) {
932 | patch(null, c2[i], container, parentComponent, anchor);
933 | i++;
934 | }
935 | }
936 | }
937 | else if (i > e2) {
938 | //4、新的比老少
939 | while (i <= e1) {
940 | hostRemove(c1[i].el);
941 | i++;
942 | }
943 | }
944 | else {
945 | //中间对比
946 | let s1 = i;
947 | let s2 = i;
948 | let patched = 0;
949 | const toBePatch = e2 - s2 + 1; //记录新节点的数量 用于老节点多余新节点时 删除逻辑的优化
950 | console.log(s2, e2);
951 | console.log('toBePatch', toBePatch);
952 | //建立新child.key的映射表
953 | const keyToNewIndexMap = new Map();
954 | //简历 最长递归子序列的映射表 最终结果是中间老节点新节点都有的节点在老节点数组的索引+1 排序
955 | const newIndexToOldIndexMap = new Array(toBePatch);
956 | let moved = false;
957 | let maxNewIndexSoFar = 0;
958 | //初始化 最长递归子序列的映射表
959 | for (let i = 0; i < toBePatch; i++) {
960 | newIndexToOldIndexMap[i] = 0;
961 | }
962 | //将新的需要更新的(key:索引) 添加到映射表中
963 | for (let i = s2; i <= e2; i++) {
964 | let nextChild = c2[i];
965 | keyToNewIndexMap.set(nextChild.key, i);
966 | }
967 | for (let i = s1; i <= e1; i++) {
968 | const prevChild = c1[i];
969 | if (patched >= toBePatch) {
970 | //patch的数量大于需要更新的数量时 表示新的需要更新的已经更新完毕,剩下多的可以直接删除
971 | hostRemove(prevChild.el);
972 | continue;
973 | }
974 | let newIndex;
975 | if (prevChild.key != null) {
976 | //有key情况
977 | newIndex = keyToNewIndexMap.get(prevChild.key);
978 | }
979 | else {
980 | //无key
981 | for (let j = s2; j <= e2; j++) {
982 | if (isSameVnodeType(prevChild, c2[j])) {
983 | newIndex = j;
984 | break;
985 | }
986 | }
987 | }
988 | //删除逻辑
989 | if (newIndex === undefined) {
990 | console.log('删除');
991 | hostRemove(prevChild.el);
992 | }
993 | else {
994 | if (newIndex >= maxNewIndexSoFar) {
995 | maxNewIndexSoFar = newIndex;
996 | }
997 | else {
998 | moved = true;
999 | }
1000 | //新老节点建立映射关系
1001 | newIndexToOldIndexMap[newIndex - s2] = i + 1; //i+1 避免i=0的情况
1002 | //更新逻辑
1003 | patch(prevChild, c2[newIndex], container, parentComponent, null);
1004 | patched++; //更新一次 数量+1
1005 | }
1006 | }
1007 | //得到最长递增子序列
1008 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
1009 | console.log('newIndexToOldIndexMap', newIndexToOldIndexMap);
1010 | console.log('最长递增子序列', increasingNewIndexSequence);
1011 | let j = increasingNewIndexSequence.length - 1;
1012 | for (let i = toBePatch - 1; i >= 0; i--) {
1013 | const nextIndex = i + s2;
1014 | const nextChild = c2[nextIndex];
1015 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null;
1016 | if (newIndexToOldIndexMap[i] === 0) {
1017 | console.log('新增');
1018 | patch(null, nextChild, container, parentComponent, anchor);
1019 | }
1020 | else if (moved) {
1021 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
1022 | console.log('移动位置');
1023 | hostInsert(nextChild.el, container, anchor);
1024 | }
1025 | else {
1026 | j--;
1027 | }
1028 | }
1029 | }
1030 | }
1031 | }
1032 | function isSameVnodeType(n1, n2) {
1033 | return n1.type === n2.type && n1.key === n2.key;
1034 | }
1035 | //componentvnode.type为component类型
1036 | function processComponent(n1, n2, container, parentComponent, anchor) {
1037 | if (!n1) {
1038 | mountComponent(n1, n2, container, parentComponent, anchor);
1039 | }
1040 | else {
1041 | updateComponent(n1, n2);
1042 | }
1043 | }
1044 | //组件初始化
1045 | function mountComponent(n1, initialVNode, container, parentComponent, anchor) {
1046 | // * 创建当前组件实例 方便后续对当前组件的操作
1047 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
1048 | // * 处理组件setup函数
1049 | setupComponent(instance);
1050 | setupRenderEffect(instance, initialVNode, container, anchor);
1051 | }
1052 | function setupRenderEffect(instance, initialVNode, container, anchor) {
1053 | //响应式
1054 | instance.update = effect(() => {
1055 | // 区分式初始化还是更新
1056 | if (!instance.isMounted) {
1057 | //init
1058 | console.log('init');
1059 | const { proxy } = instance;
1060 | // instance.subtree 用于存储当前组件的render函数生成 vnode? 是否可以称为vnode?
1061 | const subTree = (instance.subTree = instance.render.call(proxy, proxy)); //subTree 虚拟节点树 vnode树
1062 | console.log(subTree);
1063 | patch(null, subTree, container, instance, anchor);
1064 | //element ->mount
1065 | // ! 将render生成的vnode的el 存储到组件的el
1066 | initialVNode.el = subTree.el;
1067 | instance.isMounted = true;
1068 | }
1069 | else {
1070 | //update
1071 | console.log('update');
1072 | const { proxy, next, vnode } = instance;
1073 | if (next) {
1074 | next.el = vnode.el;
1075 | updateComponentPreRender(instance, next);
1076 | }
1077 | const subTree = instance.render.call(proxy, proxy); //subTree 虚拟节点树 vnode树
1078 | console.log(subTree);
1079 | const preSubTree = instance.subTree;
1080 | console.log(preSubTree);
1081 | console.log(subTree);
1082 | instance.subTree = subTree; //将现在的subTree更新到instance.subTree 方便后续再次更新
1083 | patch(preSubTree, subTree, container, instance, anchor);
1084 | }
1085 | }, {
1086 | scheduler() {
1087 | console.log('scheduler执行啦');
1088 | queueJobs(instance.update);
1089 | },
1090 | });
1091 | }
1092 | function updateComponent(n1, n2) {
1093 | //获取到挂载到vnode上的component
1094 | const instance = (n2.component = n1.component);
1095 | if (shouldUpdateComponent(n1, n2)) {
1096 | instance.next = n2;
1097 | instance.update(); // 利用effect返回值runner 调用runner()会再次执行fn
1098 | }
1099 | else {
1100 | n2.el = n1.el;
1101 | instance.vnode = n2;
1102 | }
1103 | }
1104 | return {
1105 | createApp: createAppAPI(render),
1106 | };
1107 | }
1108 | //最长递增子序列 return 递归子序列的索引数组
1109 | function getSequence(arr) {
1110 | const p = arr.slice();
1111 | const result = [0];
1112 | let i, j, u, v, c;
1113 | const len = arr.length;
1114 | for (i = 0; i < len; i++) {
1115 | const arrI = arr[i];
1116 | if (arrI !== 0) {
1117 | j = result[result.length - 1];
1118 | if (arr[j] < arrI) {
1119 | p[i] = j;
1120 | result.push(i);
1121 | continue;
1122 | }
1123 | u = 0;
1124 | v = result.length - 1;
1125 | while (u < v) {
1126 | c = (u + v) >> 1;
1127 | if (arr[result[c]] < arrI) {
1128 | u = c + 1;
1129 | }
1130 | else {
1131 | v = c;
1132 | }
1133 | }
1134 | if (arrI < arr[result[u]]) {
1135 | if (u > 0) {
1136 | p[i] = result[u - 1];
1137 | }
1138 | result[u] = i;
1139 | }
1140 | }
1141 | }
1142 | u = result.length;
1143 | v = result[u - 1];
1144 | while (u-- > 0) {
1145 | result[u] = v;
1146 | v = p[v];
1147 | }
1148 | return result;
1149 | }
1150 | //组件更新之前 需要将组件中的props 等数据先更新
1151 | function updateComponentPreRender(instance, nextVndoe) {
1152 | instance.vnode = nextVndoe;
1153 | instance.next = null;
1154 | instance.props = nextVndoe.props;
1155 | }
1156 |
1157 | // export function ref(val) {
1158 | // ref接口的实现类 对操作进行封装
1159 | class RefImpl {
1160 | constructor(value) {
1161 | this.__v_isRef = true; // 判断是ref的标识
1162 | // 将传入的值赋值给实例的私有属性property_value
1163 | this._rawValue = value;
1164 | //value 为对象的话 需要转换为reactive包裹value
1165 | // this._value = isObject(value) ? reactive(value) : value;
1166 | this._value = convert(value);
1167 | this.dep = new Set();
1168 | }
1169 | get value() {
1170 | if (isTracking()) {
1171 | // 进行依赖收集
1172 | trackEffect(this.dep);
1173 | }
1174 | return this._value;
1175 | }
1176 | set value(val) {
1177 | //如果value是reactive对象的时候 this._value 为Proxy
1178 | // 提前声明一个this._rawValue 来存储并进行比较
1179 | if (Object.is(val, this._rawValue))
1180 | return; // ref.value = 2 ref.value = 2 两次赋值相同值的操作 不会执行effect
1181 | this._rawValue = val;
1182 | // this._value = isObject(val) ? reactive(val) : val;
1183 | this._value = convert(val); //处理值 如果是对象 ->转为reactive对象 不是对象 返回原值
1184 | triggerEffect(this.dep);
1185 | }
1186 | }
1187 | function convert(val) {
1188 | return isObject(val) ? reactive(val) : val;
1189 | }
1190 | //ref
1191 | function ref(val) {
1192 | return new RefImpl(val);
1193 | }
1194 | //isRef
1195 | function isRef(val) {
1196 | return !!(val.__v_isRef === true);
1197 | }
1198 | //unRef
1199 | function unRef(val) {
1200 | return isRef(val) ? val.value : val;
1201 | }
1202 | //proxyRef 应用场景: template中使用setup中return的ref 不需要使用ref.value
1203 | function proxyRefs(objectWithRefs) {
1204 | //怎么知道调用getter 和setter ? ->proxy
1205 | return new Proxy(objectWithRefs, {
1206 | get(target, key) {
1207 | //get -> age(ref) 那么就给他返回 .value
1208 | // not ref -> return value
1209 | return unRef(Reflect.get(target, key));
1210 | },
1211 | set(target, key, newVal) {
1212 | //当前需要修改的值是ref对象,同时修改值不是ref
1213 | if (isRef(target[key]) && !isRef(newVal)) {
1214 | target[key].value = newVal;
1215 | return true;
1216 | }
1217 | else {
1218 | return Reflect.set(target, key, newVal);
1219 | }
1220 | },
1221 | });
1222 | }
1223 |
1224 | function add(a, b) {
1225 | return a + b;
1226 | }
1227 |
1228 | //创建element元素
1229 | function createElement(type) {
1230 | return document.createElement(type);
1231 | }
1232 | // function patchProp(el, key, preVal, nextVal) {
1233 | // // const isOn = (key) => /^on[A-Z]/.test(key);
1234 | // // if(isOn(key)){
1235 | // if (key.startsWith("on")) {
1236 | // // console.log(key.split("on")[1]);
1237 | // const event = key.slice(2).toLowerCase();
1238 | // el.addEventListener(event, nextVal);
1239 | // } else {
1240 | // if (nextVal === undefined || nextVal === null) {
1241 | // el.removeAttribute(key);
1242 | // } else {
1243 | // el.setAttribute(key, nextVal);
1244 | // }
1245 | // }
1246 | // }
1247 | //创建、更新props
1248 | function patchProp(el, key, prevVal, nextVal) {
1249 | const isOn = (key) => /^on[A-Z]/.test(key);
1250 | if (isOn(key)) {
1251 | const event = key.slice(2).toLowerCase();
1252 | el.addEventListener(event, nextVal);
1253 | }
1254 | else {
1255 | if (nextVal === "undefined" || nextVal === null) {
1256 | el.removeAttribute(key);
1257 | }
1258 | else {
1259 | el.setAttribute(key, nextVal);
1260 | }
1261 | }
1262 | }
1263 | //指定位置插入
1264 | function insert(child, parent, anchor) {
1265 | //只是添加到后面
1266 | // parent.append(child);
1267 | // 添加到指定位置
1268 | parent.insertBefore(child, anchor || null);
1269 | }
1270 | //删除children
1271 | function remove(child) {
1272 | const parent = child.parentNode;
1273 | if (parent) {
1274 | parent.removeChild(child);
1275 | }
1276 | }
1277 | function setElementText(el, text) {
1278 | el.textContent = text;
1279 | }
1280 | const renderer = createRenderer({
1281 | createElement,
1282 | patchProp,
1283 | insert,
1284 | remove,
1285 | setElementText,
1286 | });
1287 | function createApp(...args) {
1288 | return renderer.createApp(...args);
1289 | }
1290 |
1291 | var runtimeDom = /*#__PURE__*/Object.freeze({
1292 | __proto__: null,
1293 | createApp: createApp,
1294 | h: h,
1295 | renderSlots: renderSlots,
1296 | createTextVnode: createTextVnode,
1297 | createElementVnode: createVNode,
1298 | getCurrentInstance: getCurrentInstance,
1299 | registerRuntimeCompiler: registerRuntimeCompiler,
1300 | provide: provide,
1301 | inject: inject,
1302 | createRenderer: createRenderer,
1303 | nextTick: nextTick,
1304 | toDisplayString: toDisplayString,
1305 | ref: ref,
1306 | proxyRefs: proxyRefs,
1307 | add: add
1308 | });
1309 |
1310 | const To_Display_String = Symbol("toDisplayString");
1311 | const Create_ELEMENT_Vnode = Symbol("createElementVnode");
1312 | const helperMapName = {
1313 | [To_Display_String]: "toDisplayString ",
1314 | [Create_ELEMENT_Vnode]: "createElementVnode ",
1315 | };
1316 |
1317 | // import { isString } from "../../shared";
1318 | function generate(ast) {
1319 | const context = createCodegenContext();
1320 | const { push } = context;
1321 | genFunctionPreamble(ast, context);
1322 | const functionName = "render";
1323 | const args = ["_ctx", "_cache"];
1324 | const signature = args.join(", ");
1325 | push(`function ${functionName}(${signature}){`);
1326 | push("return ");
1327 | genNode(ast.codegenNode, context);
1328 | push("}");
1329 | return {
1330 | code: context.code,
1331 | };
1332 | }
1333 | function genFunctionPreamble(ast, context) {
1334 | const { push } = context;
1335 | const VueBinging = "Vue";
1336 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`;
1337 | if (ast.helpers.length > 0) {
1338 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`);
1339 | }
1340 | push("\n");
1341 | push("return ");
1342 | }
1343 | function createCodegenContext() {
1344 | const context = {
1345 | code: "",
1346 | push(source) {
1347 | context.code += source;
1348 | },
1349 | helper(key) {
1350 | return `_${helperMapName[key]}`;
1351 | },
1352 | };
1353 | return context;
1354 | }
1355 | function genNode(node, context) {
1356 | switch (node.type) {
1357 | case 3 /* NodeTypes.TEXT */:
1358 | genText(node, context);
1359 | break;
1360 | case 0 /* NodeTypes.INTERPOLATION */:
1361 | genInterpolation(node, context);
1362 | break;
1363 | case 1 /* NodeTypes.SIMPLE_EXPRESSION */:
1364 | genExpression(node, context);
1365 | break;
1366 | case 2 /* NodeTypes.ELEMENT */:
1367 | genElement(node, context);
1368 | break;
1369 | case 5 /* NodeTypes.COMPOUND_EXPRESSION */:
1370 | genCompoundExpression(node, context);
1371 | break;
1372 | }
1373 | }
1374 | function genCompoundExpression(node, context) {
1375 | const { push } = context;
1376 | const children = node.children;
1377 | for (let i = 0; i < children.length; i++) {
1378 | const child = children[i];
1379 | if (isString(child)) {
1380 | push(child);
1381 | }
1382 | else {
1383 | genNode(child, context);
1384 | }
1385 | }
1386 | }
1387 | function genElement(node, context) {
1388 | const { push, helper } = context;
1389 | const { tag, children, props } = node;
1390 | push(`${helper(Create_ELEMENT_Vnode)}(`);
1391 | genNodeList(genNullable([tag, props, children]), context);
1392 | push(")");
1393 | }
1394 | function genNodeList(nodes, context) {
1395 | const { push } = context;
1396 | for (let i = 0; i < nodes.length; i++) {
1397 | const node = nodes[i];
1398 | if (isString(node)) {
1399 | push(node);
1400 | }
1401 | else {
1402 | genNode(node, context);
1403 | }
1404 | if (i < nodes.length - 1) {
1405 | push(", ");
1406 | }
1407 | }
1408 | }
1409 | function genNullable(args) {
1410 | return args.map((arg) => arg || "null");
1411 | }
1412 | function genExpression(node, context) {
1413 | const { push } = context;
1414 | push(`${node.content}`);
1415 | }
1416 | function genInterpolation(node, context) {
1417 | const { push, helper } = context;
1418 | push(`${helper(To_Display_String)}(`);
1419 | genNode(node.content, context);
1420 | push(")");
1421 | }
1422 | function genText(node, context) {
1423 | const { push } = context;
1424 | push(`'${node.content}'`);
1425 | }
1426 |
1427 | function baseParse(content) {
1428 | //message
1429 | const context = createParseContext(content); //{source:message}
1430 | //重构
1431 | return createRoot(parseChildren(context, [])); //
1432 | // return {
1433 | // children: [
1434 | // {
1435 | // type: "interpolation",
1436 | // content: { type: "simple_expression", content: "message" },
1437 | // },
1438 | // ],
1439 | // };
1440 | // return createRoot([
1441 | // {
1442 | // type: "interpolation",
1443 | // content: { type: "simple_expression", content: "message" },
1444 | // },
1445 | // ]);
1446 | }
1447 | function createParseContext(content) {
1448 | return {
1449 | source: content,
1450 | };
1451 | }
1452 | function createRoot(children) {
1453 | return {
1454 | children,
1455 | type: 4 /* NodeTypes.ROOT */,
1456 | };
1457 | }
1458 | function parseChildren(context, ancestors) {
1459 | // ancestors 祖先
1460 | const nodes = [];
1461 | while (!isEnd(context, ancestors)) {
1462 | let node;
1463 | //解析插值{{}}
1464 | if (context.source.startsWith("{{")) {
1465 | node = parseInterpolation(context);
1466 | }
1467 | else if (context.source[0] === "<") {
1468 | //element类型
1469 | if (/[a-z]/.test(context.source[1])) {
1470 | console.log("parse.element");
1471 | node = parseElement(context, ancestors);
1472 | }
1473 | }
1474 | // } else {
1475 | // //text类型
1476 | // node = parseText(context);
1477 | // }
1478 | if (!node) {
1479 | node = parseText(context);
1480 | }
1481 | nodes.push(node);
1482 | }
1483 | return nodes;
1484 | // return [
1485 | // {
1486 | // type: "interpolation",
1487 | // content: { type: "simple_expression", content: "message" },
1488 | // },
1489 | // ];
1490 | }
1491 | function isEnd(context, ancestors) {
1492 | //
1493 | //结束的条件
1494 | //1、context.source 没有值的时候
1495 | //2、当遇到结束标签时候
1496 | //2
1497 | const s = context.source;
1498 | //
1499 | if (s.startsWith("")) {
1500 | for (let i = ancestors.length - 1; i >= 0; i--) {
1501 | const tag = ancestors[i].tag;
1502 | if (startsWithEndTagOpen(s, tag)) {
1503 | return true;
1504 | }
1505 | }
1506 | }
1507 | // if (parentTag && s.startsWith(`${parentTag}>`)) {
1508 | // return true;
1509 | // }
1510 | //1
1511 | return !context.source;
1512 | }
1513 | //element类型parse
1514 | function parseElement(context, ancestors) {
1515 | //implement
1516 | // 1、解析tag
1517 | // 2、删除处理完成的代码
1518 | //1
1519 | // const match: any = /^<([a-z]*)/i.exec(context.source); //
1520 | // console.log(match); // [ '', groups: undefined ]
1521 | // const tag = match[1];
1522 | // console.log(tag); //div
1523 | // //2 删除处理完成的代码
1524 | // advanceBy(context, match[0].length);
1525 | // console.log(context.source); //>
1526 | // advanceBy(context, 1);
1527 | // console.log(context.source); //
1528 | // return {
1529 | // type: NodeTypes.ELEMENT,
1530 | // tag,
1531 | // };
1532 | // 重构
1533 | const element = parseTag(context, 0 /* TagType.Start */);
1534 | ancestors.push(element);
1535 | element.children = parseChildren(context, ancestors);
1536 | ancestors.pop();
1537 | // if (context.source.slice(2, 2 + element.tag.length) === element.tag) {
1538 | //重构
1539 | if (startsWithEndTagOpen(context.source, element.tag)) {
1540 | parseTag(context, 1 /* TagType.End */);
1541 | }
1542 | else {
1543 | throw new Error(`缺少结束标签:${element.tag}`);
1544 | }
1545 | console.log("-------", context.source);
1546 | return element;
1547 | }
1548 | function parseTag(context, TagType) {
1549 | //implement
1550 | // 1、解析tag
1551 | // 2、删除处理完成的代码
1552 | //1
1553 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1554 | console.log(match); // [ '', groups: undefined ]
1555 | const tag = match[1];
1556 | console.log(tag); //div
1557 | // 2
1558 | advanceBy(context, match[0].length); //>
1559 | advanceBy(context, 1); //
1560 | if (tag === TagType.End)
1561 | return;
1562 | return {
1563 | type: 2 /* NodeTypes.ELEMENT */,
1564 | tag,
1565 | };
1566 | }
1567 | //{{}}插值类型parse
1568 | function parseInterpolation(context) {
1569 | console.log(context);
1570 | const openDelimiter = "{{";
1571 | const closeDelimiter = "}}";
1572 | //{{message}}解析
1573 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length);
1574 | advanceBy(context, openDelimiter.length);
1575 | // context.source = context.source.slice(openDelimiter.length); // message}}
1576 | console.log(context.source);
1577 | const rawContentLength = closeIndex - openDelimiter.length;
1578 | // const rawContent = context.source.slice(0, rawContentLength); // message
1579 | const rawContent = parseTextData(context, rawContentLength);
1580 | //边缘处理 利于 {{ message}} 双括号中有空格
1581 | const content = rawContent.trim();
1582 | console.log(content);
1583 | // 例如{{message}} {{message}}处理完毕的 删除掉 剩下
1584 | // context.source = context.source.slice(closeDelimiter.length);
1585 | advanceBy(context, closeDelimiter.length);
1586 | console.log("context.source", context.source);
1587 | return {
1588 | type: 0 /* NodeTypes.INTERPOLATION */,
1589 | content: { type: 1 /* NodeTypes.SIMPLE_EXPRESSION */, content: content }, //"simple_expression"
1590 | };
1591 | }
1592 | //TEXT类型parse
1593 | function parseText(context) {
1594 | //1、获取content
1595 | //2、推进代码 : 删除已经处理的代码
1596 | //1
1597 | // const content = context.source.slice(0, context.source.length);
1598 | // console.log(content);
1599 | //2
1600 | // advanceBy(context, content.length);
1601 | // console.log(context.source);
1602 | let endIndex = context.source.length;
1603 | let endTokens = ["<", "{{"];
1604 | for (let i = 0; i < endTokens.length; i++) {
1605 | const index = context.source.indexOf(endTokens[i]);
1606 | if (index !== -1 && endIndex > index) {
1607 | //!==-1 证明没有插值
1608 | endIndex = index;
1609 | }
1610 | }
1611 | //重构
1612 | const content = parseTextData(context, endIndex);
1613 | console.log("content _______", content);
1614 | return {
1615 | type: 3 /* NodeTypes.TEXT */,
1616 | content,
1617 | };
1618 | }
1619 | //推进工具函数
1620 | function advanceBy(context, length) {
1621 | context.source = context.source.slice(length);
1622 | }
1623 | //裁剪工具函数
1624 | function parseTextData(context, length) {
1625 | const content = context.source.slice(0, length);
1626 | advanceBy(context, length);
1627 | return content;
1628 | }
1629 | function startsWithEndTagOpen(source, tag) {
1630 | return source.startsWith("") && source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase();
1631 | }
1632 |
1633 | function transform(root, options = {}) {
1634 | const context = createTransformContext(root, options);
1635 | //1、遍历 ——深度优先搜索 递归
1636 | // 2、修改text content
1637 | traverseNode(root, context);
1638 | createRootCodegen(root);
1639 | root.helpers = [...context.helpers.keys()];
1640 | }
1641 | function createTransformContext(root, options) {
1642 | const context = {
1643 | root,
1644 | nodeTransforms: options.nodeTransforms || [],
1645 | helpers: new Map(),
1646 | helper(key) {
1647 | context.helpers.set(key, 1);
1648 | },
1649 | };
1650 | return context;
1651 | }
1652 | function traverseNode(node, context) {
1653 | console.log(node);
1654 | //1.element
1655 | const nodeTransforms = context.nodeTransforms;
1656 | const onExitFns = [];
1657 | for (let i = 0; i < nodeTransforms.length; i++) {
1658 | const transform = nodeTransforms[i];
1659 | const onExit = transform(node, context);
1660 | if (onExit) {
1661 | onExitFns.push(onExit);
1662 | }
1663 | }
1664 | switch (node.type) {
1665 | case 0 /* NodeTypes.INTERPOLATION */:
1666 | context.helper(To_Display_String);
1667 | break;
1668 | case 4 /* NodeTypes.ROOT */:
1669 | case 2 /* NodeTypes.ELEMENT */:
1670 | traverseChildren(node, context);
1671 | }
1672 | let i = onExitFns.length;
1673 | while (i--) {
1674 | onExitFns[i]();
1675 | }
1676 | // traverseChildren(node, context);
1677 | }
1678 | function traverseChildren(node, context) {
1679 | const children = node.children;
1680 | for (let i = 0; i < children.length; i++) {
1681 | const node = children[i];
1682 | traverseNode(node, context);
1683 | }
1684 | }
1685 | function createRootCodegen(root) {
1686 | const child = root.children[0];
1687 | if (child.type === 2 /* NodeTypes.ELEMENT */) {
1688 | root.codegenNode = child.codegenNode;
1689 | }
1690 | else {
1691 | root.codegenNode = root.children[0];
1692 | }
1693 | }
1694 |
1695 | function createVNodeCall(context, tag, props, children) {
1696 | context.helper(Create_ELEMENT_Vnode);
1697 | return {
1698 | type: 2 /* NodeTypes.ELEMENT */,
1699 | tag,
1700 | props,
1701 | children,
1702 | };
1703 | }
1704 |
1705 | function transformElement(node, context) {
1706 | if (node.type === 2 /* NodeTypes.ELEMENT */) {
1707 | return () => {
1708 | context.helper(Create_ELEMENT_Vnode);
1709 | // 中间处理层
1710 | const vnodeTag = `"${node.tag}"`;
1711 | //props
1712 | let vnodeProps;
1713 | // children
1714 | const children = node.children;
1715 | let vnodeChildren = children[0];
1716 | // const vnodeElement = {
1717 | // type: NodeTypes.ELEMENT,
1718 | // tag: vnodeTag,
1719 | // prpos: vnodeProps,
1720 | // children: vnodeChildren,
1721 | // };
1722 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
1723 | };
1724 | }
1725 | }
1726 |
1727 | function transformExpression(node) {
1728 | if (node.type === 0 /* NodeTypes.INTERPOLATION */) {
1729 | node.content = processExpression(node.content);
1730 | }
1731 | }
1732 | function processExpression(node) {
1733 | node.content = `_ctx.${node.content}`;
1734 | return node;
1735 | }
1736 |
1737 | function isText(node) {
1738 | return node.type === 3 /* NodeTypes.TEXT */ || node.type === 0 /* NodeTypes.INTERPOLATION */;
1739 | }
1740 |
1741 | function transformText(node) {
1742 | if (node.type === 2 /* NodeTypes.ELEMENT */) {
1743 | return () => {
1744 | const { children } = node;
1745 | let currentContainer;
1746 | for (let i = 0; i < children.length; i++) {
1747 | const child = children[i];
1748 | if (isText(child)) {
1749 | for (let j = i + 1; j < children.length; j++) {
1750 | const next = children[j];
1751 | if (isText(next)) {
1752 | if (!currentContainer) {
1753 | currentContainer = children[i] = {
1754 | type: 5 /* NodeTypes.COMPOUND_EXPRESSION */,
1755 | children: [child],
1756 | };
1757 | }
1758 | currentContainer.children.push(" + ");
1759 | currentContainer.children.push(next);
1760 | children.splice(j, 1);
1761 | j--;
1762 | }
1763 | else {
1764 | currentContainer = undefined;
1765 | break;
1766 | }
1767 | }
1768 | }
1769 | }
1770 | };
1771 | }
1772 | }
1773 |
1774 | function baseCompile(template) {
1775 | const ast = baseParse(template);
1776 | transform(ast, {
1777 | nodeTransforms: [transformExpression, transformElement, transformText],
1778 | });
1779 | console.log("ast_______", ast);
1780 | return generate(ast);
1781 | }
1782 |
1783 | // mini-vue 的出口
1784 | // render
1785 | function compoileToFunction(template) {
1786 | const { code } = baseCompile(template);
1787 | const render = new Function("Vue", code)(runtimeDom);
1788 | return render;
1789 | }
1790 | registerRuntimeCompiler(compoileToFunction);
1791 |
1792 | export { add, createApp, createVNode as createElementVnode, createRenderer, createTextVnode, getCurrentInstance, h, inject, nextTick, provide, proxyRefs, ref, registerRuntimeCompiler, renderSlots, toDisplayString };
1793 |
--------------------------------------------------------------------------------
/mark.md:
--------------------------------------------------------------------------------
1 | √ :掌握
2 | × :没掌握
3 | √×:可惜可惜
4 |
5 | ## reactivty模块掌握复习情况
6 |
7 |
8 | * 基础的reactive对象 √
9 | * 基础的effect函数 √
10 | * 完善effect——返回runner函数 √
11 | * 完善effect——接收scheduler √
12 | * 完善effect——stop及其完善 ×
13 | * 完善effect——接收onStop √×
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-vue",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./lib/mini-vue.cjs.js",
6 | "module": "./lib/mini-vue.esm.js",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "jest": "^27.4.5"
16 | },
17 | "devDependencies": {
18 | "@babel/core": "^7.16.5",
19 | "@babel/preset-env": "^7.16.5",
20 | "@babel/preset-typescript": "^7.16.5",
21 | "@rollup/plugin-typescript": "^8.3.0",
22 | "@types/jest": "^27.5.2",
23 | "babel-jest": "^27.4.5",
24 | "commitizen": "^4.2.4",
25 | "cz-conventional-changelog": "^3.3.0",
26 | "rollup": "^2.62.0",
27 | "tslib": "^2.3.1",
28 | "typescript": "^4.5.4"
29 | },
30 | "config": {
31 | "commitizen": {
32 | "path": "./node_modules/cz-conventional-changelog"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json'
2 |
3 | import typescript from '@rollup/plugin-typescript'
4 | export default {
5 |
6 | input: "./src/index.ts",
7 | output: [
8 |
9 | // 1.cjs commonjs
10 | // 2.esm esmodule
11 | {
12 | format: "cjs",
13 | // file: "lib/mini-vue.cjs.js"
14 | file: pkg.main
15 | },
16 | {
17 | format: "es",
18 | // file: "lib/mini-vue.esm.js"
19 | file: pkg.module
20 | }
21 | ],
22 |
23 | plugins: [
24 | typescript()
25 | ]
26 |
27 | }
--------------------------------------------------------------------------------
/src/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | import { Create_ELEMENT_Vnode } from "./runtimeHelpers";
2 |
3 | export const enum NodeTypes {
4 | INTERPOLATION,
5 | SIMPLE_EXPRESSION,
6 | ELEMENT,
7 | TEXT,
8 | ROOT,
9 | COMPOUND_EXPRESSION,
10 | }
11 |
12 | export function createVNodeCall(context, tag, props, children) {
13 | context.helper(Create_ELEMENT_Vnode);
14 |
15 | return {
16 | type: NodeTypes.ELEMENT,
17 | tag,
18 | props,
19 | children,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | // import { isString } from "../../shared";
2 | // import { NodeTypes } from "./ast";
3 | // import { Create_ELEMENT_Vnode, helperMapName, To_Display_String } from "./runtimeHelpers";
4 |
5 | // export function generate(ast) {
6 | // // let code = "";
7 | // const context: any = createCodegenContext();
8 | // const { push } = context;
9 |
10 | // // const VueBinging = "Vue";
11 | // // // const helpers = ["toDisplayString"];
12 | // // const aliasHelper = (s) => `${s}:_${s}`;
13 | // // push(`const ${ast.helpers.map(aliasHelper).join(",")}= ${VueBinging} `);
14 | // // push("\n");
15 | // // // code += "return";
16 | // // push("return ");
17 |
18 | // //重构
19 | // genFunctionPreamble(context, ast);
20 |
21 | // const functionName = "render";
22 | // const args = ["_cxt", "_cache"];
23 | // const signature = args.join(",");
24 |
25 | // push(`function ${functionName}(${signature}){`);
26 | // // code += ` function ${functionName}(${signature}){`;
27 |
28 | // console.log(ast);
29 |
30 | // // code += `return`;
31 | // push(`return `);
32 | // genNode(ast.codegenNode, context);
33 | // // code += "}";
34 | // push("}");
35 |
36 | // return {
37 | // // code: `return function render(_cxt,_cache,$props,$setup,$data,,$options){
38 | // // return "hi"
39 | // // }`,
40 | // code: context.code,
41 | // };
42 | // }
43 | // function genNode(node: any, context: any) {
44 | // switch (node.type) {
45 | // case NodeTypes.TEXT:
46 | // genText(context, node);
47 | // break;
48 | // case NodeTypes.INTERPOLATION:
49 | // genInterpolation(node, context);
50 | // break;
51 | // case NodeTypes.SIMPLE_EXPRESSION:
52 | // genExpression(node, context);
53 | // break;
54 | // case NodeTypes.ELEMENT:
55 | // genElement(node, context);
56 | // break;
57 | // case NodeTypes.COMPOUND_EXPRESSION:
58 | // genCompoundExpression(node, context);
59 | // break;
60 | // default:
61 | // break;
62 | // }
63 | // }
64 | // function createCodegenContext() {
65 | // const context = {
66 | // code: "",
67 | // push(source) {
68 | // context.code += source;
69 | // },
70 | // helper(key) {
71 | // return `_${helperMapName[key]}`;
72 | // },
73 | // };
74 | // return context;
75 | // }
76 |
77 | // function genFunctionPreamble(context: any, ast: any) {
78 | // const VueBinging = "Vue";
79 | // // const helpers = ["toDisplayString"];
80 | // const { push } = context;
81 | // const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`;
82 |
83 | // if (ast.helpers.length > 0) {
84 | // push(`const {${ast.helpers.map(aliasHelper).join(",")}}= ${VueBinging} `);
85 | // }
86 |
87 | // push("\n");
88 | // // // code += "return";
89 | // push("return ");
90 | // }
91 | // function genText(context, node) {
92 | // const { push } = context;
93 | // // code += ` "${node.content}"`;
94 | // push(`'${node.content}'`);
95 | // // return code;
96 | // }
97 |
98 | // function genInterpolation(node: any, context: any) {
99 | // const { push, helper } = context;
100 | // console.log(node);
101 |
102 | // push(`_${helper(To_Display_String)}(`);
103 | // genNode(node.content, context);
104 | // push(")");
105 | // }
106 |
107 | // function genExpression(node: any, context: any): any {
108 | // const { push } = context;
109 |
110 | // push(`${node.content}`);
111 | // }
112 |
113 | // function genElement(node: any, context: any) {
114 | // const { push, helper } = context;
115 |
116 | // const { tag, children, props } = node;
117 |
118 | // push(`${helper(Create_ELEMENT_Vnode)}(`);
119 | // genNodeList(genNullable([tag, props, children]), context);
120 | // // genNode(children, context);
121 | // // for (let i = 0; i < children.length; i++) {
122 | // // const child = children[i];
123 |
124 | // // genNode(child, context);
125 | // // }
126 |
127 | // push(")");
128 | // }
129 | // function genCompoundExpression(node: any, context: any) {
130 | // const { children } = node;
131 | // const { push } = context;
132 | // for (let i = 0; i < children.length; i++) {
133 | // const child = children[i];
134 | // if (isString(child)) {
135 | // push(child);
136 | // } else {
137 | // genNode(child, context);
138 | // }
139 | // }
140 | // }
141 | // function genNullable(args: any[]) {
142 | // return args.map((arg) => {
143 | // arg || "null";
144 | // });
145 | // }
146 |
147 | // function genNodeList(nodes, context) {
148 | // const { push } = context;
149 | // for (let i = 0; i < nodes.length; i++) {
150 | // const node = nodes[i];
151 | // if (isString(node)) {
152 | // push(node);
153 | // } else {
154 | // genNode(node, context);
155 | // }
156 | // if (i < nodes.length - 1) {
157 | // push(",");
158 | // }
159 | // }
160 | // }
161 |
162 | import { isString } from "../../shared";
163 | import { NodeTypes } from "./ast";
164 | import { Create_ELEMENT_Vnode, helperMapName, To_Display_String } from "./runtimeHelpers";
165 |
166 | export function generate(ast) {
167 | const context = createCodegenContext();
168 | const { push } = context;
169 |
170 | genFunctionPreamble(ast, context);
171 |
172 | const functionName = "render";
173 | const args = ["_ctx", "_cache"];
174 | const signature = args.join(", ");
175 |
176 | push(`function ${functionName}(${signature}){`);
177 | push("return ");
178 | genNode(ast.codegenNode, context);
179 | push("}");
180 |
181 | return {
182 | code: context.code,
183 | };
184 | }
185 |
186 | function genFunctionPreamble(ast, context) {
187 | const { push } = context;
188 | const VueBinging = "Vue";
189 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`;
190 | if (ast.helpers.length > 0) {
191 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`);
192 | }
193 | push("\n");
194 | push("return ");
195 | }
196 |
197 | function createCodegenContext(): any {
198 | const context = {
199 | code: "",
200 | push(source) {
201 | context.code += source;
202 | },
203 | helper(key) {
204 | return `_${helperMapName[key]}`;
205 | },
206 | };
207 |
208 | return context;
209 | }
210 |
211 | function genNode(node: any, context) {
212 | switch (node.type) {
213 | case NodeTypes.TEXT:
214 | genText(node, context);
215 | break;
216 | case NodeTypes.INTERPOLATION:
217 | genInterpolation(node, context);
218 | break;
219 | case NodeTypes.SIMPLE_EXPRESSION:
220 | genExpression(node, context);
221 | break;
222 | case NodeTypes.ELEMENT:
223 | genElement(node, context);
224 | break;
225 | case NodeTypes.COMPOUND_EXPRESSION:
226 | genCompoundExpression(node, context);
227 | break;
228 |
229 | default:
230 | break;
231 | }
232 | }
233 |
234 | function genCompoundExpression(node: any, context: any) {
235 | const { push } = context;
236 | const children = node.children;
237 | for (let i = 0; i < children.length; i++) {
238 | const child = children[i];
239 | if (isString(child)) {
240 | push(child);
241 | } else {
242 | genNode(child, context);
243 | }
244 | }
245 | }
246 |
247 | function genElement(node: any, context: any) {
248 | const { push, helper } = context;
249 | const { tag, children, props } = node;
250 | push(`${helper(Create_ELEMENT_Vnode)}(`);
251 | genNodeList(genNullable([tag, props, children]), context);
252 | push(")");
253 | }
254 |
255 | function genNodeList(nodes, context) {
256 | const { push } = context;
257 |
258 | for (let i = 0; i < nodes.length; i++) {
259 | const node = nodes[i];
260 | if (isString(node)) {
261 | push(node);
262 | } else {
263 | genNode(node, context);
264 | }
265 |
266 | if (i < nodes.length - 1) {
267 | push(", ");
268 | }
269 | }
270 | }
271 |
272 | function genNullable(args: any) {
273 | return args.map((arg) => arg || "null");
274 | }
275 |
276 | function genExpression(node: any, context: any) {
277 | const { push } = context;
278 | push(`${node.content}`);
279 | }
280 |
281 | function genInterpolation(node: any, context: any) {
282 | const { push, helper } = context;
283 | push(`${helper(To_Display_String)}(`);
284 | genNode(node.content, context);
285 | push(")");
286 | }
287 |
288 | function genText(node: any, context: any) {
289 | const { push } = context;
290 | push(`'${node.content}'`);
291 | }
292 |
--------------------------------------------------------------------------------
/src/compiler-core/src/compile.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "./codegen";
2 | import { baseParse } from "./parse";
3 | import { transform } from "./transform";
4 | import { transformElement } from "./transforms/tarnsformElement";
5 | import { transformExpression } from "./transforms/transformExpression";
6 | import { transformText } from "./transforms/transfromText";
7 |
8 | export function baseCompile(template) {
9 | const ast: any = baseParse(template);
10 | transform(ast, {
11 | nodeTransforms: [transformExpression, transformElement, transformText],
12 | });
13 | console.log("ast_______", ast);
14 |
15 | return generate(ast);
16 | }
17 |
--------------------------------------------------------------------------------
/src/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./compile";
2 |
--------------------------------------------------------------------------------
/src/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | const enum TagType {
4 | Start,
5 | End,
6 | }
7 |
8 | export function baseParse(content: string) {
9 | //message
10 | const context = createParseContext(content); //{source:message}
11 | //重构
12 | return createRoot(parseChildren(context, [])); //
13 | // return {
14 | // children: [
15 | // {
16 | // type: "interpolation",
17 | // content: { type: "simple_expression", content: "message" },
18 | // },
19 | // ],
20 | // };
21 |
22 | // return createRoot([
23 | // {
24 | // type: "interpolation",
25 | // content: { type: "simple_expression", content: "message" },
26 | // },
27 | // ]);
28 | }
29 |
30 | function createParseContext(content): any {
31 | return {
32 | source: content,
33 | };
34 | }
35 | function createRoot(children) {
36 | return {
37 | children,
38 | type: NodeTypes.ROOT,
39 | };
40 | }
41 |
42 | function parseChildren(context, ancestors) {
43 | // ancestors 祖先
44 | const nodes: any = [];
45 |
46 | while (!isEnd(context, ancestors)) {
47 | let node;
48 | //解析插值{{}}
49 | if (context.source.startsWith("{{")) {
50 | node = parseInterpolation(context);
51 | } else if (context.source[0] === "<") {
52 | //element类型
53 | if (/[a-z]/.test(context.source[1])) {
54 | console.log("parse.element");
55 | node = parseElement(context, ancestors);
56 | }
57 | }
58 | // } else {
59 | // //text类型
60 | // node = parseText(context);
61 | // }
62 | if (!node) {
63 | node = parseText(context);
64 | }
65 | nodes.push(node);
66 | }
67 | return nodes;
68 | // return [
69 | // {
70 | // type: "interpolation",
71 | // content: { type: "simple_expression", content: "message" },
72 | // },
73 | // ];
74 | }
75 |
76 | function isEnd(context, ancestors) {
77 | //
78 |
79 | //结束的条件
80 | //1、context.source 没有值的时候
81 | //2、当遇到结束标签时候
82 |
83 | //2
84 |
85 | const s = context.source;
86 | //
87 | if (s.startsWith("")) {
88 | for (let i = ancestors.length - 1; i >= 0; i--) {
89 | const tag = ancestors[i].tag;
90 | if (startsWithEndTagOpen(s, tag)) {
91 | return true;
92 | }
93 | }
94 | }
95 |
96 | // if (parentTag && s.startsWith(`${parentTag}>`)) {
97 | // return true;
98 | // }
99 | //1
100 | return !context.source;
101 | }
102 |
103 | //element类型parse
104 | function parseElement(context: any, ancestors) {
105 | //implement
106 | // 1、解析tag
107 | // 2、删除处理完成的代码
108 |
109 | //1
110 | // const match: any = /^<([a-z]*)/i.exec(context.source); //
111 | // console.log(match); // [ '', groups: undefined ]
112 | // const tag = match[1];
113 | // console.log(tag); //div
114 |
115 | // //2 删除处理完成的代码
116 | // advanceBy(context, match[0].length);
117 | // console.log(context.source); //>
118 | // advanceBy(context, 1);
119 | // console.log(context.source); //
120 |
121 | // return {
122 | // type: NodeTypes.ELEMENT,
123 | // tag,
124 | // };
125 |
126 | // 重构
127 | const element: any = parseTag(context, TagType.Start);
128 | ancestors.push(element);
129 | element.children = parseChildren(context, ancestors);
130 | ancestors.pop();
131 |
132 | // if (context.source.slice(2, 2 + element.tag.length) === element.tag) {
133 |
134 | //重构
135 | if (startsWithEndTagOpen(context.source, element.tag)) {
136 | parseTag(context, TagType.End);
137 | } else {
138 | throw new Error(`缺少结束标签:${element.tag}`);
139 | }
140 |
141 | console.log("-------", context.source);
142 |
143 | return element;
144 | }
145 |
146 | function parseTag(context: any, TagType) {
147 | //implement
148 | // 1、解析tag
149 | // 2、删除处理完成的代码
150 |
151 | //1
152 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
153 | console.log(match); // [ '', groups: undefined ]
154 | const tag = match[1];
155 | console.log(tag); //div
156 |
157 | // 2
158 | advanceBy(context, match[0].length); //>
159 |
160 | advanceBy(context, 1); //
161 |
162 | if (tag === TagType.End) return;
163 |
164 | return {
165 | type: NodeTypes.ELEMENT,
166 | tag,
167 | };
168 | }
169 |
170 | //{{}}插值类型parse
171 | function parseInterpolation(context) {
172 | console.log(context);
173 |
174 | const openDelimiter = "{{";
175 | const closeDelimiter = "}}";
176 |
177 | //{{message}}解析
178 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length);
179 |
180 | advanceBy(context, openDelimiter.length);
181 |
182 | // context.source = context.source.slice(openDelimiter.length); // message}}
183 | console.log(context.source);
184 |
185 | const rawContentLength = closeIndex - openDelimiter.length;
186 | // const rawContent = context.source.slice(0, rawContentLength); // message
187 | const rawContent = parseTextData(context, rawContentLength);
188 |
189 | //边缘处理 利于 {{ message}} 双括号中有空格
190 | const content = rawContent.trim();
191 | console.log(content);
192 |
193 | // 例如{{message}} {{message}}处理完毕的 删除掉 剩下
194 | // context.source = context.source.slice(closeDelimiter.length);
195 | advanceBy(context, closeDelimiter.length);
196 | console.log("context.source", context.source);
197 |
198 | return {
199 | type: NodeTypes.INTERPOLATION, //"interpolation"
200 | content: { type: NodeTypes.SIMPLE_EXPRESSION, content: content }, //"simple_expression"
201 | };
202 | }
203 |
204 | //TEXT类型parse
205 | function parseText(context) {
206 | //1、获取content
207 | //2、推进代码 : 删除已经处理的代码
208 |
209 | //1
210 | // const content = context.source.slice(0, context.source.length);
211 | // console.log(content);
212 |
213 | //2
214 | // advanceBy(context, content.length);
215 | // console.log(context.source);
216 |
217 | let endIndex = context.source.length;
218 | let endTokens = ["<", "{{"];
219 |
220 | for (let i = 0; i < endTokens.length; i++) {
221 | const index = context.source.indexOf(endTokens[i]);
222 | if (index !== -1 && endIndex > index) {
223 | //!==-1 证明没有插值
224 | endIndex = index;
225 | }
226 | }
227 |
228 | //重构
229 | const content = parseTextData(context, endIndex);
230 | console.log("content _______", content);
231 |
232 | return {
233 | type: NodeTypes.TEXT,
234 | content,
235 | };
236 | }
237 |
238 | //推进工具函数
239 | function advanceBy(context: any, length: number) {
240 | context.source = context.source.slice(length);
241 | }
242 |
243 | //裁剪工具函数
244 | function parseTextData(context, length) {
245 | const content = context.source.slice(0, length);
246 |
247 | advanceBy(context, length);
248 | return content;
249 | }
250 |
251 | function startsWithEndTagOpen(source, tag) {
252 | return source.startsWith("") && source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase();
253 | }
254 |
--------------------------------------------------------------------------------
/src/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const To_Display_String = Symbol("toDisplayString");
2 | export const Create_ELEMENT_Vnode = Symbol("createElementVnode");
3 |
4 | export const helperMapName = {
5 | [To_Display_String]: "toDisplayString ",
6 | [Create_ELEMENT_Vnode]: "createElementVnode ",
7 | };
8 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 | import { To_Display_String } from "./runtimeHelpers";
3 |
4 | export function transform(root, options = {}) {
5 | const context = createTransformContext(root, options);
6 |
7 | //1、遍历 ——深度优先搜索 递归
8 | // 2、修改text content
9 | traverseNode(root, context);
10 |
11 | createRootCodegen(root);
12 |
13 | root.helpers = [...context.helpers.keys()];
14 | }
15 |
16 | function createTransformContext(root: any, options: any) {
17 | const context = {
18 | root,
19 | nodeTransforms: options.nodeTransforms || [],
20 | helpers: new Map(),
21 | helper(key) {
22 | context.helpers.set(key, 1);
23 | },
24 | };
25 |
26 | return context;
27 | }
28 |
29 | function traverseNode(node: any, context) {
30 | console.log(node);
31 | //1.element
32 |
33 | const nodeTransforms = context.nodeTransforms;
34 | const onExitFns: any = [];
35 | for (let i = 0; i < nodeTransforms.length; i++) {
36 | const transform = nodeTransforms[i];
37 | const onExit = transform(node, context);
38 | if (onExit) {
39 | onExitFns.push(onExit);
40 | }
41 | }
42 |
43 | switch (node.type) {
44 | case NodeTypes.INTERPOLATION:
45 | context.helper(To_Display_String);
46 | break;
47 | case NodeTypes.ROOT:
48 | case NodeTypes.ELEMENT:
49 | traverseChildren(node, context);
50 | default:
51 | break;
52 | }
53 | let i = onExitFns.length;
54 | while (i--) {
55 | onExitFns[i]();
56 | }
57 | // traverseChildren(node, context);
58 | }
59 |
60 | function traverseChildren(node, context) {
61 | const children = node.children;
62 |
63 | for (let i = 0; i < children.length; i++) {
64 | const node = children[i];
65 | traverseNode(node, context);
66 | }
67 | }
68 | function createRootCodegen(root: any) {
69 | const child = root.children[0];
70 |
71 | if (child.type === NodeTypes.ELEMENT) {
72 | root.codegenNode = child.codegenNode;
73 | } else {
74 | root.codegenNode = root.children[0];
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/tarnsformElement.ts:
--------------------------------------------------------------------------------
1 | import { createVNodeCall, NodeTypes } from "../ast";
2 | import { Create_ELEMENT_Vnode } from "../runtimeHelpers";
3 |
4 | export function transformElement(node, context) {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | return () => {
7 | context.helper(Create_ELEMENT_Vnode);
8 | // 中间处理层
9 | const vnodeTag = `"${node.tag}"`;
10 | //props
11 | let vnodeProps;
12 |
13 | // children
14 | const children = node.children;
15 | let vnodeChildren = children[0];
16 |
17 | // const vnodeElement = {
18 | // type: NodeTypes.ELEMENT,
19 | // tag: vnodeTag,
20 | // prpos: vnodeProps,
21 | // children: vnodeChildren,
22 | // };
23 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
24 | };
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformExpression.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 |
3 | export function transformExpression(node) {
4 | if (node.type === NodeTypes.INTERPOLATION) {
5 | node.content = processExpression(node.content);
6 | }
7 | }
8 | function processExpression(node: any) {
9 | node.content = `_ctx.${node.content}`;
10 | return node;
11 | }
12 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transfromText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 | import { isText } from "../utils";
3 |
4 | export function transformText(node) {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | return () => {
7 | const { children } = node;
8 |
9 | let currentContainer;
10 | for (let i = 0; i < children.length; i++) {
11 | const child = children[i];
12 |
13 | if (isText(child)) {
14 | for (let j = i + 1; j < children.length; j++) {
15 | const next = children[j];
16 | if (isText(next)) {
17 | if (!currentContainer) {
18 | currentContainer = children[i] = {
19 | type: NodeTypes.COMPOUND_EXPRESSION,
20 | children: [child],
21 | };
22 | }
23 | currentContainer.children.push(" + ");
24 | currentContainer.children.push(next);
25 | children.splice(j, 1);
26 | j--;
27 | } else {
28 | currentContainer = undefined;
29 | break;
30 | }
31 | }
32 | }
33 | }
34 | };
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | export function isText(node) {
4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION;
5 | }
6 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/__snapshots__/codegen.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`codegen element 1`] = `
4 | "const { toDisplayString :_toDisplayString , createElementVnode :_createElementVnode } = Vue
5 | return function render(_ctx, _cache){return _createElementVnode (\\"div\\", null, 'hi,' + _toDisplayString (_ctx.message))}"
6 | `;
7 |
8 | exports[`codegen interpolation 1`] = `
9 | "const { toDisplayString :_toDisplayString } = Vue
10 | return function render(_ctx, _cache){return _toDisplayString (_ctx.message)}"
11 | `;
12 |
13 | exports[`codegen string 1`] = `
14 | "
15 | return function render(_ctx, _cache){return 'hi'}"
16 | `;
17 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/codegen.spec.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "../src/codegen";
2 | import { baseParse } from "../src/parse";
3 | import { transform } from "../src/transform";
4 | import { transformElement } from "../src/transforms/tarnsformElement";
5 | import { transformExpression } from "../src/transforms/transformExpression";
6 | import { transformText } from "../src/transforms/transfromText";
7 |
8 | describe("codegen", () => {
9 | it("string", () => {
10 | const ast = baseParse("hi");
11 | transform(ast);
12 | const { code } = generate(ast);
13 |
14 | //快照 作用
15 | //1、抓bug
16 | // 2、有意的改变
17 | expect(code).toMatchSnapshot();
18 | });
19 |
20 | it("interpolation", () => {
21 | const ast = baseParse("{{message}}");
22 |
23 | transform(ast, {
24 | nodeTransforms: [transformExpression],
25 | });
26 |
27 | const { code } = generate(ast);
28 | expect(code).toMatchSnapshot();
29 | });
30 |
31 | it("element", () => {
32 | const ast: any = baseParse("hi,{{message}}
");
33 |
34 | transform(ast, {
35 | nodeTransforms: [transformExpression, transformElement, transformText],
36 | });
37 | console.log("ast_______", ast);
38 |
39 | const { code } = generate(ast);
40 | expect(code).toMatchSnapshot();
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/parse.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 |
4 | describe("parse", () => {
5 | describe("interpolation", () => {
6 | test("simple interpolation", () => {
7 | const ast = baseParse("{{message}}");
8 |
9 | expect(ast.children[0]).toStrictEqual({
10 | type: NodeTypes.INTERPOLATION,
11 | content: {
12 | type: NodeTypes.SIMPLE_EXPRESSION,
13 | content: "message",
14 | },
15 |
16 | // type: "interpolation",
17 | // content: { type: "simple_expression", content: "message" },
18 | });
19 | });
20 | });
21 |
22 | describe("element", () => {
23 | it("simple element div", () => {
24 | const ast = baseParse("");
25 | expect(ast.children[0]).toStrictEqual({
26 | type: NodeTypes.ELEMENT,
27 | tag: "div",
28 | children: [],
29 | });
30 | });
31 | });
32 |
33 | describe("text", () => {
34 | it("simple text", () => {
35 | const ast = baseParse("some text");
36 |
37 | expect(ast.children[0]).toStrictEqual({
38 | type: NodeTypes.TEXT,
39 | content: "some text",
40 | });
41 | });
42 | });
43 |
44 | test("hello world", () => {
45 | const ast = baseParse("hi,{{message}}
");
46 |
47 | expect(ast.children[0]).toStrictEqual({
48 | type: NodeTypes.ELEMENT,
49 | tag: "div",
50 | children: [
51 | {
52 | type: NodeTypes.TEXT,
53 | content: "hi,",
54 | },
55 | {
56 | type: NodeTypes.INTERPOLATION,
57 | content: {
58 | type: NodeTypes.SIMPLE_EXPRESSION,
59 | content: "message",
60 | },
61 | },
62 | ],
63 | });
64 | });
65 |
66 | test("Nested element", () => {
67 | const ast = baseParse("");
68 |
69 | expect(ast.children[0]).toStrictEqual({
70 | type: NodeTypes.ELEMENT,
71 | tag: "div",
72 | children: [
73 | {
74 | type: NodeTypes.ELEMENT,
75 | tag: "p",
76 | children: [
77 | {
78 | type: NodeTypes.TEXT,
79 | content: "hi",
80 | },
81 | ],
82 | },
83 |
84 | {
85 | type: NodeTypes.INTERPOLATION,
86 | content: {
87 | type: NodeTypes.SIMPLE_EXPRESSION,
88 | content: "message",
89 | },
90 | },
91 | ],
92 | });
93 | });
94 |
95 | test("should throw error when lack end tag", () => {
96 | expect(() => {
97 | baseParse("
");
98 | }).toThrow(`缺少结束标签:span`);
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/transform.spec.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../src/ast";
2 | import { baseParse } from "../src/parse";
3 | import { transform } from "../src/transform";
4 |
5 | describe("transfrom", () => {
6 | it("happy path", () => {
7 | const ast = baseParse("hi,{{message}}
");
8 |
9 | const plugin = (node) => {
10 | if (node.type === NodeTypes.TEXT) {
11 | node.content = node.content + "mini-vue";
12 | }
13 | };
14 |
15 | transform(ast, {
16 | nodeTransforms: [plugin],
17 | });
18 | const nodeText = ast.children[0].children[0];
19 | expect(nodeText.content).toBe("hi,mini-vue");
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 的出口
2 |
3 | export * from "./runtime-dom/index";
4 |
5 | import { baseCompile } from "./compiler-core/src";
6 | import * as runtimeDom from "./runtime-dom";
7 | import { registerRuntimeCompiler } from "./runtime-dom";
8 |
9 | // render
10 |
11 | function compoileToFunction(template) {
12 | const { code } = baseCompile(template);
13 | const render = new Function("Vue", code)(runtimeDom);
14 | return render;
15 | }
16 | registerRuntimeCompiler(compoileToFunction);
17 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandler.ts:
--------------------------------------------------------------------------------
1 | // reactive和readonly对象复用代码的重构
2 |
3 | import { isObject } from "../shared/index";
4 | import { track, trigger } from "./effect";
5 | import { reactive, ReactiveFlags, readonly } from "./reactive";
6 |
7 | function createGetter(isReadonly = false, isShallow = false) {
8 | //true
9 | return function get(target, key) {
10 | //专门判断isReactive
11 | if (key === ReactiveFlags.IS_REACTIVE) {
12 | return !isReadonly;
13 | }
14 | //专门判断isReadonly
15 | else if (key === ReactiveFlags.IS_READONLY) {
16 | return isReadonly;
17 | }
18 | const res = Reflect.get(target, key);
19 |
20 | //reactive对象的getter 进行依赖收集 //readonly对象不用进行依赖收集
21 | if (!isReadonly) {
22 | track(target, key);
23 | }
24 |
25 | //如果是shallowReadonly类型,就不用执行内部嵌套的响应式转换。也不用执行依赖收集
26 | if (isShallow) {
27 | return res;
28 | }
29 | //reactive、readonly对象嵌套的响应式转换
30 | if (isObject(res)) {
31 | //递归调用
32 | // isReadonly == true -> 表明是readonly对象 :是reactive对象
33 | return isReadonly ? readonly(res) : reactive(res);
34 | }
35 | return res;
36 | };
37 | }
38 |
39 | function createSetter() {
40 | //只有reactive对象能够调用setter
41 | return function set(target, key, newVal) {
42 | const res = Reflect.set(target, key, newVal);
43 | //触发依赖
44 | trigger(target, key);
45 | return res;
46 | };
47 | }
48 |
49 | // reactive对象getter和setter
50 | const get = createGetter();
51 | const set = createSetter();
52 |
53 | export const mutableHandlers = {
54 | get,
55 | set,
56 | };
57 |
58 | // readonly对象的getter和setter
59 | const readonlyGetter = createGetter(true);
60 |
61 | export const readonlyHandlers = {
62 | get: readonlyGetter,
63 | set: function (target, key, newVal) {
64 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
65 | return true;
66 | },
67 | };
68 |
69 | const shallowReactiveGetter = createGetter(false, true);
70 | // const shallowReactiveSetter = createSetter();
71 |
72 | // export const shallowReactiveHandlers = Object.assign({}, mutableHandlers, {
73 | // get: shallowReactiveGetter,
74 | // });
75 | export const shallowReactiveHandlers = {
76 | get: shallowReactiveGetter,
77 | set,
78 | };
79 |
80 | const shallowReadonlyGetter = createGetter(true, true);
81 |
82 | export const shallowReadonlyHandlers = {
83 | get: shallowReadonlyGetter,
84 | set: function (target, key, newVal) {
85 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal);
86 | return true;
87 | },
88 | };
89 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | //computed 的类实现
4 | class computedRefIpml {
5 | private _fn: any;
6 | private _dirty: boolean = true;
7 | private _value: any;
8 | private _effect: ReactiveEffect;
9 |
10 | constructor(fn) {
11 | this._fn = fn;
12 |
13 | this._effect = new ReactiveEffect(fn, {
14 | scheduler: () => {
15 | if (!this._dirty) {
16 | this._dirty = true;
17 | }
18 | },
19 | });
20 | }
21 |
22 | get value() {
23 | if (this._dirty) {
24 | this._dirty = false;
25 | // this._value = this._fn();
26 | this._value = this._effect.run();
27 | }
28 | return this._value;
29 | }
30 | }
31 |
32 | export function computed(fn) {
33 | return new computedRefIpml(fn);
34 | }
35 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | //effect 第一个参数接受一个函数
2 | //ReactiveEffect类 (抽离的一个概念) ------>面向对象思维
3 |
4 | import { extend } from "../shared";
5 |
6 | /**
7 | * 不给activeEffect添加类型, 单测会报错
8 | * 所以进行了代码优化
9 | */
10 |
11 | // let activeEffect: () => void;
12 | // export function effect(fn: () => void) {
13 | // activeEffect = fn;
14 | // fn(); //执行函数 ->触发了响应式对象的getter ->track
15 | // activeEffect = function () {};
16 | // }
17 |
18 | //工具函数
19 | function cleanupEffect(effect) {
20 | effect.deps.forEach((dep: any) => {
21 | dep.delete(effect);
22 | });
23 | //把 effect.deps清空
24 | effect.deps.length = 0;
25 | }
26 |
27 | //代码优化 面向对象思想
28 | let activeEffect: any;
29 | let shouldTrack: boolean = false; //用于记录是否应该收集依赖,防止调用stop后触发响应式对象的property的get的依赖收集 obj.foo ++
30 |
31 | export class ReactiveEffect {
32 | private _fn: any;
33 | deps = []; //用于保存与当前实例相关的响应式对象的 property 对应的 Set 实例 用于stop操作
34 | active = true; //用于记录当前实例状态,为 true 时未调用 stop 方法,否则已调用,防止重复调用 stop 方法
35 | scheduler?: () => void;
36 | onStop?: () => void;
37 | constructor(fn, option) {
38 | this._fn = fn;
39 | this.scheduler = option?.scheduler;
40 | this.onStop = option?.onStop;
41 | // this.deps = [];
42 | // this.active = true;
43 | }
44 | //用于执行传入的函数
45 | run() {
46 | //stop的状态下(active =false) 直接执行fn 不收集依赖
47 | if (!this.active) {
48 | this.active = true;
49 | return this._fn();
50 | }
51 | //应该收集依赖
52 | shouldTrack = true;
53 |
54 | activeEffect = this;
55 | const res = this._fn();
56 |
57 | //重置
58 | shouldTrack = false;
59 | // 返回传入的函数执行的结果
60 | return res;
61 | }
62 | stop() {
63 | //删除effect active用于优化 多次调用stop也只清空一次
64 | if (this.active) {
65 | cleanupEffect(this);
66 | if (this.onStop) {
67 | this.onStop();
68 | }
69 | this.active = false;
70 | }
71 | }
72 | }
73 |
74 | //effect函数
75 | /**
76 | * @param fn 参数函数
77 | */
78 | export function effect(fn, option: any = {}) {
79 | const _effect: ReactiveEffect = new ReactiveEffect(fn, option);
80 | // Object.assign(_effect, option);
81 |
82 | if (option) {
83 | extend(_effect, option); //what this?
84 | }
85 | if (!option || !option.lazy) {
86 | _effect.run();
87 | }
88 |
89 | // _effect.run(); //实际上是调用执行了fn函数
90 |
91 | const runner: any = _effect.run.bind(_effect); //直接调用runnner
92 | runner.effect = _effect;
93 | return runner;
94 | }
95 |
96 | export function stop(runner) {
97 | runner.effect.stop();
98 | }
99 |
100 | const targetMap = new WeakMap();
101 | // 进行依赖收集track
102 | export function track(target, key) {
103 | // 若不应该收集依赖则直接返回
104 | // if (!shouldTrack || activeEffect === undefined) {
105 | // return;
106 | // }
107 | if (!isTracking()) return;
108 | //1、先获取到key的依赖集合dep
109 | //所有对象的的以来集合targetMap -> 当前对象的依赖集合objMap -> 当前key的依赖集合
110 | let objMap = targetMap.get(target);
111 | // 如果没有初始化过 需要先初始化
112 | if (!objMap) {
113 | objMap = new Map();
114 | targetMap.set(target, objMap);
115 | }
116 | //同理 如果没有初始化过 需要先初始化
117 | let dep = objMap.get(key);
118 | if (!dep) {
119 | dep = new Set(); //依赖不会重复
120 | objMap.set(key, dep);
121 | }
122 | //d将依赖函数添加给dep
123 |
124 | // if (!activeEffect) return;
125 | // if(dep.has(activeEffect)) return
126 | // dep.add(activeEffect); ? 怎么获取到fn? 添加一个全局变量activeEffect
127 | // activeEffect?.deps.push(dep); ?
128 |
129 | trackEffect(dep);
130 | }
131 |
132 | //重构
133 | export function trackEffect(dep) {
134 | //看看dep之前有没有添加过,添加过的话 就不添加了
135 | if (dep.has(activeEffect)) return;
136 | dep.add(activeEffect);
137 | activeEffect.deps.push(dep);
138 | }
139 |
140 | //activeEffect可能为undefined 原因: 访问一个单纯的reactive对象,没有任何依赖的时候 activeEffect可能为undefined
141 | export function isTracking() {
142 | return shouldTrack && activeEffect !== undefined;
143 | }
144 | console.log(targetMap.has(effect));
145 |
146 | //触发依赖trigger
147 | export function trigger(target, key) {
148 | // console.log("触发依赖了");
149 |
150 | //1、先获取到key的依赖集合dep
151 | let objMap = targetMap.get(target);
152 | // console.log(objMap);
153 |
154 | let dep = objMap.get(key);
155 | console.log(objMap);
156 |
157 | //去执行dep里面的函数
158 | // dep.forEach((effect) => {
159 | // if (effect.scheduler) {
160 | // effect.scheduler();
161 | // } else {
162 | // effect.run();
163 | // }
164 | // });
165 | triggerEffect(dep);
166 | }
167 |
168 | // 重构
169 | export function triggerEffect(dep) {
170 | dep.forEach((effect) => {
171 | if (effect.scheduler) {
172 | effect.scheduler();
173 | } else {
174 | effect.run();
175 | }
176 | });
177 | }
178 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export function add(a, b) {
2 | return a + b;
3 | }
4 |
5 | export { ref, proxyRefs } from "./ref";
6 |
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | // import { track, trigger } from "./effect";
2 | import { isObject } from "../shared/index";
3 | import { mutableHandlers, readonlyHandlers, shallowReactiveHandlers, shallowReadonlyHandlers } from "./baseHandler";
4 | // import { track, trigger } from "./effect";
5 |
6 | /**
7 | * reative 和 readonly 的get和set重复代码较多,进行代码抽取重构
8 | *
9 | */
10 |
11 | // 工具函数
12 | function createReactiveObject(target, baseHandler) {
13 | if (!isObject(target)) {
14 | console.warn(`target${target}必须是一个对象`);
15 | return target;
16 | }
17 | return new Proxy(target, baseHandler);
18 | }
19 |
20 | //reactive函数
21 | // export function reactive(obj) {
22 | // return new Proxy(obj, {
23 | // // get(target, key) {
24 | // // //ToDo 收集依赖
25 | // // track(target, key);
26 | // // const res = Reflect.get(target, key); //返回属性的值
27 | // // return res;
28 | // // },
29 | // // set(target, key, newVal) {
30 | // // //返回Boolean 返回 true 代表属性设置成功。 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。
31 | // // const res = Reflect.set(target, key, newVal); //返回一个 Boolean 值表明是否成功设置属性。
32 | // // //ToDo 触发依赖依赖
33 | // // trigger(target, key);
34 | // // return res;
35 | // // },
36 | // get,
37 | // set,
38 | // });
39 | // }
40 | export function reactive(obj) {
41 | return createReactiveObject(obj, mutableHandlers);
42 | }
43 |
44 | //readonly函数 只读不能修改
45 | // export function readonly(obj) {
46 | // return new Proxy(obj, {
47 | // // get(target, key) {
48 | // // return Reflect.get(target, key);
49 | // // },
50 | // // set(target, key, newValue) {
51 | // // console.warn(
52 | // // `target:${target} 对象是readonly对象,不能修改的属性 `,
53 | // // key,
54 | // // newValue
55 | // // );
56 | // // return true;
57 | // // },
58 | // get,
59 | // set,
60 | // });
61 | // }
62 |
63 | export function readonly(obj) {
64 | return createReactiveObject(obj, readonlyHandlers);
65 | }
66 |
67 | export const enum ReactiveFlags {
68 | IS_REACTIVE = "__v_isReactive",
69 | IS_READONLY = "__v_isReadonly",
70 | }
71 |
72 | //isReactive 功能
73 |
74 | export function isReactive(obj) {
75 | //如何判断是一个reactive对象 ? 或者这么问? 什么样的对象是reactive对象
76 | //在设计getter的时候 传入了一个isRaedonly的参数 默认false
77 | // 我们可以用 obj[is_reactive] 来调用getter函数
78 | // 在getter函数中进行判断 如果 key ==="is_reactive" return !isReadonly
79 |
80 | //reactive对象-> getter-> key===ReactiveFlags.IS_RWACTIVE return true -> !!true ->true
81 | //非 reactive独享 ->不执行getter,对象业务ReactiveFlags.IS_RWACTIVE属性值 return undefined -> !!undefined-> false
82 | return !!obj[ReactiveFlags.IS_REACTIVE];
83 | }
84 |
85 | //isReadonly功能
86 |
87 | export function isReadonly(obj) {
88 | //同理
89 | return !!obj[ReactiveFlags.IS_READONLY];
90 | }
91 |
92 | //isProxy
93 | export function isProxy(obj) {
94 | // 检查对象是否是由 reactive 或 readonly 创建的 proxy。
95 | //也就是说满足上面isReactive和isReadonly任意一个就是proxy &&(与) ||(或)
96 | return isReadonly(obj) === true || isReactive(obj) === true;
97 | }
98 |
99 | //shallowReactive
100 | //创建一个 proxy,使其自身的 property为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)
101 | // 自身property为reactive 内部嵌套不是reactive
102 | export function shallowReactive(obj) {
103 | return createReactiveObject(obj, shallowReactiveHandlers);
104 | }
105 |
106 | //shallowReadonly
107 | //创建一个 proxy,使其自身的 property为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)
108 | // 自身property为readonly 内部嵌套不是readonly
109 | export function shallowReadonly(obj) {
110 | return createReactiveObject(obj, shallowReadonlyHandlers);
111 | }
112 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | // export function ref(val) {
2 | // const refObj = {
3 | // value: val,
4 | // };
5 | // return refObj;
6 | // }
7 |
8 | import { isObject } from "../shared/index";
9 | import { isTracking, trackEffect, triggerEffect } from "./effect";
10 | import { reactive } from "./reactive";
11 |
12 | // ref接口的实现类 对操作进行封装
13 | class RefImpl {
14 | private _value: any;
15 | public dep;
16 | private _rawValue: any;
17 | public __v_isRef = true; // 判断是ref的标识
18 | constructor(value) {
19 | // 将传入的值赋值给实例的私有属性property_value
20 | this._rawValue = value;
21 | //value 为对象的话 需要转换为reactive包裹value
22 | // this._value = isObject(value) ? reactive(value) : value;
23 | this._value = convert(value);
24 | this.dep = new Set();
25 | }
26 | get value() {
27 | if (isTracking()) {
28 | // 进行依赖收集
29 | trackEffect(this.dep);
30 | }
31 |
32 | return this._value;
33 | }
34 | set value(val) {
35 | //如果value是reactive对象的时候 this._value 为Proxy
36 | // 提前声明一个this._rawValue 来存储并进行比较
37 | if (Object.is(val, this._rawValue)) return; // ref.value = 2 ref.value = 2 两次赋值相同值的操作 不会执行effect
38 | this._rawValue = val;
39 | // this._value = isObject(val) ? reactive(val) : val;
40 | this._value = convert(val); //处理值 如果是对象 ->转为reactive对象 不是对象 返回原值
41 | triggerEffect(this.dep);
42 | }
43 | }
44 | function convert(val) {
45 | return isObject(val) ? reactive(val) : val;
46 | }
47 |
48 | //ref
49 | export function ref(val) {
50 | return new RefImpl(val);
51 | }
52 |
53 | //isRef
54 | export function isRef(val) {
55 | return !!(val.__v_isRef === true);
56 | }
57 |
58 | //unRef
59 | export function unRef(val) {
60 | return isRef(val) ? val.value : val;
61 | }
62 |
63 | //proxyRef 应用场景: template中使用setup中return的ref 不需要使用ref.value
64 |
65 | export function proxyRefs(objectWithRefs) {
66 | //怎么知道调用getter 和setter ? ->proxy
67 | return new Proxy(objectWithRefs, {
68 | get(target, key) {
69 | //get -> age(ref) 那么就给他返回 .value
70 | // not ref -> return value
71 | return unRef(Reflect.get(target, key));
72 | },
73 | set(target, key, newVal) {
74 | //当前需要修改的值是ref对象,同时修改值不是ref
75 | if (isRef(target[key]) && !isRef(newVal)) {
76 | target[key].value = newVal;
77 | return true;
78 | } else {
79 | return Reflect.set(target, key, newVal);
80 | }
81 | },
82 | });
83 | }
84 |
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../computed";
2 | import { reactive } from "../reactive";
3 |
4 | describe("computed", () => {
5 | it("happy path", () => {
6 | //ref
7 | //.value
8 | //1.缓存
9 | const user = reactive({
10 | age: 1,
11 | });
12 | const age = computed(() => {
13 | return user.age;
14 | });
15 |
16 | expect(age.value).toBe(1);
17 | });
18 |
19 | it("should compute lazily", () => {
20 | const value = reactive({
21 | foo: 1,
22 | });
23 | const getter = jest.fn(() => {
24 | return value.foo;
25 | });
26 | const cValue = computed(getter);
27 | //lazy
28 | //没调用cValue 就不会调用getter
29 | expect(getter).not.toHaveBeenCalled();
30 |
31 | expect(cValue.value).toBe(1); //cValue.value触发get value -> 执行getter -> 这里才会触发value.foo ->触发reactive数据的getter 收集依赖
32 | expect(getter).toHaveBeenCalledTimes(1);
33 |
34 | // should not compute again
35 | cValue.value; //get
36 | expect(getter).toHaveBeenCalledTimes(1);
37 |
38 | // should not compute until needed
39 | value.foo = 2; // trigger -> effect ->get 重新执行了
40 | expect(getter).toHaveBeenCalledTimes(1);
41 |
42 | // now it should compute
43 | expect(cValue.value).toBe(2);
44 | expect(getter).toHaveBeenCalledTimes(2);
45 |
46 | // should not compute again
47 | cValue.value;
48 | expect(getter).toHaveBeenCalledTimes(2);
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "../reactive";
2 | import { effect, stop } from "../effect";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | const user = reactive({
7 | age: 10,
8 | });
9 |
10 | let nextAge;
11 | effect(() => {
12 | nextAge = user.age + 1;
13 | });
14 | // effect(() => {
15 | // user.age++;
16 | // });
17 |
18 | expect(nextAge).toBe(11);
19 | // update
20 | user.age++;
21 | expect(nextAge).toBe(12);
22 | });
23 | it("should return runner when call effect", () => {
24 | // 1、调用effect(fn) 会返回一个function runner函数
25 | //2、调用runner() 会再次调用fn -> 返回fn的返回值
26 |
27 | let foo = 0;
28 | const runner = effect(() => {
29 | foo++;
30 | return foo;
31 | });
32 |
33 | expect(foo).toBe(1);
34 | runner(); //函数执行,说明了effect返回了一个函数?并且返回的为传入的函数?
35 | expect(foo).toBe(2);
36 | expect(runner()).toBe(3);
37 | });
38 |
39 | it("scheduler", () => {
40 | //通过effect第二个参数给定一个scheduler的fn
41 | // effect 第一次执行的时候,还会执行第一个fn参数
42 | //当响应式对象 update 不会执行第一个fn参数,而是执行scheduler
43 | // 当执行runner的时候,会执行fn
44 | let dummy;
45 | let run: any;
46 | const scheduler = jest.fn(() => {
47 | run = runner;
48 | });
49 | const obj = reactive({ foo: 1 });
50 | const runner = effect(
51 | //effect 传入了两个参数 1回调函数 2 option对象
52 | () => {
53 | dummy = obj.foo;
54 | },
55 | { scheduler }
56 | );
57 | expect(scheduler).not.toHaveBeenCalled();
58 | expect(dummy).toBe(1);
59 |
60 | //should be called on first trigger
61 | obj.foo++;
62 | expect(scheduler).toHaveBeenCalledTimes(1); // scheduler被调用了一次
63 | //should not run yet
64 | expect(dummy).toBe(1);
65 | //manually run
66 | run();
67 | //should have run
68 | expect(dummy).toBe(2);
69 | //
70 | });
71 |
72 | it("stop", () => {
73 | // stop接受effect执行返回的函数作为参数。
74 | //用一个变量runner接受effect执行返回的函数
75 | //调用stop并传入runner后,当传入的函数依赖的响应式对象的 property 的值更新时不会再执行该函数,
76 | //只有当调用runner时才会恢复执行该函数。
77 | let dummy;
78 | const obj = reactive({ prop: 1 });
79 | const runner = effect(() => {
80 | dummy = obj.prop;
81 | });
82 | obj.prop = 2;
83 | expect(dummy).toBe(2);
84 | stop(runner);
85 | obj.prop = 3;
86 | expect(dummy).toBe(2); // trigger失效 代表依赖清空?怎么清空依赖呢?
87 | obj.prop++;
88 | expect(dummy).toBe(2);
89 | runner(); // runner 执行 effect.run 执行了fn -> obj.prop 触发了get 依赖又收集了
90 | expect(dummy).toBe(4);
91 | });
92 |
93 | it("onStop", () => {
94 | const obj = reactive({
95 | foo: 1,
96 | });
97 | const onStop = jest.fn();
98 | let dummy;
99 | const runner = effect(
100 | () => {
101 | dummy = obj.foo;
102 | },
103 | { onStop }
104 | );
105 | stop(runner);
106 | expect(onStop).toBeCalledTimes(1);
107 | });
108 |
109 | it("cleanup effect", () => {
110 | let dummy;
111 | let record;
112 | const obj = reactive({ prop: 1, foo: 2 });
113 | const runner = effect(() => {
114 | dummy = obj.prop;
115 | record = obj.foo;
116 | });
117 | expect(dummy).toBe(1);
118 | expect(record).toBe(2);
119 | stop(runner);
120 | //should not trigger effect
121 | obj.foo = 3;
122 | expect(dummy).toBe(1);
123 | expect(record).toBe(2);
124 | });
125 | });
126 |
--------------------------------------------------------------------------------
/src/reactivity/tests/index.spec.ts:
--------------------------------------------------------------------------------
1 | //测试单元测是否ok
2 |
3 | import {add} from "../index"
4 |
5 | it ("init",()=>{
6 | expect(add(1,1)).toBe(2)
7 | });
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive, isReactive, isProxy } from "../reactive";
2 | describe("reactive", () => {
3 | it("happy path", () => {
4 | const original = { foo: 1 };
5 | const observed = reactive(original);
6 | expect(observed).not.toBe(original); //检测obseved !== original
7 | expect(observed.foo).toBe(1);
8 |
9 | //isReactive
10 | expect(isReactive(observed)).toBe(true);
11 | expect(isReactive(original)).toBe(false);
12 |
13 | //isProxy
14 | expect(isProxy(observed)).toBe(true);
15 | expect(isProxy(original)).toBe(false);
16 | });
17 |
18 | it("nested reactive", () => {
19 | const original = {
20 | nested: {
21 | foo: 1,
22 | },
23 | arr: [{ bar: 2 }],
24 | };
25 | const observed = reactive(original);
26 |
27 | expect(isReactive(observed.nested)).toBe(true);
28 | expect(isReactive(observed.arr)).toBe(true);
29 | expect(isReactive(observed.arr[0])).toBe(true);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReadonly, readonly } from "../reactive";
2 |
3 | describe("readonly", () => {
4 | it("should make nested values readonly", () => {
5 | const original = { foo: 1, bar: { baz: 2 } };
6 | const wrapped = readonly(original);
7 | expect(wrapped).not.toBe(original);
8 | expect(wrapped.foo).toBe(1);
9 |
10 | //isReadonly
11 | expect(isReadonly(wrapped)).toBe(true);
12 | expect(isReadonly(original)).toBe(false);
13 |
14 | //isProxy
15 | expect(isProxy(wrapped)).toBe(true);
16 | expect(isProxy(original)).toBe(false);
17 |
18 | //嵌套
19 | expect(isReadonly(wrapped.bar)).toBe(true);
20 | });
21 |
22 | it("should call console.warn when set", () => {
23 | console.warn = jest.fn();
24 | const user = readonly({
25 | age: 10,
26 | });
27 |
28 | user.age = 11;
29 | expect(console.warn).toHaveBeenCalled();
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { isRef, proxyRefs, ref, unRef } from "../ref";
2 | import { effect } from "../effect";
3 | import { reactive } from "../reactive";
4 |
5 | describe("ref", () => {
6 | it("happy path", () => {
7 | const a = ref(1);
8 | expect(a.value).toBe(1);
9 | });
10 |
11 | it("should be reactive", () => {
12 | const a = ref(1);
13 | let dummy;
14 | let calls = 0;
15 | effect(() => {
16 | calls++;
17 | dummy = a.value;
18 | });
19 |
20 | expect(calls).toBe(1);
21 | expect(dummy).toBe(1);
22 | a.value = 2;
23 | expect(calls).toBe(2);
24 | expect(dummy).toBe(2);
25 |
26 | // same value should not trigger
27 | a.value = 2;
28 | expect(calls).toBe(2);
29 | expect(dummy).toBe(2);
30 | });
31 |
32 | it("should make nested properties reactive", () => {
33 | const a = ref({
34 | count: 1,
35 | });
36 | let dummy;
37 | effect(() => {
38 | dummy = a.value.count;
39 | });
40 |
41 | expect(dummy).toBe(1);
42 | a.value.count = 2;
43 | expect(dummy).toBe(2);
44 | });
45 |
46 | it("isRef", () => {
47 | const a = ref(1);
48 | const user = reactive({
49 | age: 1,
50 | });
51 |
52 | expect(isRef(a)).toBe(true);
53 | expect(isRef(1)).toBe(false);
54 | expect(isRef(user)).toBe(false);
55 | });
56 |
57 | it("unRef", () => {
58 | const a = ref(1);
59 |
60 | expect(unRef(a)).toBe(1);
61 | expect(unRef(1)).toBe(1);
62 | });
63 |
64 | it("proxyRefs", () => {
65 | const user = {
66 | age: ref(10),
67 | name: "aky",
68 | };
69 |
70 | //get -> age(ref) 那么就给他返回 .value
71 | // not ref -> return value
72 | const proxyUser = proxyRefs(user);
73 | expect(user.age.value).toBe(10);
74 | expect(proxyUser.age).toBe(10);
75 | expect(proxyUser.name).toBe("aky");
76 |
77 | proxyUser.age = 20;
78 | //set -> 是ref ->修改.val
79 | expect(proxyUser.age).toBe(20);
80 | expect(user.age.value).toBe(20);
81 |
82 | proxyUser.age = ref(10);
83 | //set -> 不是ref -> 直接替换掉
84 | expect(proxyUser.age).toBe(10);
85 | expect(user.age.value).toBe(10);
86 | });
87 | });
88 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { isReactive, isReadonly, reactive, shallowReactive } from "../reactive";
3 |
4 | describe("shallowReactive", () => {
5 | test("should not make non-reactive properties reactive", () => {
6 | const props = shallowReactive({ n: { foo: 1 } });
7 | expect(isReactive(props)).toBe(true);
8 | // expect(isReadonly(props)).toBe(false);
9 | expect(isReactive(props.n)).toBe(false);
10 | });
11 | test("happy shallowReactive", () => {
12 | const props = shallowReactive({
13 | bar: 2,
14 | n: { foo: 1 },
15 | });
16 |
17 | let nexProps;
18 | effect(() => {
19 | nexProps = props.bar + 1;
20 | });
21 |
22 | expect(nexProps).toBe(3);
23 |
24 | // update;
25 | props.bar++;
26 | expect(nexProps).toBe(4);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../reactive";
2 |
3 | describe("shallowReadonly", () => {
4 | test("should not make non-reactive properties reactive", () => {
5 | const props = shallowReadonly({ n: { foo: 1 } });
6 | expect(isReadonly(props)).toBe(true);
7 | expect(isReadonly(props.n)).toBe(false);
8 | });
9 |
10 | it("should call console.warn when set", () => {
11 | console.warn = jest.fn();
12 | const user = shallowReadonly({
13 | age: 10,
14 | });
15 |
16 | user.age = 11;
17 | expect(console.warn).toHaveBeenCalled();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from './component'
2 |
3 | export function provide(key, value) {
4 | //存
5 | //key value 存在哪里???? 存在当前实例对象上
6 |
7 | //获取当前组件实例对象
8 | const currentInstance: any = getCurrentInstance()
9 |
10 | if (currentInstance) {
11 | let { provides } = currentInstance
12 | const parentProvides = currentInstance.parent.provides
13 |
14 | //init 不能每次都初始化,只有第一次初始化
15 | //判断初始化状态 当前组件的provides = parentProvides
16 | if (provides === parentProvides) {
17 | provides = currentInstance.provides = Object.create(parentProvides) //利用原型原型链的机制 来进行多层inject provides
18 | }
19 |
20 | provides[key] = value
21 | }
22 | }
23 |
24 | export function inject(key, defaultValue) {
25 | //取
26 |
27 | const currentInstance: any = getCurrentInstance()
28 | if (currentInstance) {
29 | const parentProvides = currentInstance.parent.provides
30 |
31 | if (key in parentProvides) {
32 | return parentProvides[key]
33 | } else if (defaultValue) {
34 | if (typeof defaultValue === 'function') {
35 | return defaultValue()
36 | }
37 | return defaultValue
38 | }
39 | }
40 | }
41 |
42 | /**
43 | *
44 | * 思路: provide提供数据 inject获取数据
45 | *
46 | *
47 | * 1、基础作用 父子间的provide和inject
48 | * provide(key,value)
49 | * provide在setup中使用, 我们通过getCurrentInstance 获取到当前组件实例,并将传入的参数通过key-value的形式保存到instance.provide上
50 | *
51 | *
52 | * inject(key)
53 | *
54 | * 在setup中使用, 通过getCurrentInstance 获取到组件实例,,通过instance.parent.provide来获取父组件的provide
55 | * 通过key 老获取到所需要的值
56 | *
57 | *
58 | * 2、跨层级的provide和inject
59 | *
60 | * 前面的实现只支持 父子间的provide和inject传递,更深层次的传递利用了原型和原型链的机制
61 | *
62 | * 父组件 parentProvide
63 | * 子组件 provide
64 | * 孙组件 inject
65 | *
66 | * provide currentInstance.provide = Object.create(parentProvide)
67 | *
68 | * 在孙组件中 使用inject(key) 查找key的value
69 | * 先找子组件提供的provide,找到就直接返回value,
70 | * 找不到就会通过原型链查找parentProvide上的key值,逐级向上直到找到值
71 | *
72 | * 3、init provide
73 | *
74 | * 在创建instance实例的时候, 我们设置 parent和 provide 的默认值、
75 | * parent = parentInstance
76 | * provide = parent ? parent.provide :{}
77 | * 所以 provide中我们获取的provide 和 parentProvide 在最初是一致的
78 | *
79 | * 在进行object.create()后 provide会变成一个空对象,其prototype 指向 parentProvide
80 | *
81 | * 然后在空对象上进行新值的添加,我们不能每次进行provide都进行一次object.create 这样,我们在同一个setup中 之前的多次的provide只会生效最后一次
82 | *
83 | * 所以我们只需要进行一次原型链的继承
84 | *
85 | * 而这一次就要在最初的时候进行, 就是provide = parentProvide
86 | */
87 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs } from '../index'
2 | import { shallowReadonly } from '../reactivity/reactive'
3 | import { emit } from './componentEmit' //处理emit
4 | import { initProps } from './componentProps'
5 | import { PublicInstanceProxyHandlers } from './componentPublicInstance'
6 | import { initSlot } from './componentSlots'
7 |
8 | // 创建当前组件实例对象
9 | export function createComponentInstance(vnode, parent) {
10 | const component = {
11 | //初始化
12 | vnode, //* 当前组件的虚拟节点
13 | type: vnode.type, //* 当前虚拟节点的type object | string
14 | next: null, //! 下次更新的vnode节点
15 | setupState: {}, //* 记录setup函数执行后返回的结果
16 | props: {}, //* 创建props属性,方便instance实例进行初始化props
17 | slots: {}, //* 创建slots属性,方便instance实例进行初始化slots
18 | provides: parent ? parent.provides : {}, //! 初始化provides
19 | parent, //* 父组件
20 | emit: () => {}, // * 初始化emit
21 | isMounted: false, //! 挂载标识符 用于组件更新
22 | subTree: {}, //! elemnt 树
23 | }
24 | component.emit = emit.bind(null, component) as any //绑定instance为this 并返回函数 这里emit是从外部引入的emit
25 | return component
26 | }
27 |
28 | export function setupComponent(instance) {
29 | // TODO
30 | // initProps()
31 | console.log(instance)
32 | // * 初始化props
33 | initProps(instance, instance.vnode.props)
34 |
35 | //! 初始化slot
36 | initSlot(instance, instance.vnode.children)
37 |
38 | setupStatefulComponent(instance)
39 | }
40 |
41 | function setupStatefulComponent(instance: any) {
42 | const Component = instance.type
43 |
44 | //* 增加了代理对象
45 | //cxt
46 | console.log({ _: 123 })
47 |
48 | console.log({ _: instance })
49 |
50 | instance.proxy = new Proxy( //增加了代理对象
51 | { _: instance },
52 |
53 | // get(target, key) {
54 | // //setupState
55 | // const { setupState } = instance;
56 | // if (key in setupState) {
57 | // return setupState[key];
58 | // }
59 | // //key ->$el
60 | // if (key === "$el") {
61 | // return instance.vnode.el;
62 | // }
63 | // },
64 | PublicInstanceProxyHandlers
65 | )
66 |
67 | const { setup } = Component
68 |
69 | if (setup) {
70 | // currentInstance = instance;
71 | setCurrentInstance(instance)
72 | const setupResult = setup(shallowReadonly(instance.props), {
73 | emit: instance.emit,
74 | })
75 |
76 | setCurrentInstance(null)
77 |
78 | handleSetupResult(instance, setupResult)
79 | }
80 | }
81 |
82 | function handleSetupResult(instance, setupResult: any) {
83 | // function Object
84 | // TODO function
85 | if (typeof setupResult === 'object') {
86 | instance.setupState = proxyRefs(setupResult) //setup返回值的ref对象 直接key访问,不用key.value
87 | }
88 |
89 | finishComponentSetup(instance)
90 | }
91 |
92 | function finishComponentSetup(instance: any) {
93 | const Component = instance.type
94 | //如果用户不提供render函数 而是用的template
95 | if (compiler && !Component.render) {
96 | if (Component.template) {
97 | Component.render = compiler(Component.template)
98 | }
99 | }
100 |
101 | instance.render = Component.render
102 | }
103 |
104 | //借助全局变量获取instccne
105 |
106 | let currentInstance = null
107 |
108 | export function getCurrentInstance() {
109 | return currentInstance
110 | }
111 |
112 | function setCurrentInstance(instance) {
113 | currentInstance = instance
114 | }
115 |
116 | let compiler
117 |
118 | export function registerRuntimeCompiler(_compiler) {
119 | compiler = _compiler
120 | }
121 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, capitalize, toHandlerKey } from "../shared/index";
2 |
3 | export function emit(instance, event, ...args) {
4 | console.log("emit" + event);
5 |
6 | // instance.props 有没有对应event的回调
7 | const { props } = instance;
8 |
9 | //tpp ->
10 | //先去写一个特定行为 -》 重构通用行为
11 |
12 | // const handler = props["onAdd"];
13 |
14 | //add-foo ->addFoo
15 | // const camelize = (str: string) => {
16 | // return str.replace(/-(\w)/g, (_, c: string) => {
17 | // return c ? c.toUpperCase() : "";
18 | // });
19 | // };
20 | // //addFoo ->AddFoo
21 | // const capitalize = (str: string) => {
22 | // return str.charAt(0).toUpperCase() + str.slice(1);
23 | // };
24 | // console.log(capitalize(event));
25 |
26 | // const str = capitalize(camelize(event));
27 | // AddFoo -> noAddFoo
28 | // const toHandlerKey = (str: string) => {
29 | // return str ? "on" + str : "";
30 | // };
31 | //add-foo -> addFoo
32 | let str = camelize(event);
33 |
34 | //addFoo -> AddFoo
35 | str = capitalize(str);
36 | const handlerName = toHandlerKey(str);
37 | const handler = props[handlerName];
38 | handler && handler(...args);
39 | }
40 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, rawProps) {
2 | instance.props = rawProps || {};
3 | }
4 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from '../shared/index.js'
2 |
3 | const PublicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | $props: (i) => i.props,
7 | }
8 |
9 | // * proxy的getter方法 get(target,key)
10 | export const PublicInstanceProxyHandlers = {
11 | get({ _: instance }, key) {
12 | //这里的_:instance是解构
13 | // console.log("instance:", instance);
14 |
15 | //setupState
16 | const { setupState, props } = instance
17 | // if (key in setupState) {
18 | // return setupState[key];
19 | // }
20 |
21 | if (hasOwn(setupState, key)) {
22 | return setupState[key]
23 | } else if (hasOwn(props, key)) {
24 | return props[key]
25 | }
26 |
27 | //key ->$el
28 | const publicGetter = PublicPropertiesMap[key]
29 | if (publicGetter) {
30 | return publicGetter(instance)
31 | }
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { shapeFlags } from "../shared/shapeFlags";
2 |
3 | export function initSlot(instance, children) {
4 | // instance.slots = Array.isArray(children) ? children : [children];
5 |
6 | //children Object
7 |
8 | // const slots = {};
9 | // for (const key in children) {
10 | // const value = children[key];
11 |
12 | // slots[key] = normalizeSlotsValue(value);
13 | // }
14 |
15 | //
16 | // const { vnode } = instance;
17 | // if (vnode.shapeFlag & shapeFlags.slot_children) {
18 | // console.log("isntance.slots:" + instance.slots);
19 |
20 | normalizeObjectSlots(children, instance.slots);
21 | // }
22 | // instance.slots = slots;
23 | }
24 |
25 | function normalizeObjectSlots(children, slots) {
26 | // console.log("isntance.slots:" + JSON.stringify(slots));\
27 | console.log("children", children);
28 |
29 | for (const key in children) {
30 | const value = children[key];
31 | slots[key] = (props) => normalizeSlotsValue(value(props));
32 | // slots[key] = (props) => normalizeSlotsValue(value);
33 |
34 | console.log("isntance.slots:" + slots);
35 | }
36 | }
37 | function normalizeSlotsValue(value) {
38 | return Array.isArray(value) ? value : [value];
39 | }
40 |
41 | /**
42 | * 父组件通过children 传递 slot 类型为对象类型
43 | *
44 | * {
45 | * key:(props)=>{h()}
46 | * }
47 | * 通过遍历将所有插槽通过 key value的形式 保存在instance.slot对象中 使用this.$slots可以访问得到
48 | * 在子组件中进行插槽渲染的时候 通过renderSlots(this.$slots,name,props)函数
49 | * 每一个slot 都是一个函数, renderSlots函数中会执行插槽函数 生成vnode对象或者vnode对象组成的数组result
50 | * 然后将生成的结果通过createVnode(Fragment,{},result)包裹成vnode对象
51 | *
52 | * 又因为 result是vnode对象或者vnode对象组成的数组,
53 | * createVnode 传入的children 只能是数组或者string
54 | * 所有如果是vnode对象则需要先包裹一个[]数组,normalizeSlotsValue就是这个职责
55 | *
56 | */
57 |
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(preVnode, nextVnode) {
2 | const { props: prevProps } = preVnode;
3 | const { props: nextProps } = nextVnode;
4 | for (const key in nextProps) {
5 | if (nextProps[key] !== prevProps[key]) {
6 | return true;
7 | }
8 | }
9 | return false;
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | // import { render } from "./renderer";
2 | import { createVNode } from "./vnode";
3 |
4 | export function createAppAPI(render) {
5 | return function createApp(rootComponent) {
6 | return {
7 | mount(rootContainer) {
8 | const vnode = createVNode(rootComponent);
9 | render(vnode, rootContainer);
10 | },
11 | };
12 | };
13 | }
14 |
15 | // export function createApp(rootComponent) {
16 | // return {
17 | // mount(rootContainer) {
18 | // const vnode = createVNode(rootComponent);
19 |
20 | // render(vnode, rootContainer);
21 | // },
22 | // };
23 | // }
24 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 | export function h(type, props?, children?) {
3 | return createVNode(type, props, children);
4 | }
5 |
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | const slot = slots[name];
5 |
6 | if (slot) {
7 | if (typeof slot === "function") {
8 | return createVNode(Fragment, {}, slot(props));
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | // export { createApp } from "./createApp";
2 |
3 | export { h } from "./h";
4 |
5 | export { renderSlots } from "./helpers/renderSlots";
6 |
7 | export { createTextVnode, createElementVnode } from "./vnode";
8 |
9 | export { getCurrentInstance, registerRuntimeCompiler } from "./component";
10 |
11 | export { provide, inject } from "./apiInject";
12 |
13 | export { createRenderer } from "./renderer";
14 |
15 | export { nextTick } from "./scheduler";
16 | export { toDisplayString } from "../shared";
17 |
18 | export * from "../reactivity/index";
19 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '../reactivity/effect'
2 | import { EMPTY_OBJ, isObject } from '../shared/index'
3 | import { shapeFlags } from '../shared/shapeFlags'
4 | import { createComponentInstance, setupComponent } from './component'
5 | import { shouldUpdateComponent } from './componentUpdateUtils'
6 | import { createAppAPI } from './createApp'
7 | import { queueJobs } from './scheduler'
8 | import { Fragment, Text } from './vnode'
9 |
10 | export function createRenderer(options) {
11 | const {
12 | createElement: hostCreateElement,
13 | patchProp: hostPatchProp,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText,
17 | } = options
18 |
19 | function render(vnode, container) {
20 | // debugger;
21 | patch(null, vnode, container, null, null)
22 | }
23 |
24 | /**
25 | *
26 | * @param n1 老的vnode n1存在 更新逻辑 n1不存在 初始化逻辑
27 | * @param n2 新的vnode
28 | * @param container 容器
29 | * @param parentComponent 父组件 createInstance创建组件实例的时候 用得到
30 | */
31 |
32 | function patch(n1, n2, container, parentComponent, anchor) {
33 | //TODO 判断vnode是不是一个element
34 | //是element 就应该处理element
35 | //如何去区分是element类型和component类型 :vnode.type 来判断
36 | // console.log(vnode.type);
37 |
38 | // const { type, shapeFlag } = vnode;
39 | const { type, shapeFlag } = n2
40 |
41 | //Fragment -> 只渲染children
42 | switch (type) {
43 | case Fragment:
44 | processFragment(n1, n2, container, parentComponent, anchor)
45 | break
46 | case Text:
47 | processText(n1, n2, container)
48 | break
49 | default:
50 | if (shapeFlag & shapeFlags.element) {
51 | // if (typeof vnode.type === "string") {
52 | // element类型
53 | processElement(n1, n2, container, parentComponent, anchor)
54 | // } else if (isObject(vnode.type)) {
55 | } else if (shapeFlag & shapeFlags.stateful_component) {
56 | // component类型
57 | processComponent(n1, n2, container, parentComponent, anchor)
58 | }
59 | }
60 | }
61 |
62 | function processFragment(n1, n2, container: any, parentComponent, anchor) {
63 | mountChildren(n2, container, parentComponent, anchor)
64 | }
65 |
66 | function processText(n1: any, n2: any, container: any) {
67 | const { children } = n2
68 | const textNode = (n2.el = document.createTextNode(children))
69 | container.appendChild(textNode)
70 | }
71 | //element vnode.type为element类型
72 | function processElement(n1, n2, container, parentComponent, anchor) {
73 | //init 初始化
74 | if (!n1) {
75 | mountElement(n2, container, parentComponent, anchor)
76 | } else {
77 | //TODO UPDATE
78 | console.log('patchElement')
79 |
80 | patchElement(n1, n2, container, parentComponent, anchor)
81 | }
82 | }
83 |
84 | //挂载element
85 | function mountElement(vnode: any, container: any, parentComponent, anchor) {
86 | //跨平台渲染
87 | //canvas
88 | // new Element()
89 |
90 | //Dom平台
91 | // const el = document.createElement("div")
92 | // const el = (n2.el = document.createElement(n2.type));
93 | const el = (vnode.el = hostCreateElement(vnode.type))
94 |
95 | //props
96 | // el.setttribute("id", "root");
97 | const { props } = vnode
98 | for (const key in props) {
99 | const val = props[key]
100 | // console.log(key);
101 | // // const isOn = (key) => /^on[A-Z]/.test(key);
102 | // // if(isOn(key)){
103 | // if (key.startsWith("on")) {
104 | // // console.log(key.split("on")[1]);
105 | // const event = key.slice(2).toLowerCase();
106 | // el.addEventListener(event, val);
107 | // } else {
108 | // el.setAttribute(key, val);
109 | // }
110 |
111 | hostPatchProp(el, key, null, val)
112 | }
113 |
114 | //children
115 |
116 | // el.textContent = "hi mini-vue";
117 | const { children, shapeFlag } = vnode
118 |
119 | if (shapeFlag & shapeFlags.text_children) {
120 | // if (typeof children === "string") {
121 | //children为srting类型
122 | el.textContent = children
123 | // } else if (Array.isArray(children)) {
124 | } else if (shapeFlag & shapeFlags.array_children) {
125 | //children 是数组类型
126 | // children.forEach((v) => {
127 | // patch(v, el);
128 | // });
129 | mountChildren(vnode, el, parentComponent, anchor)
130 | }
131 |
132 | //挂载要渲染的el
133 | // document.appendChild(el)
134 | // container.appendChild(el);
135 | // container.append(el);
136 |
137 | hostInsert(el, container, anchor)
138 | }
139 | function mountChildren(childrenVnode, container, parentComponent, anchor) {
140 | childrenVnode.children.forEach((v) => {
141 | patch(null, v, container, parentComponent, anchor)
142 | })
143 | }
144 |
145 | //更新element
146 | function patchElement(n1, n2, container, parentComponent, anchor) {
147 | console.log('n1:', n1)
148 | console.log('n2:', n2)
149 | //type
150 |
151 | //props
152 | const oldProps = n1.props || EMPTY_OBJ
153 | const newProps = n2.props || EMPTY_OBJ
154 | const el = (n2.el = n1.el)
155 |
156 | //1、key不变 value 改变
157 | //2、 value= undefined 、null ==> 删除key
158 | //3、 老的vnode 里的key 在新的element vnode不存在了 ==> 删除
159 | patchProps(el, oldProps, newProps)
160 |
161 | // children
162 |
163 | patchChildren(n1, n2, el, parentComponent, anchor)
164 | }
165 |
166 | // const EMPTY_OBJ = {}
167 | function patchProps(el, oldProps, newProps) {
168 | // debugger;
169 | if (oldProps !== newProps) {
170 | for (const key in newProps) {
171 | const prevProp = oldProps[key]
172 | const nextProp = newProps[key]
173 | if (prevProp !== nextProp) {
174 | hostPatchProp(el, key, prevProp, nextProp)
175 | }
176 | }
177 |
178 | //第三个场景
179 | if (oldProps !== EMPTY_OBJ) {
180 | for (const key in oldProps) {
181 | if (!(key in newProps)) {
182 | hostPatchProp(el, key, oldProps[key], null)
183 | }
184 | }
185 | }
186 | }
187 | }
188 |
189 | function patchChildren(n1, n2, container, parentComponent, anchor) {
190 | // ArrayToText
191 | //判断新节点的shapeFlag 判断 children是text还是array
192 | // const prevShapeFlag = n1.shapeFlag;
193 | const { shapeFlag: prevShapeFlag } = n1.shapeFlag
194 | const { shapeFlag } = n2
195 | const c1 = n1.children
196 | const c2 = n2.children
197 | if (shapeFlag & shapeFlags.text_children) {
198 | //新节点children为 text类型
199 | if (prevShapeFlag & shapeFlags.array_children) {
200 | //老节点children 为array类型
201 | //1、把老节点清空
202 | unmountedChildren(n1.children)
203 | //2、设置text
204 | hostSetElementText(container, c2)
205 | } else {
206 | //老节点children为 text类型
207 | if (c1 !== c2) {
208 | //设置text
209 | hostSetElementText(container, c2)
210 | }
211 | }
212 | } else {
213 | //新节点children为 array类型
214 | if (prevShapeFlag & shapeFlags.text_children) {
215 | //老节点children为text
216 | // 1、清空老节点
217 | hostSetElementText(container, '')
218 | // 2、设置新节点
219 | mountChildren(n2, container, parentComponent, anchor)
220 | } else {
221 | //老节点children 为array类型
222 | patchKeyChildren(c1, c2, container, parentComponent, anchor)
223 | }
224 | }
225 | }
226 |
227 | function unmountedChildren(children) {
228 | for (let i = 0; i < children.length; i++) {
229 | const el = children[i].el
230 | //remove
231 | hostRemove(el)
232 | }
233 | }
234 |
235 | function patchKeyChildren(c1, c2, container, parentComponent, anchor) {
236 | // debugger;
237 | let i = 0
238 | let e1 = c1.length - 1
239 | let e2 = c2.length - 1
240 | //1.左侧对比
241 | while (i <= e1 && i <= e2) {
242 | const n1 = c1[i]
243 | const n2 = c2[i]
244 |
245 | if (isSameVnodeType(n1, n2)) {
246 | patch(n1, n2, container, parentComponent, anchor)
247 | } else {
248 | break
249 | }
250 | i++
251 | }
252 |
253 | //2.右侧对比
254 | while (i <= e1 && i <= e2) {
255 | const n1 = c1[e1]
256 | const n2 = c2[e2]
257 |
258 | if (isSameVnodeType(n1, n2)) {
259 | patch(n1, n2, container, parentComponent, anchor)
260 | } else {
261 | break
262 | }
263 | e1--
264 | e2--
265 | }
266 |
267 | //3.新的比老的多 添加到指定的位置
268 | if (i > e1) {
269 | if (i <= e2) {
270 | const nextPos = e2 + 1
271 | // const anchor = i + 1 < c2.length ? c2[nextPos].el : null; 有bug
272 | const anchor = nextPos < c2.length ? c2[nextPos].el : null
273 |
274 | while (i <= e2) {
275 | patch(null, c2[i], container, parentComponent, anchor)
276 |
277 | i++
278 | }
279 | }
280 | } else if (i > e2) {
281 | //4、新的比老少
282 | while (i <= e1) {
283 | hostRemove(c1[i].el)
284 | i++
285 | }
286 | } else {
287 | //中间对比
288 | let s1 = i
289 | let s2 = i
290 | let patched = 0
291 | const toBePatch = e2 - s2 + 1 //记录新节点的数量 用于老节点多余新节点时 删除逻辑的优化
292 | console.log(s2, e2)
293 |
294 | console.log('toBePatch', toBePatch)
295 |
296 | //建立新child.key的映射表
297 | const keyToNewIndexMap = new Map()
298 |
299 | //简历 最长递归子序列的映射表 最终结果是中间老节点新节点都有的节点在老节点数组的索引+1 排序
300 | const newIndexToOldIndexMap = new Array(toBePatch)
301 |
302 | let moved = false
303 | let maxNewIndexSoFar = 0
304 | //初始化 最长递归子序列的映射表
305 | for (let i = 0; i < toBePatch; i++) {
306 | newIndexToOldIndexMap[i] = 0
307 | }
308 |
309 | //将新的需要更新的(key:索引) 添加到映射表中
310 | for (let i = s2; i <= e2; i++) {
311 | let nextChild = c2[i]
312 | keyToNewIndexMap.set(nextChild.key, i)
313 | }
314 |
315 | for (let i = s1; i <= e1; i++) {
316 | const prevChild = c1[i]
317 |
318 | if (patched >= toBePatch) {
319 | //patch的数量大于需要更新的数量时 表示新的需要更新的已经更新完毕,剩下多的可以直接删除
320 | hostRemove(prevChild.el)
321 | continue
322 | }
323 |
324 | let newIndex
325 | if (prevChild.key != null) {
326 | //有key情况
327 | newIndex = keyToNewIndexMap.get(prevChild.key)
328 | } else {
329 | //无key
330 | for (let j = s2; j <= e2; j++) {
331 | if (isSameVnodeType(prevChild, c2[j])) {
332 | newIndex = j
333 | break
334 | }
335 | }
336 | }
337 | //删除逻辑
338 | if (newIndex === undefined) {
339 | console.log('删除')
340 |
341 | hostRemove(prevChild.el)
342 | } else {
343 | if (newIndex >= maxNewIndexSoFar) {
344 | maxNewIndexSoFar = newIndex
345 | } else {
346 | moved = true
347 | }
348 | //新老节点建立映射关系
349 | newIndexToOldIndexMap[newIndex - s2] = i + 1 //i+1 避免i=0的情况
350 | //更新逻辑
351 | patch(prevChild, c2[newIndex], container, parentComponent, null)
352 | patched++ //更新一次 数量+1
353 | }
354 | }
355 |
356 | //得到最长递增子序列
357 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []
358 | console.log('newIndexToOldIndexMap', newIndexToOldIndexMap)
359 | console.log('最长递增子序列', increasingNewIndexSequence)
360 |
361 | let j = increasingNewIndexSequence.length - 1
362 |
363 | for (let i = toBePatch - 1; i >= 0; i--) {
364 | const nextIndex = i + s2
365 | const nextChild = c2[nextIndex]
366 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null
367 | if (newIndexToOldIndexMap[i] === 0) {
368 | console.log('新增')
369 | patch(null, nextChild, container, parentComponent, anchor)
370 | } else if (moved) {
371 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
372 | console.log('移动位置')
373 | hostInsert(nextChild.el, container, anchor)
374 | } else {
375 | j--
376 | }
377 | }
378 | }
379 | }
380 | }
381 |
382 | function isSameVnodeType(n1, n2) {
383 | return n1.type === n2.type && n1.key === n2.key
384 | }
385 | //componentvnode.type为component类型
386 | function processComponent(n1, n2: any, container: any, parentComponent, anchor) {
387 | if (!n1) {
388 | mountComponent(n1, n2, container, parentComponent, anchor)
389 | } else {
390 | updateComponent(n1, n2)
391 | }
392 | }
393 |
394 | //组件初始化
395 | function mountComponent(n1, initialVNode: any, container, parentComponent, anchor) {
396 | // * 创建当前组件实例 方便后续对当前组件的操作
397 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent))
398 | // * 处理组件setup函数
399 | setupComponent(instance)
400 | setupRenderEffect(instance, initialVNode, container, anchor)
401 | }
402 |
403 | function setupRenderEffect(instance: any, initialVNode, container, anchor) {
404 | //响应式
405 | instance.update = effect(
406 | () => {
407 | // 区分式初始化还是更新
408 | if (!instance.isMounted) {
409 | //init
410 | console.log('init')
411 | const { proxy } = instance
412 | // instance.subtree 用于存储当前组件的render函数生成 vnode? 是否可以称为vnode?
413 | const subTree = (instance.subTree = instance.render.call(proxy, proxy)) //subTree 虚拟节点树 vnode树
414 | console.log(subTree)
415 |
416 | patch(null, subTree, container, instance, anchor)
417 |
418 | //element ->mount
419 | // ! 将render生成的vnode的el 存储到组件的el
420 | initialVNode.el = subTree.el
421 |
422 | instance.isMounted = true
423 | } else {
424 | //update
425 | console.log('update')
426 | const { proxy, next, vnode } = instance
427 |
428 | if (next) {
429 | next.el = vnode.el
430 |
431 | updateComponentPreRender(instance, next)
432 | }
433 |
434 | const subTree = instance.render.call(proxy, proxy) //subTree 虚拟节点树 vnode树
435 | console.log(subTree)
436 | const preSubTree = instance.subTree
437 |
438 | console.log(preSubTree)
439 | console.log(subTree)
440 | instance.subTree = subTree //将现在的subTree更新到instance.subTree 方便后续再次更新
441 |
442 | patch(preSubTree, subTree, container, instance, anchor)
443 | }
444 | },
445 | {
446 | scheduler() {
447 | console.log('scheduler执行啦')
448 | queueJobs(instance.update)
449 | },
450 | }
451 | )
452 | }
453 |
454 | function updateComponent(n1, n2) {
455 | //获取到挂载到vnode上的component
456 | const instance = (n2.component = n1.component)
457 | if (shouldUpdateComponent(n1, n2)) {
458 | instance.next = n2
459 | instance.update() // 利用effect返回值runner 调用runner()会再次执行fn
460 | } else {
461 | n2.el = n1.el
462 | instance.vnode = n2
463 | }
464 | }
465 |
466 | return {
467 | createApp: createAppAPI(render),
468 | }
469 | }
470 |
471 | //最长递增子序列 return 递归子序列的索引数组
472 | function getSequence(arr) {
473 | const p = arr.slice()
474 | const result = [0]
475 | let i, j, u, v, c
476 | const len = arr.length
477 | for (i = 0; i < len; i++) {
478 | const arrI = arr[i]
479 | if (arrI !== 0) {
480 | j = result[result.length - 1]
481 | if (arr[j] < arrI) {
482 | p[i] = j
483 | result.push(i)
484 | continue
485 | }
486 | u = 0
487 | v = result.length - 1
488 | while (u < v) {
489 | c = (u + v) >> 1
490 | if (arr[result[c]] < arrI) {
491 | u = c + 1
492 | } else {
493 | v = c
494 | }
495 | }
496 | if (arrI < arr[result[u]]) {
497 | if (u > 0) {
498 | p[i] = result[u - 1]
499 | }
500 | result[u] = i
501 | }
502 | }
503 | }
504 | u = result.length
505 | v = result[u - 1]
506 | while (u-- > 0) {
507 | result[u] = v
508 | v = p[v]
509 | }
510 | return result
511 | }
512 |
513 | //组件更新之前 需要将组件中的props 等数据先更新
514 | function updateComponentPreRender(instance: any, nextVndoe: any) {
515 | instance.vnode = nextVndoe
516 | instance.next = null
517 | instance.props = nextVndoe.props
518 | }
519 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue: any[] = [];
2 |
3 | let isFlushPending = false;
4 |
5 | export function nextTick(fn) {
6 | return fn ? Promise.resolve().then(fn) : Promise.resolve();
7 | }
8 |
9 | export function queueJobs(job) {
10 | if (!queue.includes(job)) {
11 | queue.push(job);
12 | }
13 | queueFlush();
14 | }
15 |
16 | function queueFlush() {
17 | if (isFlushPending) return;
18 | isFlushPending = true;
19 | // Promise.resolve().then(() => {
20 | // isFlushPending = false;
21 | // let job;
22 | // while ((job = queue.shift())) {
23 | // job && job();
24 | // }
25 | // });
26 | nextTick(flushJobs);
27 | }
28 |
29 | function flushJobs() {
30 | isFlushPending = false;
31 | let job;
32 | while ((job = queue.shift())) {
33 | job && job();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from '../shared/index'
2 | import { shapeFlags } from '../shared/shapeFlags'
3 |
4 | export const Fragment = Symbol('Fragment')
5 | export const Text = Symbol('Text')
6 |
7 | export { createVNode as createElementVnode }
8 |
9 | export function createVNode(type, props?, children?) {
10 | const vnode = {
11 | type,
12 | props,
13 | children,
14 | key: props && props.key,
15 | shapeFlag: getShapeFlag(type),
16 | el: null, //$el用的
17 | component: null,
18 | }
19 | //children
20 | if (typeof children === 'string') {
21 | vnode.shapeFlag = vnode.shapeFlag | shapeFlags.text_children
22 | } else if (Array.isArray(children)) {
23 | vnode.shapeFlag = vnode.shapeFlag | shapeFlags.array_children
24 | }
25 |
26 | // 组件 + children 为object类型
27 | if (vnode.shapeFlag & shapeFlags.stateful_component) {
28 | if (isObject(children)) {
29 | vnode.shapeFlag = vnode.shapeFlag | shapeFlags.slot_children
30 | }
31 | }
32 |
33 | return vnode
34 | }
35 |
36 | export function createTextVnode(text: string) {
37 | return createVNode(Text, {}, text)
38 | }
39 |
40 | function getShapeFlag(type) {
41 | return typeof type === 'string' ? shapeFlags.element : shapeFlags.stateful_component
42 | }
43 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../runtime-core/index";
2 |
3 | //创建element元素
4 | function createElement(type) {
5 | return document.createElement(type);
6 | }
7 |
8 | // function patchProp(el, key, preVal, nextVal) {
9 | // // const isOn = (key) => /^on[A-Z]/.test(key);
10 | // // if(isOn(key)){
11 | // if (key.startsWith("on")) {
12 | // // console.log(key.split("on")[1]);
13 | // const event = key.slice(2).toLowerCase();
14 | // el.addEventListener(event, nextVal);
15 | // } else {
16 | // if (nextVal === undefined || nextVal === null) {
17 | // el.removeAttribute(key);
18 | // } else {
19 | // el.setAttribute(key, nextVal);
20 | // }
21 | // }
22 | // }
23 |
24 | //创建、更新props
25 | function patchProp(el, key, prevVal, nextVal) {
26 | const isOn = (key: string) => /^on[A-Z]/.test(key);
27 | if (isOn(key)) {
28 | const event = key.slice(2).toLowerCase();
29 | el.addEventListener(event, nextVal);
30 | } else {
31 | if (nextVal === "undefined" || nextVal === null) {
32 | el.removeAttribute(key);
33 | } else {
34 | el.setAttribute(key, nextVal);
35 | }
36 | }
37 | }
38 |
39 | //指定位置插入
40 | function insert(child, parent, anchor) {
41 | //只是添加到后面
42 | // parent.append(child);
43 |
44 | // 添加到指定位置
45 | parent.insertBefore(child, anchor || null);
46 | }
47 |
48 | //删除children
49 | function remove(child) {
50 | const parent = child.parentNode;
51 | if (parent) {
52 | parent.removeChild(child);
53 | }
54 | }
55 |
56 | function setElementText(el, text) {
57 | el.textContent = text;
58 | }
59 |
60 | const renderer: any = createRenderer({
61 | createElement,
62 | patchProp,
63 | insert,
64 | remove,
65 | setElementText,
66 | });
67 |
68 | export function createApp(...args) {
69 | return renderer.createApp(...args);
70 | }
71 |
72 | export * from "../runtime-core/index";
73 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./toDisplayString";
2 |
3 | export const extend = Object.assign;
4 |
5 | export const EMPTY_OBJ = {};
6 |
7 | export function isObject(val) {
8 | return val !== null && typeof val === "object";
9 | }
10 |
11 | export const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
12 |
13 | //add-foo ->addFoo
14 | export const camelize = (str: string) => {
15 | return str.replace(/-(\w)/g, (_, c: string) => {
16 | return c ? c.toUpperCase() : "";
17 | });
18 | };
19 | //addFoo ->AddFoo
20 | export const capitalize = (str: string) => {
21 | return str.charAt(0).toUpperCase() + str.slice(1);
22 | };
23 | // console.log(capitalize(event));
24 |
25 | // AddFoo -> toAddFoo
26 | export const toHandlerKey = (str: string) => {
27 | return str ? "on" + str : "";
28 | };
29 |
30 | export const isString = (value) => typeof value === "string";
31 |
--------------------------------------------------------------------------------
/src/shared/shapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum shapeFlags {
2 | element = 1, //0001
3 | stateful_component = 1 << 1, // << 左移1位 0010
4 | text_children = 1 << 2, // 左移2位 0100
5 | array_children = 1 << 3, //左移3位 1000
6 | slot_children = 1 << 4, //左移四位 10000
7 | }
8 |
--------------------------------------------------------------------------------
/src/shared/text.ts:
--------------------------------------------------------------------------------
1 | const shapeFlags = {
2 | element: 0,
3 | stateful_component: 0,
4 | text_children: 0,
5 | array_children: 0,
6 | };
7 |
8 | //可以设置,修改
9 | // v node -> stateful_component ->
10 | //shapeFlags.stateful_component = 1
11 | //shapeFlags.array_children = 1,
12 |
13 | //2. 查找
14 | // if(shapeFlags.element)
15 | // if(shapeFlags.stateful_component)
16 |
17 | // 不够高效 -> 位运算的方式
18 | // 0000;
19 |
20 | // 0001 -> Element
21 | // 0010 -> stateful
22 | // 0100 -> text_children
23 | // 1000 -> array_children
24 |
25 | // | 或运算 两位都为0 才为0
26 | // & 与运算 两位都为1,才为1
27 |
28 | //修改
29 | // 0000
30 | // 0001
31 | // ----
32 | // 0000 | 0001 = 0001
33 |
34 | //查找 &
35 | // 0001
36 | // 0001
37 | // ----
38 | // 0001
39 |
--------------------------------------------------------------------------------
/src/shared/toDisplayString.ts:
--------------------------------------------------------------------------------
1 | export function toDisplayString(value) {
2 | return String(value);
3 | }
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
8 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
9 | "lib": ["DOM", "ES6", "ES2016.Array.Include"] /* Specify library files to be included in the compilation. */,
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | // "outDir": "./", /* Redirect output structure to the directory. */
18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true /* Enable all strict type-checking options. */,
29 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
45 |
46 | /* Module Resolution Options */
47 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
51 | // "typeRoots": [], /* List of folders to include type definitions from. */
52 | "types": ["jest"] /* Type declaration files to be included in compilation. */,
53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
54 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
57 |
58 | /* Source Map Options */
59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
63 |
64 | /* Experimental Options */
65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
67 |
68 | /* Advanced Options */
69 | "skipLibCheck": true /* Skip type checking of declaration files. */,
70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
71 | }
72 | }
73 |
--------------------------------------------------------------------------------