├── .gitignore
├── README.md
├── babel.config.js
├── example
├── apiInject
│ ├── App.js
│ └── index.html
├── compiler-base
│ ├── App.js
│ ├── index.html
│ └── main.js
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentUpdate
│ ├── App.js
│ ├── Child.js
│ ├── index.html
│ └── main.js
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── customRenderer
│ ├── App.js
│ ├── index.html
│ └── main.js
├── helloWorld
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── nextTicker
│ ├── App.js
│ ├── index.html
│ └── main.js
├── patchChildren
│ ├── App.js
│ ├── ArrayToArray.js
│ ├── ArrayToText.js
│ ├── TextToArray.js
│ ├── TextToText.js
│ ├── index.html
│ └── main.js
└── update
│ ├── App.js
│ ├── index.html
│ └── main.js
├── lib
├── guide-mini-vue.cjs.js
└── guide-mini-vue.esm.js
├── md
├── customRender.md
└── tag.md
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── 112.js
├── compiler-core
│ ├── src
│ │ ├── ast.ts
│ │ ├── codegen.ts
│ │ ├── compile.ts
│ │ ├── index.ts
│ │ ├── parse.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transform.ts
│ │ ├── transforms
│ │ │ ├── transformElement.ts
│ │ │ ├── transformExpression.ts
│ │ │ └── transformText.ts
│ │ └── utils.ts
│ └── tests
│ │ ├── __snapshots__
│ │ └── codegen.spec.ts.snap
│ │ ├── codegen.spec.ts
│ │ ├── parse.spec.ts
│ │ └── transform.spec.ts
├── index.ts
├── reactivity
│ ├── baseHandlers.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reaadonly.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── ref.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
│ │ └── renderSolts.ts
│ ├── index.ts
│ ├── renderer.ts
│ ├── scheduler.ts
│ └── vnode.ts
├── runtime-dom
│ └── index.ts
└── shared
│ ├── ShapeFlags.ts
│ ├── index.ts
│ └── toDisplayString.ts
├── tsconfig.json
├── yarn-error.log
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 给特定的某个 commit 版本打标签,比如现在某次提交的 id 为 039bf8b
2 |
3 | - git tag v1.0.0 039bf8b
4 | - git tag v1.0.0 -m "add tags information" 039bf8b
5 | - git tag v1.0.0 039bf8b -m "add tags information"
6 |
7 | ### 删除本地某个标签
8 |
9 | - git tag --delete v1.0.0
10 | - git tag -d v1.0.0
11 | - git tag --d v1.0.0
12 |
13 | ### 删除远程的某个标签
14 |
15 | - git push -d origin v1.0.0
16 | - git push --delete origin v1.0.0
17 | - git push origin -d v1.0.0
18 | - git push origin --delete v1.0.0
19 | - git push origin :v1.0.0
20 |
21 | ### 将本地标签一次性推送到远程 注意 这并不代表 push 代码,代码需要单独的 git push
22 |
23 | - git push --tag
24 |
25 | ```
26 | it("happy path", () => {
27 | const user = reactive({
28 | age:10
29 | })
30 | let nextAge
31 | effect(() => {
32 | nextAge = user.age + 1
33 | })
34 | expect(nextAge).toBe(11)
35 | ```
36 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", {targets: {node: "current"}}],
4 | "@babel/preset-typescript"
5 | ]
6 | }
--------------------------------------------------------------------------------
/example/apiInject/App.js:
--------------------------------------------------------------------------------
1 | // 组件 provide 和 inject 功能
2 | import { h, provide, inject } from "../../lib/guide-mini-vue.esm.js";
3 |
4 | const Provider = {
5 | name: "Provider",
6 | setup() {
7 | provide("foo", "fooOne11111");
8 | provide("bar", "barVal1111");
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", "fooTwo22222");
19 | const foo = inject("foo");
20 |
21 | return {
22 | foo,
23 | };
24 | },
25 | render() {
26 | return h("div", {}, [
27 | h("p", {}, `ProviderTwo foo:${this.foo}`),
28 | h(Consumer),
29 | ]);
30 | },
31 | };
32 |
33 | const Consumer = {
34 | name: "Consumer",
35 | setup() {
36 | const foo = inject("foo");
37 | const bar = inject("bar");
38 | // const baz = inject("baz", "bazDefault");
39 | const baz = inject("baz", () => "bazDefault");
40 |
41 | return {
42 | foo,
43 | bar,
44 | baz,
45 | };
46 | },
47 |
48 | render() {
49 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`);
50 | },
51 | };
52 |
53 | export default {
54 | name: "App",
55 | setup() {},
56 | render() {
57 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]);
58 | },
59 | };
60 |
--------------------------------------------------------------------------------
/example/apiInject/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/compiler-base/App.js:
--------------------------------------------------------------------------------
1 | import { ref } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 | template: `hi,{{count}}, {{message}}
`,
6 | // template: `hi,{{message}}
`,
7 | setup() {
8 | const count = (window.count = ref(1));
9 | return {
10 | count,
11 | message: "mini-vue",
12 | };
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/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/guide-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 {
2 | h
3 | } from '../../lib/guide-mini-vue.esm.js'
4 | import {
5 | Foo
6 | } from './Foo.js'
7 |
8 | export const APP = {
9 |
10 | setup() {
11 | return {
12 | msg: 'this setState'
13 | }
14 | },
15 | render() {
16 | return h('div', {}, [
17 | h('div', {}, 'app'),
18 | h(Foo, {
19 | onAdd(a,b) {
20 | console.log('onAdd',a,b);
21 | },
22 | onAddFoo (a,b) {
23 | console.log('onAddFoo',a,b);
24 | }
25 | })
26 | ])
27 | },
28 | }
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 | export const Foo = {
3 | setup(props, { emit }) {
4 | const emitAdd = () => {
5 | emit("add",1,6)
6 | emit("add-foo",2,5)
7 | };
8 | return {
9 | emitAdd,
10 | };
11 | },
12 | render() {
13 | const btn = h(
14 | "button",
15 | {
16 | onClick: this.emitAdd,
17 | },
18 | "emitAdd"
19 | );
20 | const foo = h("p", {}, "foo");
21 |
22 | return h("div", {}, [foo, btn]);
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | const rootContainer = document.querySelector('#app')
3 | import { APP } from './App.js'
4 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h,createTextVNode } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const APP = {
5 | render() {
6 | const app = h("div", {}, "App");
7 | // 虚拟节点的children 赋值给slots
8 | // p标签
9 | // const foo = h(Foo,{},h("p", {}, "123"))
10 |
11 | // 数组
12 | // const foo = h(Foo, {}, [h("p", {}, "header"), h("p", {}, "footer")]);
13 |
14 | // 对象,具名插槽
15 | const foo = h(
16 | Foo,
17 | {},
18 | {
19 | header: ({ age }) => [
20 | h("p", {}, "header" + age),
21 | createTextVNode("text vnode"),
22 | ],
23 | footer: () => h("p", {}, "footer"),
24 | }
25 | );
26 | return h("div", {}, [app, foo]);
27 | },
28 | setup() {
29 | return {};
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js";
2 | export const Foo = {
3 | setup() {
4 | return {};
5 | },
6 | render() {
7 | const foo = h("p", {}, "foo");
8 | console.log(this.$slots);
9 |
10 | // children 可以为string, array
11 | // children在这里应该是vnode,vnode只能是string或组件,如果是数组应该处理
12 | // return h("div", {}, [foo, this.$slots]);不能渲染
13 | // 所以这里要用一个vnode包裹一下
14 | // return h("div", {}, [foo, h("div", {}, this.$slots)]);
15 | // 优化一下
16 |
17 | // 带参数 作用域插槽
18 | return h("div", {}, [
19 | renderSlots(this.$slots, "header", {age: 18}),
20 | foo,
21 | renderSlots(this.$slots, "footer"),
22 | ]);
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | const rootContainer = document.querySelector('#app')
3 | import { APP } from './App.js'
4 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/componentUpdate/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js";
2 | import Child from "./Child.js";
3 |
4 | export const App = {
5 | name: "App",
6 | setup() {
7 | const msg = ref("123");
8 | const count = ref(1);
9 |
10 | window.msg = msg;
11 |
12 | const changeChildProps = () => {
13 | msg.value = "456";
14 | };
15 |
16 | const changeCount = () => {
17 | count.value++;
18 | };
19 |
20 | return { msg, changeChildProps, changeCount, count };
21 | },
22 |
23 | render() {
24 | return h("div", {}, [
25 | h("div", {}, "你好"),
26 | h(
27 | "button",
28 | {
29 | onClick: this.changeChildProps,
30 | },
31 | "change child props"
32 | ),
33 | h(Child),
34 | h(
35 | "button",
36 | {
37 | onClick: this.changeCount,
38 | },
39 | "change self count"
40 | ),
41 | h("p", {}, "count: " + this.count),
42 | ]);
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/example/componentUpdate/Child.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 | export default {
3 | name: "Child",
4 | setup(props, { emit }) {},
5 | render(proxy) {
6 | return h("div", {}, [h("div", {}, "child - props - msg: " + this.$props.msg)]);
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/example/componentUpdate/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/componentUpdate/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const APP = {
5 | render() {
6 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]);
7 | },
8 | setup() {
9 | const instance = getCurrentInstance();
10 | console.log("App:", instance);
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | setup(props, {}) {
6 | const instance = getCurrentInstance();
7 | console.log("Foo: ", instance);
8 | return {};
9 | },
10 | render() {
11 | return h("div", {}, "foo");
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from '../../lib/guide-mini-vue.esm.js'
2 | const rootContainer = document.querySelector('#app')
3 | import { APP } from './App.js'
4 | createApp(APP).mount(rootContainer)
--------------------------------------------------------------------------------
/example/customRenderer/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const App = {
4 | setup() {
5 | return {
6 | x: 100,
7 | y: 100,
8 | };
9 | },
10 | render() {
11 | return h("rect", { x: this.x, y: this.y });
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/example/customRenderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/example/customRenderer/main.js:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const game = new PIXI.Application({
5 | width: 500,
6 | height: 500,
7 | });
8 |
9 | document.body.append(game.view);
10 |
11 | const renderer = createRenderer({
12 | createElement(type) {
13 | if (type === "rect") {
14 | const rect = new PIXI.Graphics();
15 | rect.beginFill(0xff0000);
16 | rect.drawRect(0, 0, 100, 100);
17 | rect.endFill();
18 |
19 | return rect;
20 | }
21 | },
22 | patchProp(el, key, val) {
23 | el[key] = val;
24 | },
25 | insert(el, parent) {
26 | parent.addChild(el);
27 | },
28 | });
29 |
30 | renderer.createApp(App).mount(game.stage);
31 | // 相当于 createApp(App).mount("container")
--------------------------------------------------------------------------------
/example/helloWorld/App.js:
--------------------------------------------------------------------------------
1 | import { h,ref } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | window.self = null;
4 | export const App = {
5 | render() {
6 | window.self = this;
7 | return h(
8 | "div",
9 | {
10 | id: "root",
11 | class: ["red hard"],
12 | onClick: this.click1,
13 | },
14 | // children 是string
15 | "hi " + this.msg
16 | // childred 是 array
17 | // [h("p",{class: "red"}, "hi p"), h("h3",{class: "green"}, "hi h3")]
18 | // props
19 | // [h("div", {}, "hi " + this.msg), h(Foo, { count: 1 })]
20 | );
21 | },
22 | setup() {
23 | const msg = ref('1')
24 | const click1 = () => {
25 | console.log("6666");
26 | msg.value = msg.value+1
27 | }
28 |
29 |
30 | return {
31 | click1,
32 | msg,
33 | };
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/example/helloWorld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 | export const Foo = {
3 | setup(props) {
4 | // 1 props.count
5 | console.log("props", props)
6 |
7 | // 2 this.count 访问
8 |
9 | // shallow readdonly
10 | // props.count++
11 | },
12 | render() {
13 | return h('p',{}, "foo "+ this.count)
14 | }
15 | }
--------------------------------------------------------------------------------
/example/helloWorld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/helloWorld/main.js:
--------------------------------------------------------------------------------
1 | import {createApp} from "../../lib/guide-mini-vue.esm.js"
2 | import {App} from "./App.js"
3 |
4 |
5 | const rootContainer = document.querySelector("#app")
6 | createApp(App).mount(rootContainer)
--------------------------------------------------------------------------------
/example/nextTicker/App.js:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | h,
4 | ref,
5 | getCurrentInstance,
6 | nextTick,
7 | } from "../../lib/guide-mini-vue.esm.js";
8 |
9 | export default {
10 |
11 | name: "App",
12 | setup() {
13 | const count = ref(1);
14 | const instance = getCurrentInstance();
15 |
16 | function onClick() {
17 | for (let i = 0; i < 100; i++) {
18 | console.log("update");
19 | count.value = i;
20 | }
21 |
22 | // debugger;
23 | console.log(instance);
24 | nextTick(() => {
25 | console.log(instance);
26 | });
27 |
28 | // await nextTick()
29 | // console.log(instance)
30 | }
31 |
32 | return {
33 | onClick,
34 | count,
35 | };
36 | },
37 | render() {
38 | const button = h("button", { onClick: this.onClick }, "update");
39 | const p = h("p", {}, "count:" + this.count);
40 |
41 | return h("div", {}, [button, p]);
42 | },
43 | };
--------------------------------------------------------------------------------
/example/nextTicker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/nextTicker/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/patchChildren/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | import ArrayToText from "./ArrayToText.js";
4 | import TextToText from "./TextToText.js";
5 | import TextToArray from "./TextToArray.js";
6 | import ArrayToArray from "./ArrayToArray.js";
7 |
8 | export default {
9 | name: "App",
10 | setup() {},
11 |
12 | render() {
13 | return h("div", { tId: 1 }, [
14 | h("p", {}, "主页"),
15 | // 老的是 array 新的是 text
16 | // h(ArrayToText),
17 | // 老的是 text 新的是 text
18 | // h(TextToText),
19 | // 老的是 text 新的是 array
20 | // h(TextToArray)
21 | // 老的是 array 新的是 array
22 | h(ArrayToArray),
23 | ]);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToArray.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 array
3 |
4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
5 |
6 | // 1. 左侧的对比
7 | // (a b) c
8 | // (a b) d e
9 | // const prevChildren = [
10 | // h("p", { key: "A" }, "A"),
11 | // h("p", { key: "C" }, "C"),
12 | // ];
13 | // const nextChildren = [
14 | // h("p", { key: "A" }, "A"),
15 | // h("p", { key: "D" }, "D"),
16 | // h("p", { key: "E" }, "E"),
17 | // ];
18 |
19 | // 2. 右侧的对比
20 | // a (b c)
21 | // d e (b c)
22 | // const prevChildren = [
23 | // h("p", { key: "A" }, "A"),
24 | // h("p", { key: "B" }, "B"),
25 | // h("p", { key: "C" }, "C"),
26 | // ];
27 | // const nextChildren = [
28 | // h("p", { key: "D" }, "D"),
29 | // h("p", { key: "E" }, "E"),
30 | // h("p", { key: "B" }, "B"),
31 | // h("p", { key: "C" }, "C"),
32 | // ];
33 |
34 | // 3. 新的比老的长
35 | // 创建新的
36 | // 左侧
37 | // (a b)
38 | // (a b) c
39 | // i = 2, e1 = 1, e2 = 2
40 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
41 | // const nextChildren = [
42 | // h("p", { key: "A" }, "A"),
43 | // h("p", { key: "B" }, "B"),
44 | // h("p", { key: "C" }, "C"),
45 | // h("p", { key: "D" }, "D"),
46 | // ];
47 |
48 | // 右侧
49 | // (a b)
50 | // c (a b)
51 | // i = 0, e1 = -1, e2 = 0
52 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
53 | // const nextChildren = [
54 | // h("p", { key: "C" }, "Ca"),
55 | // h("p", { key: "C" }, "Ca"),
56 | // h("p", { key: "A" }, "A"),
57 | // h("p", { key: "B" }, "B"),
58 | // ];
59 |
60 | // 4. 老的比新的长
61 | // 删除老的
62 | // 左侧
63 | // (a b) c
64 | // (a b)
65 | // i = 2, e1 = 2, e2 = 1
66 | // const prevChildren = [
67 | // h("p", { key: "A" }, "A"),
68 | // h("p", { key: "B" }, "B"),
69 | // h("p", { key: "C" }, "C"),
70 | // ];
71 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
72 |
73 | // 右侧
74 | // a (b c)
75 | // (b c)
76 | // i = 0, e1 = 0, e2 = -1
77 |
78 | // const prevChildren = [
79 | // h("p", { key: "A" }, "A"),
80 | // h("p", { key: "B" }, "B"),
81 | // h("p", { key: "C" }, "C"),
82 | // ];
83 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
84 |
85 | // 5. 对比中间的部分
86 | // 删除老的 (在老的里面存在,新的里面不存在)
87 | // 5.1
88 | // a,b,(c,d),f,g
89 | // a,b,(e,c),f,g
90 | // D 节点在新的里面是没有的 - 需要删除掉
91 | // C 节点 props 也发生了变化
92 |
93 | // const prevChildren = [
94 | // h("p", { key: "A" }, "A"),
95 | // h("p", { key: "B" }, "B"),
96 | // h("p", { key: "C", id: "c-prev" }, "C"),
97 | // h("p", { key: "D" }, "D"),
98 | // h("p", { key: "F" }, "F"),
99 | // h("p", { key: "G" }, "G"),
100 | // ];
101 |
102 | // const nextChildren = [
103 | // h("p", { key: "A" }, "A"),
104 | // h("p", { key: "B" }, "B"),
105 | // h("p", { key: "E" }, "E"),
106 | // h("p", { key: "C", id:"c-next" }, "C"),
107 | // h("p", { key: "F" }, "F"),
108 | // h("p", { key: "G" }, "G"),
109 | // ];
110 |
111 | // 5.1.1
112 | // a,b,(c,e,d),f,g
113 | // a,b,(e,c),f,g
114 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
115 | // const prevChildren = [
116 | // h("p", { key: "A" }, "A"),
117 | // h("p", { key: "B" }, "B"),
118 | // h("p", { key: "C", id: "c-prev" }, "C"),
119 | // h("p", { key: "E" }, "E"),
120 | // h("p", { key: "D" }, "D"),
121 | // h("p", { key: "F" }, "F"),
122 | // h("p", { key: "G" }, "G"),
123 | // ];
124 |
125 | // const nextChildren = [
126 | // h("p", { key: "A" }, "A"),
127 | // h("p", { key: "B" }, "B"),
128 | // h("p", { key: "E" }, "E"),
129 | // h("p", { key: "C", id:"c-next" }, "C"),
130 | // h("p", { key: "F" }, "F"),
131 | // h("p", { key: "G" }, "G"),
132 | // ];
133 |
134 |
135 |
136 |
137 | // 2 移动 (节点存在于新的和老的里面,但是位置变了)
138 | // const prevChildren = [
139 | // h("p", { key: "A" }, "A"),
140 | // h("p", { key: "B" }, "B"),
141 |
142 | // h("p", { key: "C" }, "C"),
143 | // h("p", { key: "D" }, "D"),
144 | // h("p", { key: "E" }, "E"),
145 |
146 | // h("p", { key: "F" }, "F"),
147 | // h("p", { key: "G" }, "G"),
148 | // ];
149 |
150 | // const nextChildren = [
151 | // h("p", { key: "A" }, "A"),
152 | // h("p", { key: "B" }, "B"),
153 |
154 | // h("p", { key: "E" }, "E"),
155 | // h("p", { key: "C" }, "C"),
156 | // h("p", { key: "D" }, "D"),
157 |
158 | // h("p", { key: "F" }, "F"),
159 | // h("p", { key: "G" }, "G"),
160 | // ];
161 |
162 | // 综合例子
163 | // a,b,(c,d,e,z),f,g
164 | // a,b,(d,c,y,e),f,g
165 |
166 | const prevChildren = [
167 | h("p", { key: "A" }, "A"),
168 | h("p", { key: "B" }, "B"),
169 | h("p", { key: "C" }, "C"),
170 | h("p", { key: "D" }, "D"),
171 | h("p", { key: "E" }, "E"),
172 | h("p", { key: "Z" }, "Z"),
173 | h("p", { key: "F" }, "F"),
174 | h("p", { key: "G" }, "G"),
175 | ];
176 |
177 | const nextChildren = [
178 | h("p", { key: "A" }, "A"),
179 | h("p", { key: "B" }, "B"),
180 | h("p", { key: "D" }, "D"),
181 | h("p", { key: "C" }, "C"),
182 | h("p", { key: "Y" }, "Y"),
183 | h("p", { key: "E" }, "E"),
184 | h("p", { key: "F" }, "F"),
185 | h("p", { key: "G" }, "G"),
186 | ];
187 |
188 | export default {
189 | name: "ArrayToArray",
190 | setup() {
191 | const isChange = ref(false);
192 | window.isChange = isChange;
193 |
194 | return {
195 | isChange,
196 | };
197 | },
198 | render() {
199 | const self = this;
200 | return self.isChange === true
201 | ? h("div", {}, nextChildren)
202 | : h("div", {}, prevChildren);
203 | },
204 | };
205 |
--------------------------------------------------------------------------------
/example/patchChildren/ArrayToText.js:
--------------------------------------------------------------------------------
1 | // 老的是 array
2 | // 新的是 text
3 |
4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
5 | const nextChildren = "newChildren";
6 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "ArrayToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToArray.js:
--------------------------------------------------------------------------------
1 | // 新的是 array
2 | // 老的是 text
3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")];
7 |
8 | export default {
9 | name: "TextToArray",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/TextToText.js:
--------------------------------------------------------------------------------
1 | // 新的是 text
2 | // 老的是 text
3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js";
4 |
5 | const prevChildren = "oldChild";
6 | const nextChildren = "newChild";
7 |
8 | export default {
9 | name: "TextToText",
10 | setup() {
11 | const isChange = ref(false);
12 | window.isChange = isChange;
13 |
14 | return {
15 | isChange,
16 | };
17 | },
18 | render() {
19 | const self = this;
20 |
21 | return self.isChange === true
22 | ? h("div", {}, nextChildren)
23 | : h("div", {}, prevChildren);
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/example/patchChildren/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/patchChildren/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import App from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#root");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/example/update/App.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const App = {
4 | name: "App",
5 |
6 | setup() {
7 | const count = ref(0);
8 |
9 | const onClick = () => {
10 | count.value++;
11 | };
12 |
13 | const props = ref({
14 | foo: "foo",
15 | bar: "bar",
16 | });
17 | const onChangePropsDemo1 = () => {
18 | props.value.foo = "new-foo";
19 | };
20 |
21 | const onChangePropsDemo2 = () => {
22 | props.value.foo = undefined;
23 | };
24 |
25 | const onChangePropsDemo3 = () => {
26 | props.value = {
27 | foo: "foo",
28 | };
29 | };
30 |
31 | return {
32 | count,
33 | onClick,
34 | onChangePropsDemo1,
35 | onChangePropsDemo2,
36 | onChangePropsDemo3,
37 | props,
38 | };
39 | },
40 | render() {
41 | return h(
42 | "div",
43 | {
44 | id: "root",
45 | ...this.props,
46 | },
47 | [
48 | h("div", {}, "count:" + this.count),
49 | h(
50 | "button",
51 | {
52 | onClick: this.onClick,
53 | },
54 | "click"
55 | ),
56 | h(
57 | "button",
58 | {
59 | onClick: this.onChangePropsDemo1,
60 | },
61 | "changeProps - 值改变了 - 修改"
62 | ),
63 |
64 | h(
65 | "button",
66 | {
67 | onClick: this.onChangePropsDemo2,
68 | },
69 | "changeProps - 值变成了 undefined - 删除"
70 | ),
71 |
72 | h(
73 | "button",
74 | {
75 | onClick: this.onChangePropsDemo3,
76 | },
77 | "changeProps - key 在新的里面没有了 - 删除"
78 | ),
79 | ]
80 | );
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/example/update/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/update/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | const rootContainer = document.querySelector("#app");
5 | createApp(App).mount(rootContainer);
6 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | const Fragment = Symbol("Fragment");
2 | const Text = Symbol("Text");
3 | function createVNode(type, props, children) {
4 | // console.log(props)
5 | // 相同的节点 type key 相同
6 | const vnode = {
7 | type,
8 | key: props && props.key,
9 | props,
10 | // 组件实例 instance
11 | component: null,
12 | children,
13 | shapeFlag: getShapeFlag(type),
14 | el: null,
15 | };
16 | // 为处理children准备,给vnode再次添加一个flag
17 | // 这里的逻辑是这样的
18 | /**
19 | * a,b,c,d 为二进制数
20 | * 如果 c = a | b,那么 c&b 和 c&a 后转为十进制为非0, c&d 后转为10进制为0
21 | *
22 | */
23 | if (typeof children === "string") {
24 | // 0001 | 0100 -> 0101
25 | // 0010 | 0100 -> 0110
26 | vnode.shapeFlag = vnode.shapeFlag | 4 /* TEXT_CHILDREN */;
27 | }
28 | else if (Array.isArray(children)) {
29 | // 0001 | 1000 -> 1001
30 | // 0010 | 1000 -> 1010
31 | vnode.shapeFlag = vnode.shapeFlag | 8 /* ARRAY_CHILDREN */;
32 | }
33 | // slots children
34 | // 组件 + children object
35 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
36 | if (typeof children === "object") {
37 | vnode.shapeFlag |= 16 /* SLOTS_CHILDREN */;
38 | }
39 | }
40 | return vnode;
41 | }
42 | function createTextVNode(text) {
43 | return createVNode(Text, {}, text);
44 | }
45 | function getShapeFlag(type) {
46 | // vnode 是element元素 还是 组件 0001 0010
47 | return typeof type === "string"
48 | ? 1 /* ELEMENT */
49 | : 2 /* STATEFUL_COMPONENT */;
50 | }
51 |
52 | function h(type, props, children) {
53 | return createVNode(type, props, children);
54 | }
55 |
56 | function renderSlots(slots, name, props) {
57 | const slot = slots[name];
58 | if (slot) {
59 | if (typeof slot === "function") {
60 | return createVNode(Fragment, {}, slot(props));
61 | }
62 | }
63 | }
64 |
65 | function toDisplayString(value) {
66 | return String(value);
67 | }
68 |
69 | const extend = Object.assign;
70 | const isObject = (value) => {
71 | return value !== null && typeof value === "object";
72 | };
73 | const hasChanged = (v1, v2) => {
74 | return !Object.is(v1, v2);
75 | };
76 | const camelize = (str) => {
77 | return str.replace(/-(\w)/g, (_, c) => {
78 | return c ? c.toUpperCase() : "";
79 | });
80 | };
81 | const isString = (value) => typeof value === "string";
82 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
83 | const capitalize = (str) => {
84 | return str.charAt(0).toUpperCase() + str.slice(1);
85 | };
86 | const toHandlerKey = (str) => {
87 | return str ? "on" + capitalize(str) : "";
88 | };
89 |
90 | class ReactiveEffect {
91 | // pbulic 是为了给外部获取到
92 | constructor(fn, scheduler) {
93 | this.scheduler = scheduler;
94 | this.deps = [];
95 | // active 是处理重复调用stop的
96 | this.active = true;
97 | this._fn = fn;
98 | }
99 | run() {
100 | if (!this.active) {
101 | // 不应该收集依赖
102 | // 如果调用了stop,active 为 false
103 | // 只调用第一次的 _fn, 不进行下面的依赖赋值,也就是不进行依赖收集的 track 操作
104 | return this._fn();
105 | }
106 | // this 就是依赖的,依赖的run 方法就是执行fn
107 | // 应该收集依赖逻辑
108 | activeEffect = this;
109 | shouldTract = true;
110 | const r = this._fn();
111 | shouldTract = false;
112 | return r;
113 | }
114 | stop() {
115 | // 1个 dep 对应多个 effect,同一个effect可能存在多个dep里面
116 | // 现在要清除所有 dep 里面的 目标effect,也就是先遍历depsMap得到dep,在delete每一个dep里面的effect
117 | // 但是depsMap 与 effect不存在关联关系,也就是说当前的effect 不能关系到 所有的depsMap
118 | // 这样处理,
119 | /**
120 | * 1. dep 与 effect 的关系的 dep.add(effect)
121 | * 2. 我们给每一个effect 添加一个deps 的数组空间,用来存储谁 add 当前端的effect 了
122 | * 3. 那么,我们就能从effect 本身关联到与他有关的所有dep了,也就是 deps 数组
123 | * 4. 返回来,只要遍历当前的的efect的deps属性(deps这里面的每一个dep都存在effect),dep是Set,deps是数组
124 | * 5. effect.deps.forEach(dep => dep.delete(effect))
125 | */
126 | if (this.active) {
127 | if (this.onStop) {
128 | this.onStop();
129 | }
130 | cleanUpEffect(this);
131 | this.active = false;
132 | }
133 | }
134 | }
135 | function cleanUpEffect(effect) {
136 | effect.deps.forEach((dep) => {
137 | dep.delete(effect);
138 | });
139 | effect.deps.length = 0;
140 | }
141 | let targetMap = new Map();
142 | let activeEffect;
143 | let shouldTract;
144 | function track(target, key) {
145 | if (!isTracking())
146 | return;
147 | // target key dep
148 | // 对象-- key -- 依赖
149 | let depsMap = targetMap.get(target);
150 | if (!depsMap) {
151 | depsMap = new Map();
152 | targetMap.set(target, depsMap);
153 | }
154 | let dep = depsMap.get(key);
155 | if (!dep) {
156 | dep = new Set();
157 | depsMap.set(key, dep);
158 | }
159 | // 这不光光是抽离一个函数那么简单,为ref做准备
160 | trackEffects(dep);
161 | // if(dep.has(activeEffect)) return
162 | // dep.add(activeEffect);
163 | // activeEffect.deps.push(dep);
164 | }
165 | function trackEffects(dep) {
166 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
167 | if (dep.has(activeEffect))
168 | return;
169 | dep.add(activeEffect);
170 | activeEffect.deps.push(dep);
171 | }
172 | function isTracking() {
173 | return shouldTract && activeEffect !== undefined;
174 | }
175 | function effect(fn, options = {}) {
176 | const _effect = new ReactiveEffect(fn, options.scheduler);
177 | _effect.onStop = options.onStop;
178 | extend(_effect, options);
179 | _effect.run();
180 | const runner = _effect.run.bind(_effect);
181 | runner.effect = _effect;
182 | return runner;
183 | }
184 | function trigger(target, key) {
185 | let depsMap = targetMap.get(target);
186 | let dep = depsMap.get(key);
187 | triggerEffect(dep);
188 | }
189 | function triggerEffect(dep) {
190 | for (const effect of dep) {
191 | if (effect.scheduler) {
192 | effect.scheduler();
193 | }
194 | else {
195 | effect.run();
196 | }
197 | }
198 | }
199 |
200 | const get = createGetter();
201 | const set = createSetter();
202 | const readonlyGet = createGetter(true);
203 | const shallowReadonlyGet = createGetter(true, true);
204 | // shallow 浅层次
205 | function createGetter(isReadOnly = false, shallow = false) {
206 | return function get(target, key) {
207 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
208 | return !isReadOnly;
209 | }
210 | else if (key === "__v_isREADONLY" /* IS_READONLY */) {
211 | return isReadOnly;
212 | }
213 | let res = Reflect.get(target, key);
214 | if (shallow) {
215 | return res;
216 | }
217 | if (isObject(res)) {
218 | return isReadOnly ? readonly(res) : reactive(res);
219 | }
220 | // TODO 收集依赖
221 | if (!isReadOnly) {
222 | track(target, key);
223 | }
224 | return res;
225 | };
226 | }
227 | function createSetter() {
228 | return function set(target, key, value) {
229 | let res = Reflect.set(target, key, value);
230 | trigger(target, key);
231 | return res;
232 | };
233 | }
234 | const mutableHandles = {
235 | get,
236 | set,
237 | };
238 | const readonlyHandles = {
239 | get: readonlyGet,
240 | set(target, key, value) {
241 | console.warn(`${key} 不能set,readonly!`);
242 | return true;
243 | },
244 | };
245 | const shallowReadonlyHandles = extend({}, readonlyHandles, {
246 | get: shallowReadonlyGet,
247 | });
248 |
249 | // raw 生的
250 | function reactive(raw) {
251 | return createReactiveObject(raw, mutableHandles);
252 | }
253 | function readonly(raw) {
254 | return createReactiveObject(raw, readonlyHandles);
255 | }
256 | function createReactiveObject(raw, baseHandlers) {
257 | return new Proxy(raw, baseHandlers);
258 | }
259 | function shallowReadonly(raw) {
260 | return createReactiveObject(raw, shallowReadonlyHandles);
261 | }
262 |
263 | class RefImpl {
264 | constructor(value) {
265 | this.__v_isRef = true;
266 | // 存一下原始值,当value 为reactive时候使用
267 | this._rawValue = value;
268 | this._value = convert(value);
269 | this.dep = new Set();
270 | }
271 | get value() {
272 | trackRefValue(this);
273 | return this._value;
274 | }
275 | set value(newValue) {
276 | // 如果value 是个reactive类型,那么需要用他的原始值作比较
277 | if (hasChanged(newValue, this._rawValue)) {
278 | this._rawValue = newValue;
279 | this._value = convert(newValue);
280 | this._rawValue = newValue;
281 | triggerEffect(this.dep);
282 | }
283 | }
284 | }
285 | function convert(value) {
286 | return isObject(value) ? reactive(value) : value;
287 | }
288 | function ref(value) {
289 | return new RefImpl(value);
290 | }
291 | function trackRefValue(ref) {
292 | if (isTracking()) {
293 | trackEffects(ref.dep);
294 | }
295 | }
296 | function isRef(value) {
297 | return !!value.__v_isRef;
298 | }
299 | function unRef(value) {
300 | return !!value.__v_isRef ? value.value : value;
301 | }
302 | function proxyRefs(objectWithRefs) {
303 | return new Proxy(objectWithRefs, {
304 | get(target, key) {
305 | return unRef(Reflect.get(target, key));
306 | },
307 | set(target, key, value) {
308 | if (isRef(target[key]) && !isRef(value)) {
309 | return target[key].value = value;
310 | }
311 | else {
312 | return Reflect.set(target, key, value);
313 | }
314 | }
315 | });
316 | }
317 |
318 | function emit(instance, event, ...arg) {
319 | const { props } = instance;
320 | // add -> Add
321 | // add-add -> addAdd
322 | const handlerName = toHandlerKey(camelize(event));
323 | // console.log(handlerName)
324 | const handler = props[handlerName];
325 | handler && handler(...arg);
326 | }
327 |
328 | function initProps(instance, rawProps) {
329 | instance.props = rawProps || {};
330 | }
331 |
332 | const publicPropertiesMap = {
333 | $el: (i) => i.vnode.el,
334 | $slots: (i) => i.slots,
335 | $props: (i) => i.props,
336 | };
337 | const PublicInstanceProxyHandlers = {
338 | get({ _: instance }, key) {
339 | // console.log(instance)
340 | const { setupState, props } = instance;
341 | if (hasOwn(setupState, key)) {
342 | return setupState[key];
343 | }
344 | else if (hasOwn(props, key)) {
345 | return props[key];
346 | }
347 | // $el
348 | const publicGetter = publicPropertiesMap[key];
349 | if (publicGetter) {
350 | return publicGetter(instance);
351 | }
352 | },
353 | };
354 |
355 | function initSlots(instance, children) {
356 | const { vnode } = instance;
357 | if (vnode.shapeFlag & 16 /* SLOTS_CHILDREN */) {
358 | normalizeObjectSlots(children, instance.slots);
359 | }
360 | }
361 | function normalizeObjectSlots(children, slots) {
362 | for (const key in children) {
363 | const value = children[key];
364 | slots[key] = props => normalizeSlotValue(value(props));
365 | }
366 | }
367 | function normalizeSlotValue(value) {
368 | return Array.isArray(value) ? value : [value];
369 | }
370 |
371 | function createComponentInstance(vnode, parent) {
372 | // instance component
373 | const instance = {
374 | vnode,
375 | // 下次要更新的虚拟节点
376 | next: null,
377 | type: vnode.type,
378 | setupState: {},
379 | isMounted: false,
380 | // subTree:'',
381 | emit: () => { },
382 | slots: {},
383 | provides: parent ? parent.provides : {},
384 | parent,
385 | props: {},
386 | };
387 | instance.emit = emit.bind(null, instance);
388 | return instance;
389 | }
390 | function setupComponent(instance) {
391 | // 初始化
392 | // props
393 | initProps(instance, instance.vnode.props);
394 | initSlots(instance, instance.vnode.children);
395 | // 创建有状态的组件
396 | setupStatefulComponent(instance);
397 | }
398 | function setupStatefulComponent(instance) {
399 | // 调用setup 函数,拿到setup函数的返回值
400 | const Component = instance.vnode.type;
401 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
402 | const { setup } = Component;
403 | if (setup) {
404 | setCurrentInstance(instance);
405 | const setupResult = setup(shallowReadonly(instance.props), {
406 | emit: instance.emit,
407 | });
408 | setCurrentInstance(null);
409 | handleSetupResult(instance, setupResult);
410 | }
411 | }
412 | function handleSetupResult(instance, setupResult) {
413 | // 返回值是function,那就是render函数
414 | // 返回值是Object,那需要把这个对象挂到组件上下文
415 | if (typeof setupResult === "object") {
416 | instance.setupState = proxyRefs(setupResult);
417 | }
418 | // 保证组件render有值
419 | // 组件 -> const App = {
420 | // render() {
421 | // return h("div", this.msg)
422 | // },
423 | // setup() {
424 | // return {
425 | // msg: "hello vue"
426 | // }
427 | // }
428 | // }
429 | finishComponentSetup(instance);
430 | }
431 | function finishComponentSetup(instance) {
432 | const Component = instance.type;
433 | if (compiler && !Component.render) {
434 | if (Component.template) {
435 | Component.render = compiler(Component.template);
436 | }
437 | }
438 | instance.render = Component.render;
439 | // instance -> {
440 | // render:
441 | // setupState
442 | // vnode: {
443 | // type: App
444 | // }
445 | // }
446 | }
447 | let currentInstance = null;
448 | function getCurrentInstance() {
449 | return currentInstance;
450 | }
451 | function setCurrentInstance(instance) {
452 | currentInstance = instance;
453 | }
454 | let compiler;
455 | function registerRuntimeCompiler(_compiler) {
456 | compiler = _compiler;
457 | }
458 |
459 | function provide(key, value) {
460 | const currentInstance = getCurrentInstance();
461 | if (currentInstance) {
462 | let { provides } = currentInstance;
463 | const parentProvides = currentInstance.parent.provides;
464 | // 初始化
465 | if (provides === parentProvides) {
466 | provides = currentInstance.provides = Object.create(parentProvides);
467 | }
468 | provides[key] = value;
469 | }
470 | }
471 | function inject(key, defaultValue) {
472 | const currentInstance = getCurrentInstance();
473 | if (currentInstance) {
474 | const parentProvides = currentInstance.parent.provides;
475 | if (key in parentProvides) {
476 | return parentProvides[key];
477 | }
478 | else if (defaultValue) {
479 | if (typeof defaultValue === "function") {
480 | return defaultValue();
481 | }
482 | return defaultValue;
483 | }
484 | }
485 | }
486 |
487 | function shouldUpdateComponent(prevVNode, nextVNode) {
488 | const { props: prevProps } = prevVNode;
489 | const { props: nextProps } = nextVNode;
490 | for (const key in nextProps) {
491 | if (nextProps[key] !== prevProps[key]) {
492 | return true;
493 | }
494 | }
495 | return false;
496 | }
497 |
498 | // import { render } from "./renderer";
499 | function createAppAPI(render) {
500 | return function createApp(rootComponent) {
501 | return {
502 | mount(rootContainer) {
503 | // 先创建 vnode
504 | // component -> vnode
505 | // 所有逻辑操作 都会基于 vnode 做处理
506 | const vnode = createVNode(rootComponent);
507 | // 渲染虚拟节点
508 | render(vnode, rootContainer);
509 | },
510 | };
511 | };
512 | }
513 |
514 | const queue = [];
515 | let isFlushPending = false;
516 | function nextTick(fn) {
517 | return fn ? Promise.resolve().then(fn) : Promise.resolve();
518 | }
519 | function queueJobs(job) {
520 | if (!queue.includes(job)) {
521 | queue.push(job);
522 | }
523 | queueFlush();
524 | }
525 | function queueFlush() {
526 | if (isFlushPending) {
527 | return;
528 | }
529 | isFlushPending = true;
530 | nextTick(flushJobs);
531 | }
532 | function flushJobs() {
533 | Promise.resolve().then(() => {
534 | isFlushPending = false;
535 | let job;
536 | while ((job = queue.shift())) {
537 | job && job();
538 | }
539 | });
540 | }
541 |
542 | function createRenderer(options) {
543 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, setElementText: hostSetElementText, remove: hostRemove, } = options;
544 | // 查查初始化时候调用render了么?
545 | function render(vnode, container) {
546 | // patch
547 | patch(null, vnode, container, null, null);
548 | }
549 | /**
550 | * n1 老的
551 | * n2 新的
552 | */
553 | function patch(n1, n2, container, parentComponent, anchor) {
554 | // 当vnode.type的值时,组件是object,element是string,这样区分组件和元素
555 | const { type, shapeFlag } = n2;
556 | switch (type) {
557 | case Fragment:
558 | processFragment(n1, n2, container, parentComponent, anchor);
559 | break;
560 | case Text:
561 | processText(n1, n2, container);
562 | break;
563 | default:
564 | // if (typeof vnode.type === "string") {
565 | if (shapeFlag & 1 /* ELEMENT */) {
566 | // patch element
567 | processElement(n1, n2, container, parentComponent, anchor);
568 | // } else if (isObject(vnode.type)) {
569 | }
570 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
571 | // patch 组件
572 | console.log("组件逻辑");
573 | processComponent(n1, n2, container, parentComponent, anchor);
574 | }
575 | }
576 | }
577 | function processText(n1, n2, container) {
578 | const { children } = n2;
579 | const text = document.createTextNode(children);
580 | container.append(text);
581 | }
582 | function processFragment(n1, n2, container, parentComponent, anchor) {
583 | mountChildren(n2.children, container, parentComponent, anchor);
584 | }
585 | function processElement(n1, n2, container, parentComponent, anchor) {
586 | // 包含初始化和更新流程
587 | // init
588 | if (!n1) {
589 | mountElement(n2, container, parentComponent, anchor);
590 | }
591 | else {
592 | patchElement(n1, n2, container, parentComponent, anchor);
593 | }
594 | }
595 | function patchElement(n1, n2, container, parentComponent, anchor) {
596 | // 获取新,老 prosp
597 | const oldProps = n1.props || {};
598 | const newProps = n2.props || {};
599 | // 对比新老props
600 | const el = (n2.el = n1.el);
601 | patchProps(el, oldProps, newProps);
602 | // 对比children
603 | patchChildren(n1, n2, el, parentComponent, anchor);
604 | }
605 | function patchChildren(n1, n2, container, parentComponent, anchor) {
606 | // 子节点只有两种类型 文本节点 数组
607 | /*
608 | 1 新的是text,老的是array
609 | 2 删除老的array 添加 文本节点
610 | */
611 | /*
612 | 1 新的 老的都是 文本节点
613 | 2 对比是否相同,不相同的话 替换老的节点
614 | */
615 | /*
616 | 1 新的是数组,老的是文本
617 | 2 删除老的,挂载新的
618 | */
619 | const { shapeFlag } = n2;
620 | const c2 = n2.children;
621 | const c1 = n1.children;
622 | const prevshapeFlag = n1.shapeFlag;
623 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
624 | // if (prevshapeFlag & ShapeFlags.ARRAY_CHILDREN) {
625 | // // 1 把老的 children 删除
626 | // unmountChildren(n1.children);
627 | // // 2 添加 text
628 | // hostSetElementText(container, c2);
629 | // } else {
630 | // // 新老都是文本节点
631 | // if(c1 !== c2) {
632 | // hostSetElementText(container, c2);
633 | // }
634 | // }
635 | // 重构一下
636 | if (prevshapeFlag & 8 /* ARRAY_CHILDREN */) {
637 | unmountChildren(n1.children);
638 | }
639 | if (c1 !== c2) {
640 | hostSetElementText(container, c2);
641 | }
642 | }
643 | else {
644 | // 新的是array 老的是text
645 | if (prevshapeFlag & 4 /* TEXT_CHILDREN */) {
646 | hostSetElementText(container, "");
647 | mountChildren(c2, container, parentComponent, anchor);
648 | }
649 | else {
650 | // array diff array
651 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
652 | }
653 | }
654 | }
655 | /**
656 | * @description array diff array
657 | * @author Werewolf
658 | * @date 2021-12-20
659 | * @param {*} c1 老
660 | * @param {*} c2 新
661 | * @param {*} container 容器
662 | * @param {*} parentComponent 父组件
663 | * @param {*} parentAnthor 在这个元素之前插入。原由:插入有位置的要求
664 | */
665 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnthor) {
666 | // 初始指针 i
667 | let i = 0;
668 | let l2 = c2.length;
669 | let e1 = c1.length - 1;
670 | let e2 = l2 - 1;
671 | function isSameNodeType(n1, n2) {
672 | // 相同节点 type key 相同
673 | return n1.type === n2.type && n1.key === n2.key;
674 | }
675 | // 初始指针不能超过两个数组
676 | /**
677 | * 第一种情况
678 | * 左侧对吧
679 | * ab c
680 | * ab de
681 | */
682 | while (i <= e1 && i <= e2) {
683 | const n1 = c1[i];
684 | const n2 = c2[i];
685 | if (isSameNodeType(n1, n2)) {
686 | patch(n1, n2, container, parentComponent, parentAnthor);
687 | }
688 | else {
689 | break;
690 | }
691 | i++;
692 | }
693 | /**
694 | * 第二种情况
695 | * 右侧对比
696 | * a bc
697 | * de bc
698 | */
699 | while (i <= e1 && i <= e2) {
700 | const n1 = c1[e1];
701 | const n2 = c2[e2];
702 | if (isSameNodeType(n1, n2)) {
703 | patch(n1, n2, container, parentComponent, parentAnthor);
704 | }
705 | else {
706 | break;
707 | }
708 | e1--;
709 | e2--;
710 | }
711 | /**
712 | * 第三种情况
713 | * 新的比老的多,两种情况
714 | * ab ab
715 | * ab c c ab
716 | */
717 | if (i > e1) {
718 | if (i <= e2) {
719 | const nextPos = i + 1;
720 | const anchor = i + 1 > l2 ? null : c2[nextPos].el;
721 | while (i <= e2) {
722 | patch(null, c2[i], container, parentComponent, anchor);
723 | i++;
724 | }
725 | }
726 | }
727 | else if (i > e2) {
728 | /**
729 | * 第四种情况
730 | * 新的比老的少, 两种情况
731 | * ab c a bc
732 | * ab bc
733 | */
734 | while (i <= e1) {
735 | hostRemove(c1[i].el);
736 | i++;
737 | }
738 | }
739 | else {
740 | // 中间对比,经过以上逻辑已经找到了两个临界点
741 | /**
742 | * 第五种情况-1。删除老的d,修改c
743 | * 旧 ab cd fg
744 | * 新 ab ec fg
745 | * 1 旧的里面存在,新的不存在(d),那么需要删除 d。
746 | * 如果在ec里面遍历看是否存在d,那么时间复杂度是O(n),如果用 key 映射,那么时间复杂度是O(1)
747 | *
748 | */
749 | /**
750 | * 根据新的节点建立关于key的映射关系 keyToNewIndexMap
751 | * 在老的节点里根据key查找是否存在值,也就是是否存在 keyToNewIndexMap[oldChild.key]
752 | * 存在说明是相同节点,拿到索引,进行深度 patch,不存在直接在老的节点里删除
753 | * 注意:老的节点可能是用户没有写key属性,那只能 for 遍历了
754 | *
755 | */
756 | // s1 s2 新老节点中间不同的起始位置
757 | let s1 = i;
758 | let s2 = i;
759 | /**
760 | * 优化点:当新节点的个数小于老节点点个数,也就是新的已经patch完毕,但是老节点还存在,那么老节点剩下的无需在对比,直接删除
761 | * 老 ab cedm fg,新 ab ec fg,当新节点的ec对比完毕,老节点还剩dm,那么直接删除,无需对比
762 | *
763 | * toBePatched 新节点需要patch的个数
764 | * patched 已经处理的个数
765 | *
766 | */
767 | const toBePatched = e2 - s2 + 1;
768 | let patched = 0;
769 | // 映射关系
770 | const keyToNewIndexMap = new Map();
771 | // 节点位置移动的逻辑
772 | /**
773 | * 旧 ab cde fg
774 | * 新 ab ecd fg
775 | * newIndexToOldIndexMap的长度是3, 指的是新的 ecd 的映射
776 | * 我们要把 e 在老数组的的位置(4)映射到 newIndexToOldIndexMap 里面。newIndexToOldIndexMap[0] = 4
777 | *
778 | */
779 | // 建立 初始化映射表 定长数组性能相对要好
780 | const newIndexToOldIndexMap = new Array(toBePatched);
781 | /**
782 | * 优化逻辑
783 | * moved
784 | * maxNewIndexSoFar
785 | */
786 | let moved = false;
787 | let maxNewIndexSoFar = 0;
788 | for (let i = 0; i < toBePatched; i++) {
789 | newIndexToOldIndexMap[i] = 0;
790 | }
791 | // 建立 新的映射关系
792 | for (let i = s2; i <= e2; i++) {
793 | const nextChild = c2[i];
794 | keyToNewIndexMap.set(nextChild.key, i);
795 | }
796 | // 老的映射关系
797 | for (let i = s1; i <= e1; i++) {
798 | // 老节点 prevChild
799 | const prevChild = c1[i];
800 | if (patched >= toBePatched) {
801 | // 新的已经对比完,但是老的还没完事。直接删除
802 | hostRemove(prevChild.el);
803 | // 进入下一次循环
804 | continue;
805 | }
806 | let newIndex;
807 | /**
808 | * 如果 newIndex 存在,说明 prevChild 在新的里面存在。
809 | * 如果用户写了key,用key映射查找。如果没写key,用循环查找
810 | */
811 | if (prevChild.key !== null) {
812 | newIndex = keyToNewIndexMap.get(prevChild.key);
813 | }
814 | else {
815 | for (let j = s2; j <= e2; j++) {
816 | if (isSameNodeType(c2[j], prevChild)) {
817 | newIndex = j;
818 | break;
819 | }
820 | }
821 | }
822 | if (newIndex === undefined) {
823 | // 说明不存在prevChild,删掉老的 prevChild
824 | hostRemove(prevChild.el);
825 | }
826 | else {
827 | /**
828 | * 优化点
829 | * 思路:
830 | * 1 首先最长递归子序列是递增,那么我们想要 newIndex 也应该是递增,也就不用遍历递增序列了,优化了性能
831 | * 2 如果不是递增,那么肯定需要 移动并插入
832 | *
833 | */
834 | if (newIndex >= maxNewIndexSoFar) {
835 | maxNewIndexSoFar = newIndex;
836 | }
837 | else {
838 | moved = true;
839 | }
840 | /**
841 | * ab ecd fg
842 | * 从e开始映射 e 为 0,newIndex - s2 减去前面相同的 s2 部分
843 | * 由于 newIndexToOldIndexMap[i] 的初始化都为 0,0的意义代表 新的存在,老的不存在,需要创建新的
844 | * 这里的 e 为 0,有歧义,所以用 i+1 处理,最小 为 1,不会有歧义
845 | *
846 | * */
847 | /**
848 | * newIndexToOldIndexMap 逻辑是这样的
849 | * 老的 ab cde fg
850 | * 新的 ad ecd fg
851 | * 初始 newIndexToOldIndexMap -> [0, 0, 0]
852 | * 遍历老节点,老c存在新节点创建的 Map 中,即 老c 的索引是0,所以newIndexToOldIndexMap[1] = 1(0+1)
853 | * 同理,老d存在新节点创建的Map中,即 老d 的索引是 1,所以 newIndexToOldIndexMap[2] = 2(1+1)
854 | * 老e的索引是2,所以 newIndexToOldIndexMap[0] = 3(2+1)
855 | * newIndexToOldIndexMap -> [3, 1, 2]
856 | */
857 | // 图12
858 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
859 | // 存在,继续进行深度对比
860 | patch(prevChild, c2[newIndex], container, parentComponent, null);
861 | patched++;
862 | }
863 | }
864 | /**
865 | * 移动节点
866 | * 新老都存在,只需要移动节点
867 | * 找到一个固定的序列cd,减少对比插入次数
868 | * 算法:最长递增子序列
869 | * [4,2,3] => [1,2], [4,2,3,5]=>[1,2,4]
870 | * a[i]= 0; i--) {
906 | // 拿到一个倒序的索引
907 | const nextIndex = i + s2;
908 | // 新节点树c2对应的 节点
909 | const nextChild = c2[nextIndex];
910 | // 这个节点的下一个节点的el,如果需要移动,那么就插入到这个节点之前,这就是他为锚点
911 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
912 | if (newIndexToOldIndexMap[i] === 0) {
913 | // 创建逻辑
914 | patch(null, nextChild, container, parentComponent, anchor);
915 | }
916 | else if (moved) {
917 | /**
918 | * i 是 c2 的中间部分的索引
919 | * 如果倒序的索引 i 跟当前的 最长递归子序列的倒序索引 j 相同,那么说明是这个节点的位置不用移动
920 | * 如果不相同,那么需要插入这个节点
921 | * 需要找到这个节点,和锚点
922 | *
923 | * */
924 | if (j < 0 || i !== increasingNewSequence[j]) {
925 | hostInsert(nextChild.el, container, anchor);
926 | // 不在最长递归子序列
927 | console.log("移动位置");
928 | }
929 | else {
930 | j--;
931 | }
932 | }
933 | }
934 | }
935 | }
936 | /**
937 | * @description 删除children 节点
938 | * @author Werewolf
939 | * @date 2021-12-17
940 | * @param {*} children
941 | */
942 | function unmountChildren(children) {
943 | for (var i = 0; i < children.length; i++) {
944 | const el = children[i].el;
945 | hostRemove(el);
946 | }
947 | }
948 | /**
949 | * @description patch 属性
950 | * @author Werewolf
951 | * @date 2021-12-20
952 | * @param {*} el
953 | * @param {*} oldProps
954 | * @param {*} newProps
955 | */
956 | function patchProps(el, oldProps, newProps) {
957 | if (oldProps !== newProps) {
958 | // newProps 里面的 prop 不在 oldProps 里面,遍历新的
959 | for (const key in newProps) {
960 | // 对比props对象的属性
961 | const prveProp = oldProps[key];
962 | const nextprop = newProps[key];
963 | if (prveProp !== nextprop) {
964 | // 调用之前的 添加属性方法,需要一个 el
965 | // 多传一个参数,同时需要修改 hostPatchProp 方法
966 | // hostPatchProp(el, key, prveProp, nextprop)
967 | hostPatchProp(el, key, prveProp, nextprop);
968 | }
969 | }
970 | // oldProps 里的 prop 不在 newProps 里面,遍历旧的
971 | if (oldProps !== {}) {
972 | for (const key in oldProps) {
973 | if (!(key in newProps)) {
974 | hostPatchProp(el, key, oldProps[key], null);
975 | }
976 | }
977 | }
978 | }
979 | }
980 | function mountElement(vnode, container, parentComponent, anchor) {
981 | // canvas new Element
982 | // const el = (vnode.el = document.createElement(vnode.type));
983 | const el = (vnode.el = hostCreateElement(vnode.type));
984 | const { props, children, shapeFlag } = vnode;
985 | // string array
986 | // if (typeof children === "string") {
987 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
988 | el.textContent = children;
989 | }
990 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
991 | mountChildren(vnode.children, el, parentComponent, anchor);
992 | }
993 | for (const key in props) {
994 | const val = props[key];
995 | hostPatchProp(el, key, null, val);
996 | }
997 | // canvas el.x = 10
998 | // container.append(el);
999 | hostInsert(el, container, anchor);
1000 | // canvas addChild()
1001 | }
1002 | /**
1003 | * @description 挂载数组节点
1004 | * @author Werewolf
1005 | * @date 2021-12-17
1006 | * @param {*} children [vnode1,vnode2]
1007 | * @param {*} container
1008 | * @param {*} parentComponent
1009 | */
1010 | function mountChildren(children, container, parentComponent, anchor) {
1011 | children.forEach((v) => {
1012 | patch(null, v, container, parentComponent, anchor);
1013 | });
1014 | }
1015 | function processComponent(n1, n2, container, parentComponent, anchor) {
1016 | if (!n1) {
1017 | // 初始化
1018 | mountComponent(n2, container, parentComponent, anchor);
1019 | }
1020 | else {
1021 | // 更新组件 调用当前组件的render 函数,重新 vnode 重新 patch, 也就是走 setupRenderEffect 逻辑
1022 | updateComponent(n1, n2);
1023 | }
1024 | }
1025 | /**
1026 | * @description 组件更新
1027 | * @author Werewolf
1028 | * @date 2021-12-24
1029 | * @param {*} n1
1030 | * @param {*} n2
1031 | */
1032 | function updateComponent(n1, n2) {
1033 | // 利用effect runner 逻辑
1034 | /**
1035 | * 怎么找instance,现在只有n 虚拟节点
1036 | * 那么把实例挂载到虚拟节点
1037 | *
1038 | */
1039 | const instance = (n2.component = n1.component);
1040 | if (shouldUpdateComponent(n1, n2)) {
1041 | instance.next = n2;
1042 | instance.update();
1043 | }
1044 | else {
1045 | // 不需要更新也要重置虚拟节点 和 el
1046 | n2.el = n1.el;
1047 | n2.vnode = n2;
1048 | }
1049 | }
1050 | function mountComponent(initialVNode, container, parentComponent, anchor) {
1051 | // 根据虚拟节点创建组件实例
1052 | // 将组件实例 挂载到虚拟接节点
1053 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
1054 | // 初始化,收集信息,instance挂载相关属性,方法, 装箱
1055 | setupComponent(instance);
1056 | // 渲染组件,调用组件的render方法
1057 | // 组件 -> const App = {
1058 | // render() {
1059 | // return h("div", this.msg)
1060 | // },
1061 | // setup() {
1062 | // return {
1063 | // msg: "hello vue"
1064 | // }
1065 | // }
1066 | // }
1067 | // 一个组件不会真实渲染出来,渲染的是组件的render函数内部的element值,拆箱过程
1068 | // render 返回的subTree 给patch,如果是组件继续递归,如果是element 则渲染
1069 | setupRenderEffect(instance, initialVNode, container, anchor);
1070 | }
1071 | /**
1072 | * @description 调用render,也就是生成虚拟节点,进行patch。包括 初始化和更新流程
1073 | * @author Werewolf
1074 | * @date 2021-12-24
1075 | * @param {*} instance
1076 | * @param {*} initialVNode
1077 | * @param {*} container
1078 | * @param {*} anchor
1079 | */
1080 | function setupRenderEffect(instance, initialVNode, container, anchor) {
1081 | instance.update = effect(() => {
1082 | if (!instance.isMounted) {
1083 | console.log("init 初始化");
1084 | const { proxy } = instance;
1085 | const subTree = (instance.subTree = instance.render.call(proxy, proxy));
1086 | patch(null, subTree, container, instance, anchor);
1087 | initialVNode.el = subTree.el;
1088 | instance.isMounted = true;
1089 | }
1090 | else {
1091 | console.log("update 更新");
1092 | // next 新的虚拟节点
1093 | // vnode 老的虚拟节点
1094 | const { next, vnode } = instance;
1095 | // 更新el
1096 | if (next) {
1097 | next.el = vnode.el;
1098 | // 更新属性
1099 | updateComponentPreRender(instance, next);
1100 | }
1101 | const { proxy } = instance;
1102 | const subTree = instance.render.call(proxy, proxy);
1103 | const prevSubTree = instance.subTree;
1104 | instance.subTree = subTree;
1105 | patch(prevSubTree, subTree, container, instance, anchor);
1106 | }
1107 | }, {
1108 | scheduler() {
1109 | console.log("effect 的 scheduler 逻辑,数据更新,视图不更新");
1110 | queueJobs(instance.update);
1111 | },
1112 | });
1113 | }
1114 | return {
1115 | createApp: createAppAPI(render),
1116 | };
1117 | }
1118 | /**
1119 | * @description 更新属性
1120 | * @author Werewolf
1121 | * @date 2021-12-24
1122 | * @param {*} instance
1123 | * @param {*} nextVNode
1124 | */
1125 | function updateComponentPreRender(instance, nextVNode) {
1126 | // 更新实例的虚拟节点
1127 | instance.vnode = nextVNode;
1128 | instance.next = null;
1129 | // 更新props
1130 | instance.props = nextVNode.props;
1131 | }
1132 | /**
1133 | * @description 最长递增子序列
1134 | * @author Werewolf
1135 | * @date 2021-12-24
1136 | * @param {*} arr
1137 | * @return {*}
1138 | */
1139 | function getSequence(arr) {
1140 | const p = arr.slice();
1141 | const result = [0];
1142 | let i, j, u, v, c;
1143 | const len = arr.length;
1144 | for (i = 0; i < len; i++) {
1145 | const arrI = arr[i];
1146 | if (arrI !== 0) {
1147 | j = result[result.length - 1];
1148 | if (arr[j] < arrI) {
1149 | p[i] = j;
1150 | result.push(i);
1151 | continue;
1152 | }
1153 | u = 0;
1154 | v = result.length - 1;
1155 | while (u < v) {
1156 | c = (u + v) >> 1;
1157 | if (arr[result[c]] < arrI) {
1158 | u = c + 1;
1159 | }
1160 | else {
1161 | v = c;
1162 | }
1163 | }
1164 | if (arrI < arr[result[u]]) {
1165 | if (u > 0) {
1166 | p[i] = result[u - 1];
1167 | }
1168 | result[u] = i;
1169 | }
1170 | }
1171 | }
1172 | u = result.length;
1173 | v = result[u - 1];
1174 | while (u-- > 0) {
1175 | result[u] = v;
1176 | v = p[v];
1177 | }
1178 | return result;
1179 | }
1180 |
1181 | function createElement(type) {
1182 | // console.log("dom ------api")
1183 | return document.createElement(type);
1184 | }
1185 | function patchProp(el, key, prevVal, nextVal) {
1186 | const isOn = (key) => /^on[A-Z]/.test(key);
1187 | if (isOn(key)) {
1188 | const event = key.slice(2).toLowerCase();
1189 | el.addEventListener(event, nextVal);
1190 | }
1191 | else {
1192 | if (nextVal === undefined || nextVal === null) {
1193 | el.removeAttribute(key);
1194 | }
1195 | else {
1196 | el.setAttribute(key, nextVal);
1197 | }
1198 | }
1199 | }
1200 | /**
1201 | * @description 将子节点插入到指定位置anchor,没有指定位置默认插入到最后
1202 | * @author Werewolf
1203 | * @date 2021-12-20
1204 | * @param {*} child
1205 | * @param {*} parent
1206 | * @param {*} anchor 将要插在这个节点之前
1207 | */
1208 | function insert(child, parent, anchor) {
1209 | // console.log("dom ------api")
1210 | // 插入到最后
1211 | // parent.append(child) 等价于 parent.insertBefore(child, parent, null)
1212 | // console.log()
1213 | parent.insertBefore(child, anchor || null);
1214 | }
1215 | /**
1216 | * @description 删除子节点
1217 | * @author Werewolf
1218 | * @date 2021-12-17
1219 | * @param {*} child 子节点
1220 | */
1221 | function remove(child) {
1222 | const parent = child.parentNode;
1223 | if (parent) {
1224 | parent.removeChild(child);
1225 | }
1226 | }
1227 | /**
1228 | * @description 设置text 节点
1229 | * @author Werewolf
1230 | * @date 2021-12-17
1231 | * @param {*} el 父容器
1232 | * @param {*} text 子节点
1233 | */
1234 | function setElementText(el, text) {
1235 | el.textContent = text;
1236 | }
1237 | const renderer = createRenderer({
1238 | createElement,
1239 | patchProp,
1240 | setElementText,
1241 | remove,
1242 | insert,
1243 | });
1244 | // return {
1245 | // createApp: createAppAPI(render)
1246 | // }
1247 | function createApp(...args) {
1248 | return renderer.createApp(...args);
1249 | // 调用流程
1250 | // return createAppAPI(render)(...args);
1251 | // export function createAppAPI(render) {
1252 | // return function createApp(rootComponent) {
1253 | // return {
1254 | // mount(rootContainer) {
1255 | // // 先创建 vnode
1256 | // // component -> vnode
1257 | // // 所有逻辑操作 都会基于 vnode 做处理
1258 | // const vnode = createVNode(rootComponent);
1259 | // // 渲染虚拟节点
1260 | // render(vnode, rootContainer);
1261 | // },
1262 | // };
1263 | // }
1264 | // }
1265 | }
1266 |
1267 | var runtimeDom = /*#__PURE__*/Object.freeze({
1268 | __proto__: null,
1269 | createApp: createApp,
1270 | h: h,
1271 | renderSlots: renderSlots,
1272 | createTextVNode: createTextVNode,
1273 | createElementVNode: createVNode,
1274 | getCurrentInstance: getCurrentInstance,
1275 | registerRuntimeCompiler: registerRuntimeCompiler,
1276 | provide: provide,
1277 | inject: inject,
1278 | createRenderer: createRenderer,
1279 | nextTick: nextTick,
1280 | toDisplayString: toDisplayString,
1281 | ref: ref,
1282 | isRef: isRef,
1283 | unRef: unRef,
1284 | proxyRefs: proxyRefs
1285 | });
1286 |
1287 | const TO_DISPLAY_STRING = Symbol("toDisplayString");
1288 | const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
1289 | const helperMapName = {
1290 | [TO_DISPLAY_STRING]: "toDisplayString",
1291 | [CREATE_ELEMENT_VNODE]: "createElementVNode",
1292 | };
1293 |
1294 | function generate(ast) {
1295 | const context = createCodegenContext();
1296 | const { push } = context;
1297 | // 导入逻辑 const { toDisplayString: _toDisplayString } = Vue
1298 | genFunctionPreamble(ast, context);
1299 | const functionName = "render";
1300 | const args = ["_ctx", "_cache"];
1301 | const signature = args.join(", ");
1302 | push(`function ${functionName}(${signature}) {`);
1303 | push("return ");
1304 | genNode(ast.codegenNode, context);
1305 | push("}");
1306 | return {
1307 | code: context.code,
1308 | };
1309 | }
1310 | function genFunctionPreamble(ast, context) {
1311 | const { push } = context;
1312 | const VueBinging = "Vue";
1313 | if (ast.helpers.length) {
1314 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`;
1315 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`);
1316 | push("\n");
1317 | }
1318 | push("return ");
1319 | }
1320 | function genNode(node, context) {
1321 | switch (node.type) {
1322 | case 3 /* TEXT */:
1323 | // 处理文本 把内容返回
1324 | genText(node, context);
1325 | break;
1326 | case 0 /* INTERPOLATION */:
1327 | // 处理插值 _toDisplayString
1328 | // node - { type: 0, content: { type: 1, content: 'message' } }
1329 | genInterpolation(node, context);
1330 | break;
1331 | case 1 /* SIMPLE_EXPRESSION */:
1332 | genExpression(node, context);
1333 | break;
1334 | case 2 /* ELEMENT */:
1335 | genElement(node, context);
1336 | break;
1337 | case 5 /* COMPOUND_EXPRESSION */:
1338 | genCompoundExpression(node, context);
1339 | break;
1340 | }
1341 | }
1342 | function genCompoundExpression(node, context) {
1343 | const { push } = context;
1344 | const children = node.children;
1345 | for (let i = 0; i < children.length; i++) {
1346 | const child = children[i];
1347 | if (isString(child)) {
1348 | push(child);
1349 | }
1350 | else {
1351 | genNode(child, context);
1352 | }
1353 | }
1354 | }
1355 | function genElement(node, context) {
1356 | const { push, helper } = context;
1357 | const { tag, children, props } = node;
1358 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
1359 | genNodeList(genNullable([tag, props, children]), context);
1360 | // console.log(children);
1361 | // [
1362 | // { type: 3, content: 'hi, ' },
1363 | // { type: 0, content: { type: 1, content: 'message' } }
1364 | // ]
1365 | // for (let i = 0; i < children.length; i++) {
1366 | // const child = children[i];
1367 | // genNode(child, context);
1368 | // }
1369 | // genNode(children, context);
1370 | push(")");
1371 | }
1372 | function genNodeList(nodes, context) {
1373 | const { push } = context;
1374 | for (let i = 0; i < nodes.length; i++) {
1375 | const node = nodes[i];
1376 | if (isString(node)) {
1377 | push(node);
1378 | }
1379 | else {
1380 | genNode(node, context);
1381 | }
1382 | if (i < nodes.length - 1) {
1383 | push(", ");
1384 | }
1385 | }
1386 | }
1387 | function genNullable(args) {
1388 | return args.map((arg) => arg || "null");
1389 | }
1390 | function genText(node, context) {
1391 | const { push } = context;
1392 | push(`'${node.content}'`);
1393 | }
1394 | function createCodegenContext() {
1395 | const context = {
1396 | code: "",
1397 | push(source) {
1398 | context.code += source;
1399 | },
1400 | helper(key) {
1401 | return `_${helperMapName[key]}`;
1402 | },
1403 | };
1404 | return context;
1405 | }
1406 | function genInterpolation(node, context) {
1407 | const { push, helper } = context;
1408 | // push(`_toDisplayString(_ctx.message)`)
1409 | push(`${helper(TO_DISPLAY_STRING)}(`);
1410 | genNode(node.content, context);
1411 | push(`)`);
1412 | }
1413 | function genExpression(node, context) {
1414 | const { push } = context;
1415 | push(`${node.content}`);
1416 | }
1417 |
1418 | function baseParse(content) {
1419 | const context = createParserContext(content);
1420 | return createRoot(parseChildren(context, []));
1421 | }
1422 | function parseChildren(context, ancestors) {
1423 | const nodes = [];
1424 | while (!isEnd(context, ancestors)) {
1425 | let node;
1426 | let s = context.source;
1427 | if (s.startsWith("{{")) {
1428 | node = parseInterpolation(context);
1429 | }
1430 | else if (s[0] === "<") {
1431 | // element
1432 | if (/[a-z]/i.test(s[1])) {
1433 | node = parseElement(context, ancestors);
1434 | }
1435 | }
1436 | if (!node) {
1437 | node = parseText(context);
1438 | }
1439 | nodes.push(node);
1440 | }
1441 | return nodes;
1442 | }
1443 | function isEnd(context, ancestors) {
1444 | const s = context.source;
1445 | if (s.startsWith("")) {
1446 | for (let i = ancestors.length - 1; i >= 0; i--) {
1447 | const tag = ancestors[i].tag;
1448 | // if (s.slice(2, 2 + tag.length) === tag) {
1449 | if (startsWithEndTagOpen(s, tag)) {
1450 | return true;
1451 | }
1452 | }
1453 | }
1454 | return !s;
1455 | }
1456 | function parseText(context) {
1457 | let endIndex = context.source.length;
1458 | let endToken = ["<", "{{"];
1459 | for (let i = 0; i < endToken.length; i++) {
1460 | const index = context.source.indexOf(endToken[i]);
1461 | if (index !== -1 && endIndex > index) {
1462 | endIndex = index;
1463 | }
1464 | }
1465 | const content = parseTextData(context, endIndex);
1466 | return {
1467 | type: 3 /* TEXT */,
1468 | content: content,
1469 | };
1470 | }
1471 | function parseTextData(context, length) {
1472 | const content = context.source.slice(0, length);
1473 | advanceBy(context, length);
1474 | return content;
1475 | }
1476 | function parseInterpolation(context) {
1477 | // {{xxx}}
1478 | const openDelimiter = "{{";
1479 | const closeDelimiter = "}}";
1480 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length);
1481 | advanceBy(context, openDelimiter.length);
1482 | const rawContentLength = closeIndex - openDelimiter.length;
1483 | const rawContent = parseTextData(context, rawContentLength);
1484 | // context.source.slice(0, rawContentLength);
1485 | const content = rawContent.trim();
1486 | // delete }}
1487 | advanceBy(context, closeDelimiter.length);
1488 | return {
1489 | type: 0 /* INTERPOLATION */,
1490 | content: {
1491 | type: 1 /* SIMPLE_EXPRESSION */,
1492 | content: content,
1493 | },
1494 | };
1495 | }
1496 | function advanceBy(context, length) {
1497 | context.source = context.source.slice(length);
1498 | }
1499 | function createRoot(children) {
1500 | return {
1501 | children,
1502 | type: 4 /* ROOT */,
1503 | };
1504 | }
1505 | function createParserContext(context) {
1506 | return {
1507 | source: context,
1508 | };
1509 | }
1510 | function parseElement(context, ancestors) {
1511 | // 解析tag
1512 | const element = parseTag(context, 0 /* Start */);
1513 | ancestors.push(element);
1514 | element.children = parseChildren(context, ancestors);
1515 | ancestors.pop();
1516 | if (startsWithEndTagOpen(context.source, element.tag)) {
1517 | parseTag(context, 1 /* End */);
1518 | }
1519 | else {
1520 | throw new Error(`缺少结束标签:${element.tag}`);
1521 | }
1522 | // element 已经推进,所以直接地柜即可
1523 | return element;
1524 | }
1525 | function startsWithEndTagOpen(source, tag) {
1526 | return source.startsWith("") && source.slice(2, 2 + tag.length) === tag;
1527 | }
1528 | function parseTag(context, type) {
1529 | const match = /^<\/?([a-z]*)/i.exec(context.source);
1530 | const tag = match[1];
1531 | advanceBy(context, match[0].length);
1532 | advanceBy(context, 1);
1533 | if (type === 1 /* End */)
1534 | return;
1535 | return {
1536 | type: 2 /* ELEMENT */,
1537 | tag,
1538 | };
1539 | }
1540 |
1541 | function transform(root, options = {}) {
1542 | // 全局上下文
1543 | const context = createTransformContext(root, options);
1544 | // 遍历
1545 | traverseNode(root, context);
1546 | createRootCodegen(root);
1547 | root.helpers = [...context.helpers.keys()];
1548 | // 修改
1549 | }
1550 | function createRootCodegen(root) {
1551 | const child = root.children[0];
1552 | if (child.type === 2 /* ELEMENT */) {
1553 | root.codegenNode = child.codegenNode;
1554 | }
1555 | else {
1556 | root.codegenNode = root.children[0];
1557 | }
1558 | }
1559 | function traverseNode(node, context) {
1560 | const nodeTransformer = context.nodeTransformer;
1561 | const exitFns = [];
1562 | for (let i = 0; i < nodeTransformer.length; i++) {
1563 | let transform = nodeTransformer[i];
1564 | let onExit = transform(node, context);
1565 | if (onExit) {
1566 | exitFns.push(onExit);
1567 | }
1568 | }
1569 | switch (node.type) {
1570 | case 0 /* INTERPOLATION */:
1571 | // 如果是插值 需要 toDisplayString
1572 | context.helper(TO_DISPLAY_STRING);
1573 | break;
1574 | case 4 /* ROOT */:
1575 | case 2 /* ELEMENT */:
1576 | traverseChildren(node, context);
1577 | break;
1578 | }
1579 | let i = exitFns.length;
1580 | while (i--) {
1581 | exitFns[i]();
1582 | }
1583 | }
1584 | function traverseChildren(node, context) {
1585 | const children = node.children;
1586 | for (let i = 0; i < children.length; i++) {
1587 | const node = children[i];
1588 | traverseNode(node, context);
1589 | }
1590 | }
1591 | function createTransformContext(root, options) {
1592 | const context = {
1593 | root,
1594 | nodeTransformer: options.nodeTransforms || [],
1595 | helpers: new Map(),
1596 | helper(key) {
1597 | context.helpers.set(key, 1);
1598 | },
1599 | };
1600 | return context;
1601 | }
1602 |
1603 | function createVNodeCall(context, tag, props, children) {
1604 | context.helper(CREATE_ELEMENT_VNODE);
1605 | return {
1606 | type: 2 /* ELEMENT */,
1607 | tag,
1608 | props,
1609 | children,
1610 | };
1611 | }
1612 |
1613 | function transformElement(node, context) {
1614 | if (node.type === 2 /* ELEMENT */) {
1615 | // context.helper(CREATE_ELEMENT_VNODE)
1616 | return () => {
1617 | // tag
1618 | const vnodeTag = `"${node.tag}"`;
1619 | // props
1620 | let vnodeProps;
1621 | // children
1622 | const children = node.children;
1623 | let vnodeChildren = children[0];
1624 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren);
1625 | };
1626 | }
1627 | }
1628 |
1629 | function transformExpression(node) {
1630 | if (node.type === 0 /* INTERPOLATION */) {
1631 | node.content = processExpression(node.content);
1632 | }
1633 | }
1634 | function processExpression(node) {
1635 | node.content = `_ctx.${node.content}`;
1636 | return node;
1637 | }
1638 |
1639 | function isText(node) {
1640 | return (node.nodeType =
1641 | 3 /* TEXT */ /* INTERPOLATION */);
1642 | }
1643 |
1644 | function transformText(node, context) {
1645 | if (node.type === 2 /* ELEMENT */) {
1646 | return () => {
1647 | const { children } = node;
1648 | let currentContainer;
1649 | for (let i = 0; i < children.length; i++) {
1650 | const child = children[i];
1651 | if (isText(child)) {
1652 | for (let j = i + 1; j < children.length; j++) {
1653 | const next = children[j];
1654 | if (isText(next)) {
1655 | if (!currentContainer) {
1656 | currentContainer = children[i] = {
1657 | type: 5 /* COMPOUND_EXPRESSION */,
1658 | children: [child],
1659 | };
1660 | }
1661 | currentContainer.children.push(" + ");
1662 | currentContainer.children.push(next);
1663 | children.splice(j, 1);
1664 | j--;
1665 | }
1666 | else {
1667 | currentContainer = undefined;
1668 | break;
1669 | }
1670 | }
1671 | }
1672 | }
1673 | };
1674 | }
1675 | }
1676 |
1677 | function baseCompile(template) {
1678 | const ast = baseParse(template);
1679 | transform(ast, {
1680 | nodeTransforms: [transformExpression, transformElement, transformText],
1681 | });
1682 | return generate(ast);
1683 | }
1684 |
1685 | // mini-vue 的出口
1686 | function compileToFunction(template) {
1687 | const { code } = baseCompile(template);
1688 | const render = new Function("Vue", code)(runtimeDom);
1689 | return render;
1690 | }
1691 | registerRuntimeCompiler(compileToFunction);
1692 |
1693 | export { createApp, createVNode as createElementVNode, createRenderer, createTextVNode, getCurrentInstance, h, inject, isRef, nextTick, provide, proxyRefs, ref, registerRuntimeCompiler, renderSlots, toDisplayString, unRef };
1694 |
--------------------------------------------------------------------------------
/md/customRender.md:
--------------------------------------------------------------------------------
1 | ## 第一种情况,只基于 dom api 的 render
2 |
3 | 1. 用户调用 createApp
4 |
5 | ```ts
6 | createApp(App).mount(rootContainer);
7 | ```
8 |
9 | 2. createApp
10 |
11 | ```ts
12 | // 直接导出的 createApp
13 | export function createApp(rootComponent) {
14 | return {
15 | mount(rootContainer) {
16 | const vnode = createVNode(rootComponent);
17 | render(vnode, rootContainer);
18 | },
19 | };
20 | }
21 | ```
22 |
23 | 3. render
24 |
25 | ```ts
26 | export function render(vnode, container) {
27 | // dom api 创建元素
28 | const el = (vnode.el = document.createElement(vnode.type));
29 | // dom api 设置属性
30 | // ...
31 | // dom api 插入
32 | container.append(el);
33 | }
34 | ```
35 |
36 | ## 第二种情况,基于用户传入 api 的 dom 的 customRenderer
37 |
38 | 1. 用户调用 createApp
39 |
40 | ```ts
41 | createApp(App).mount(rootContainer);
42 | ```
43 |
44 | 2. createApp
45 |
46 | ```ts
47 | // runtime-dom 里面的导出的 createApp
48 | export function createApp(...args) {
49 | return renderer.createApp(...args);
50 | }
51 |
52 | const renderer: any = createRenderer({
53 | //下面的api 都是 dom的
54 | createElement,
55 | patchProp,
56 | insert,
57 | });
58 | ```
59 |
60 | 3. createRenderer
61 |
62 | ```ts
63 | export function createRenderer(options) {
64 | const { createElement, patchProp, insert } = options;
65 |
66 | function render(vnode, container) {
67 | // 同第一种情况的render相同了,相当只是提取了
68 |
69 | // dom api 创建元素
70 | createElement();
71 | // dom api 设置属性
72 | patchProp();
73 | // ...
74 | // dom api 插入
75 | insert();
76 | }
77 |
78 | // ...
79 | return {
80 | createApp: createAppAPI(render),
81 | };
82 | }
83 |
84 | function createAppAPI(render) {
85 | return function createApp(rootComponent) {
86 | return {
87 | mount(rootContain) {
88 | const vnode = createVNode(rootComponent);
89 | render(vnode, rootContain);
90 | },
91 | };
92 | };
93 | }
94 | ```
95 |
96 | ## 第二种情况拓展-基于用户传入的 api 的 customRenderer
97 |
98 | #### 例子基于 canvas
99 |
100 | 1. 用户调用
101 |
102 | ```ts
103 | renderer.createApp(App).mount(game.stage);
104 | ```
105 |
106 | 2. renderer
107 |
108 | ```ts
109 | const renderer = createRenderer({
110 | // 用户传入的基于 canvas 的 api
111 | createElement(type) {
112 | if (type === "rect") {
113 | const rect = new PIXI.Graphics();
114 | rect.beginFill(0xff0000);
115 | rect.drawRect(0, 0, 100, 100);
116 | rect.endFill();
117 | return rect;
118 | }
119 | },
120 | patchProp(el, key, val) {
121 | el[key] = val;
122 | },
123 | insert(el, parent) {
124 | parent.addChild(el);
125 | },
126 | });
127 |
128 | export function createRenderer(options) {
129 | const { createElement, patchProp, insert } = options;
130 |
131 | function render(vnode, container) {
132 | // canvas api 创建元素
133 | createElement();
134 | // canvas api 设置属性
135 | patchProp();
136 | // ...
137 | // canvas api 插入
138 | insert();
139 | }
140 |
141 | // ...
142 | return {
143 | createApp: createAppAPI(render),
144 | };
145 | }
146 | // 下面同第二种情况的 createApp 的逻辑了
147 | function createAppAPI(render) {
148 | return function createApp(rootComponent) {
149 | return {
150 | mount(rootContain) {
151 | const vnode = createVNode(rootComponent);
152 | render(vnode, rootContain);
153 | },
154 | };
155 | };
156 | }
157 | ```
158 |
159 | _总结_
160 |
161 | - 基于 dom 的 createApp 是在 runtime-dom 里面导出的, render 函数使用的 api 是默认传入的
162 | - 基于 canvas 的 createApp 是用户自定义的,用户需要定义一些 api,给 render 函数使用
163 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-vue-again",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "build": "rollup -c rollup.config.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/fengjinlong/mini-vue-again.git"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC",
17 | "bugs": {
18 | "url": "https://github.com/fengjinlong/mini-vue-again/issues"
19 | },
20 | "homepage": "https://github.com/fengjinlong/mini-vue-again#readme",
21 | "devDependencies": {
22 | "@babel/core": "^7.16.0",
23 | "@babel/preset-env": "^7.16.4",
24 | "@babel/preset-typescript": "^7.16.0",
25 | "@rollup/plugin-typescript": "^8.3.0",
26 | "@types/jest": "^27.0.3",
27 | "jest": "^27.4.3",
28 | "rollup": "^2.61.1",
29 | "tslib": "^2.3.1",
30 | "typescript": "^4.5.2"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript"
2 | export default {
3 | input: "./src/index.ts",
4 | output: [
5 | {
6 | format: "cjs",
7 | file: "lib/guide-mini-vue.cjs.js"
8 | },
9 | {
10 | format: "es",
11 | file: "lib/guide-mini-vue.esm.js"
12 | }
13 | ],
14 | plugins: [typescript()]
15 | }
--------------------------------------------------------------------------------
/src/112.js:
--------------------------------------------------------------------------------
1 | function fun(arr) {
2 | let maxArrIndex = [];
3 |
4 | // 起始点
5 | for (let i = 0; i < arr.length; i++) {
6 | // 从i出发,第一个递增子数组
7 | const list = [];
8 | // i< arr.length 可以优化
9 |
10 | let j = i;
11 | while (j < arr.length && arr[j] <= arr[j + 1]) {
12 | list.push(j);
13 | j++;
14 | }
15 | list.push(j);
16 |
17 | if (list.length > maxArrIndex.length) {
18 | maxArrIndex = [];
19 | for (t = 0; t < list.length; t++) {
20 | maxArrIndex.push(list[t]);
21 | }
22 | }
23 | }
24 | console.log(maxArrIndex);
25 | }
26 | const arr = [3, 2, 5, 1, 2, 3];
27 | fun(arr);
28 | // 最长递增子序列
29 |
30 |
31 |
--------------------------------------------------------------------------------
/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 | export function createVNodeCall(context, tag, props, children) {
12 | context.helper(CREATE_ELEMENT_VNODE);
13 |
14 | return {
15 | type: NodeTypes.ELEMENT,
16 | tag,
17 | props,
18 | children,
19 | };
20 | }
--------------------------------------------------------------------------------
/src/compiler-core/src/codegen.ts:
--------------------------------------------------------------------------------
1 | import { isString } from "../../shared";
2 | import { NodeTypes } from "./ast";
3 | import {
4 | CREATE_ELEMENT_VNODE,
5 | helperMapName,
6 | TO_DISPLAY_STRING,
7 | } from "./runtimeHelpers";
8 |
9 | export function generate(ast) {
10 | const context = createCodegenContext();
11 | const { push } = context;
12 |
13 | // 导入逻辑 const { toDisplayString: _toDisplayString } = Vue
14 | genFunctionPreamble(ast, context);
15 |
16 | const functionName = "render";
17 | const args = ["_ctx", "_cache"];
18 | const signature = args.join(", ");
19 | push(`function ${functionName}(${signature}) {`);
20 | push("return ");
21 | genNode(ast.codegenNode, context);
22 | push("}");
23 | return {
24 | code: context.code,
25 | };
26 | }
27 | function genFunctionPreamble(ast, context) {
28 | const { push } = context;
29 | const VueBinging = "Vue";
30 | if (ast.helpers.length) {
31 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`;
32 | push(
33 | `const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`
34 | );
35 | push("\n");
36 | }
37 | push("return ");
38 | }
39 |
40 | function genNode(node: any, context) {
41 | switch (node.type) {
42 | case NodeTypes.TEXT:
43 | // 处理文本 把内容返回
44 | genText(node, context);
45 | break;
46 | case NodeTypes.INTERPOLATION:
47 | // 处理插值 _toDisplayString
48 | // node - { type: 0, content: { type: 1, content: 'message' } }
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 | }
61 | }
62 |
63 | function genCompoundExpression(node: any, context: any) {
64 | const { push } = context;
65 | const children = node.children;
66 | for (let i = 0; i < children.length; i++) {
67 | const child = children[i];
68 | if (isString(child)) {
69 | push(child);
70 | } else {
71 | genNode(child, context);
72 | }
73 | }
74 | }
75 | function genElement(node: any, context) {
76 | const { push, helper } = context;
77 | const { tag, children, props } = node;
78 | push(`${helper(CREATE_ELEMENT_VNODE)}(`);
79 | genNodeList(genNullable([tag, props, children]), context);
80 | // console.log(children);
81 | // [
82 | // { type: 3, content: 'hi, ' },
83 | // { type: 0, content: { type: 1, content: 'message' } }
84 | // ]
85 |
86 | // for (let i = 0; i < children.length; i++) {
87 | // const child = children[i];
88 | // genNode(child, context);
89 | // }
90 | // genNode(children, context);
91 | push(")");
92 | }
93 | function genNodeList(nodes, context) {
94 | const { push } = context;
95 |
96 | for (let i = 0; i < nodes.length; i++) {
97 | const node = nodes[i];
98 | if (isString(node)) {
99 | push(node);
100 | } else {
101 | genNode(node, context);
102 | }
103 |
104 | if (i < nodes.length - 1) {
105 | push(", ");
106 | }
107 | }
108 | }
109 | function genNullable(args: any) {
110 | return args.map((arg) => arg || "null");
111 | }
112 | function genText(node, context: any) {
113 | const { push } = context;
114 | push(`'${node.content}'`);
115 | }
116 |
117 | function createCodegenContext() {
118 | const context = {
119 | code: "",
120 | push(source) {
121 | context.code += source;
122 | },
123 | helper(key) {
124 | return `_${helperMapName[key]}`;
125 | },
126 | };
127 | return context;
128 | }
129 | function genInterpolation(node: any, context: any) {
130 | const { push, helper } = context;
131 | // push(`_toDisplayString(_ctx.message)`)
132 | push(`${helper(TO_DISPLAY_STRING)}(`);
133 | genNode(node.content, context);
134 | push(`)`);
135 | }
136 |
137 | function genExpression(node: any, context: any) {
138 | const { push } = context;
139 | push(`${node.content}`);
140 | }
141 |
--------------------------------------------------------------------------------
/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/transformElement";
5 | import { transformExpression } from "./transforms/transformExpression";
6 | import { transformText } from "./transforms/transformText";
7 |
8 | export function baseCompile(template) {
9 | const ast: any = baseParse(template);
10 | transform(ast, {
11 | nodeTransforms: [transformExpression, transformElement, transformText],
12 | });
13 | return generate(ast);
14 | }
15 |
--------------------------------------------------------------------------------
/src/compiler-core/src/index.ts:
--------------------------------------------------------------------------------
1 | // 每个模块基于 此 对外导出方法
2 | export * from "./compile";
3 |
--------------------------------------------------------------------------------
/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 | const context = createParserContext(content);
10 |
11 | return createRoot(parseChildren(context, []));
12 | }
13 |
14 | function parseChildren(context, ancestors) {
15 | const nodes: any = [];
16 | while (!isEnd(context, ancestors)) {
17 | let node;
18 | let s = context.source;
19 | if (s.startsWith("{{")) {
20 | node = parseInterpolation(context);
21 | } else if (s[0] === "<") {
22 | // element
23 | if (/[a-z]/i.test(s[1])) {
24 | node = parseElement(context, ancestors);
25 | }
26 | }
27 | if (!node) {
28 | node = parseText(context);
29 | }
30 | nodes.push(node);
31 | }
32 | return nodes;
33 | }
34 | function isEnd(context, ancestors) {
35 | const s = context.source;
36 | if (s.startsWith("")) {
37 | for (let i = ancestors.length - 1; i >= 0; i--) {
38 | const tag = ancestors[i].tag;
39 | // if (s.slice(2, 2 + tag.length) === tag) {
40 | if (startsWithEndTagOpen(s, tag)) {
41 | return true;
42 | }
43 | }
44 | }
45 | return !s;
46 | }
47 |
48 | function parseText(context: any): any {
49 | let endIndex = context.source.length;
50 | let endToken = ["<", "{{"];
51 | for (let i = 0; i < endToken.length; i++) {
52 | const index = context.source.indexOf(endToken[i]);
53 |
54 | if (index !== -1 && endIndex > index) {
55 | endIndex = index;
56 | }
57 | }
58 | const content = parseTextData(context, endIndex);
59 | return {
60 | type: NodeTypes.TEXT,
61 | content: content,
62 | };
63 | }
64 |
65 | function parseTextData(context: any, length) {
66 | const content = context.source.slice(0, length);
67 | advanceBy(context, length);
68 | return content;
69 | }
70 |
71 | function parseInterpolation(context: any) {
72 | // {{xxx}}
73 |
74 | const openDelimiter = "{{";
75 | const closeDelimiter = "}}";
76 |
77 | const closeIndex = context.source.indexOf(
78 | closeDelimiter,
79 | openDelimiter.length
80 | );
81 | advanceBy(context, openDelimiter.length);
82 | const rawContentLength = closeIndex - openDelimiter.length;
83 | const rawContent = parseTextData(context, rawContentLength);
84 | // context.source.slice(0, rawContentLength);
85 | const content = rawContent.trim();
86 |
87 | // delete }}
88 | advanceBy(context, closeDelimiter.length);
89 |
90 | return {
91 | type: NodeTypes.INTERPOLATION,
92 | content: {
93 | type: NodeTypes.SIMPLE_EXPRESSION,
94 | content: content,
95 | },
96 | };
97 | }
98 |
99 | function advanceBy(context: any, length: number) {
100 | context.source = context.source.slice(length);
101 | }
102 |
103 | function createRoot(children) {
104 | return {
105 | children,
106 | type: NodeTypes.ROOT,
107 | };
108 | }
109 | function createParserContext(context: any) {
110 | return {
111 | source: context,
112 | };
113 | }
114 | function parseElement(context: any, ancestors) {
115 | // 解析tag
116 | const element: any = parseTag(context, TagType.Start);
117 | ancestors.push(element);
118 | element.children = parseChildren(context, ancestors);
119 | ancestors.pop();
120 | if (startsWithEndTagOpen(context.source, element.tag)) {
121 | parseTag(context, TagType.End);
122 | } else {
123 | throw new Error(`缺少结束标签:${element.tag}`);
124 | }
125 | // element 已经推进,所以直接地柜即可
126 | return element;
127 | }
128 |
129 | function startsWithEndTagOpen(source: any, tag: any) {
130 | return source.startsWith("") && source.slice(2, 2 + tag.length) === tag;
131 | }
132 |
133 | function parseTag(context: any, type: TagType) {
134 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
135 | const tag = match[1];
136 | advanceBy(context, match[0].length);
137 | advanceBy(context, 1);
138 | if (type === TagType.End) return;
139 | return {
140 | type: NodeTypes.ELEMENT,
141 | tag,
142 | };
143 | }
144 |
--------------------------------------------------------------------------------
/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 | // 全局上下文
6 | const context = createTransformContext(root, options);
7 | // 遍历
8 | traverseNode(root, context);
9 | createRootCodegen(root);
10 | root.helpers = [...context.helpers.keys()];
11 | // 修改
12 | }
13 | function createRootCodegen(root) {
14 | const child = root.children[0];
15 | if (child.type === NodeTypes.ELEMENT) {
16 | root.codegenNode = child.codegenNode;
17 | } else {
18 | root.codegenNode = root.children[0];
19 | }
20 | }
21 | function traverseNode(node: any, context) {
22 | const nodeTransformer = context.nodeTransformer;
23 | const exitFns: any = [];
24 | for (let i = 0; i < nodeTransformer.length; i++) {
25 | let transform = nodeTransformer[i];
26 | let onExit = transform(node, context);
27 | if (onExit) {
28 | exitFns.push(onExit);
29 | }
30 | }
31 |
32 | switch (node.type) {
33 | case NodeTypes.INTERPOLATION:
34 | // 如果是插值 需要 toDisplayString
35 | context.helper(TO_DISPLAY_STRING);
36 | break;
37 | case NodeTypes.ROOT:
38 | case NodeTypes.ELEMENT:
39 | traverseChildren(node, context);
40 | break;
41 | default:
42 | break;
43 | }
44 | let i = exitFns.length;
45 | while (i--) {
46 | exitFns[i]();
47 | }
48 | }
49 | function traverseChildren(node: any, context: any) {
50 | const children = node.children;
51 | for (let i = 0; i < children.length; i++) {
52 | const node = children[i];
53 | traverseNode(node, context);
54 | }
55 | }
56 |
57 | function createTransformContext(root: any, options: any) {
58 | const context = {
59 | root,
60 | nodeTransformer: options.nodeTransforms || [],
61 | helpers: new Map(),
62 | helper(key) {
63 | context.helpers.set(key, 1);
64 | },
65 | };
66 | return context;
67 | }
68 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformElement.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 | // context.helper(CREATE_ELEMENT_VNODE)
7 | return () => {
8 | // tag
9 | const vnodeTag = `"${node.tag}"`;
10 |
11 | // props
12 | let vnodeProps;
13 |
14 | // children
15 | const children = node.children;
16 | let vnodeChildren = children[0];
17 |
18 | node.codegenNode = createVNodeCall(
19 | context,
20 | vnodeTag,
21 | vnodeProps,
22 | vnodeChildren
23 | );
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 |
9 | function processExpression(node: any) {
10 | node.content = `_ctx.${node.content}`;
11 | return node;
12 | }
13 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformText.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "../ast";
2 | import { isText } from "../utils";
3 |
4 | export function transformText(node, context) {
5 | if (node.type === NodeTypes.ELEMENT) {
6 | return () => {
7 | const { children } = node;
8 | let currentContainer;
9 | for (let i = 0; i < children.length; i++) {
10 | const child = children[i];
11 | if (isText(child)) {
12 | for (let j = i + 1; j < children.length; j++) {
13 | const next = children[j];
14 | if (isText(next)) {
15 | if (!currentContainer) {
16 | currentContainer = children[i] = {
17 | type: NodeTypes.COMPOUND_EXPRESSION,
18 | children: [child],
19 | };
20 | }
21 | currentContainer.children.push(" + ");
22 | currentContainer.children.push(next);
23 | children.splice(j, 1);
24 | j--;
25 | } else {
26 | currentContainer = undefined;
27 | break;
28 | }
29 | }
30 | }
31 | }
32 | };
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/compiler-core/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast";
2 |
3 | export function isText(node) {
4 | return (node.nodeType =
5 | NodeTypes.TEXT || node.nodeType === NodeTypes.INTERPOLATION);
6 | }
7 |
--------------------------------------------------------------------------------
/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`] = `"return function render(_ctx, _cache) {return 'hi'}"`;
14 |
--------------------------------------------------------------------------------
/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/transformElement";
5 | import { transformExpression } from "../src/transforms/transformExpression";
6 | import { transformText } from "../src/transforms/transformText";
7 |
8 | describe("codegen", () => {
9 | it("string", () => {
10 | const ast = baseParse("hi");
11 | transform(ast);
12 | const { code } = generate(ast);
13 | expect(code).toMatchSnapshot();
14 | });
15 |
16 | it("interpolation", () => {
17 | const ast = baseParse("{{message}}");
18 | transform(ast, {
19 | nodeTransforms: [transformExpression],
20 | });
21 | const { code } = generate(ast);
22 | // 快照
23 | expect(code).toMatchSnapshot();
24 | });
25 | it("element", () => {
26 | const ast: any = baseParse("hi, {{message}}
");
27 | transform(ast, {
28 | nodeTransforms: [transformExpression, transformElement, transformText],
29 | });
30 | const { code } = generate(ast);
31 | expect(code).toMatchSnapshot();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/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("interpolation", () => {
7 | const ast = baseParse("{{message}}");
8 | expect(ast.children[0]).toStrictEqual({
9 | type: NodeTypes.INTERPOLATION,
10 | content: {
11 | type: NodeTypes.SIMPLE_EXPRESSION,
12 | content: "message",
13 | },
14 | });
15 | });
16 | });
17 |
18 | describe("element", () => {
19 | it("simple element div", () => {
20 | // const ast = baseParse("");
21 | const ast = baseParse("");
22 |
23 | expect(ast.children[0]).toStrictEqual({
24 | type: NodeTypes.ELEMENT,
25 | tag: "div",
26 | children: [
27 | // {
28 | // type: NodeTypes.ELEMENT,
29 | // tag: "div",
30 | // children: [],
31 | // },
32 | ],
33 | });
34 | });
35 | });
36 |
37 | describe("text", () => {
38 | it("simple text", () => {
39 | const ast = baseParse("some text");
40 |
41 | expect(ast.children[0]).toStrictEqual({
42 | type: NodeTypes.TEXT,
43 | content: "some text",
44 | });
45 | });
46 | });
47 |
48 | test("hello world", () => {
49 | const ast = baseParse("hi,{{message}}
");
50 |
51 | expect(ast.children[0]).toStrictEqual({
52 | type: NodeTypes.ELEMENT,
53 | tag: "p",
54 | children: [
55 | {
56 | type: NodeTypes.TEXT,
57 | content: "hi,",
58 | },
59 | {
60 | type: NodeTypes.INTERPOLATION,
61 | content: {
62 | type: NodeTypes.SIMPLE_EXPRESSION,
63 | content: "message",
64 | },
65 | },
66 | ],
67 | });
68 | });
69 |
70 | test("Nested element ", () => {
71 | const ast = baseParse("");
72 |
73 | expect(ast.children[0]).toStrictEqual({
74 | type: NodeTypes.ELEMENT,
75 | tag: "div",
76 | children: [
77 | {
78 | type: NodeTypes.ELEMENT,
79 | tag: "p",
80 | children: [
81 | {
82 | type: NodeTypes.TEXT,
83 | content: "hi",
84 | },
85 | ],
86 | },
87 | {
88 | type: NodeTypes.INTERPOLATION,
89 | content: {
90 | type: NodeTypes.SIMPLE_EXPRESSION,
91 | content: "message",
92 | },
93 | },
94 | ],
95 | });
96 | });
97 | test("should throw error when lack end tag", () => {
98 | expect(() => {
99 | baseParse("
");
100 | }).toThrow(`缺少结束标签:span`);
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/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("transform", () => {
6 | it("happpy path", () => {
7 | const ast = baseParse("hi,{{message}}
");
8 | const plugin = (node) => {
9 | if (node.type === NodeTypes.TEXT) {
10 | node.content = "hi, mini-vue";
11 | }
12 | };
13 |
14 | transform(ast, {
15 | nodeTransforms: [plugin],
16 | });
17 | const nodeText = ast.children[0].children[0];
18 | expect(nodeText.content).toBe("hi, mini-vue");
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 的出口
2 | export * from "./runtime-dom";
3 | import { baseCompile } from "./compiler-core/src";
4 | import * as runtimeDom from "./runtime-dom";
5 | import {registerRuntimeCompiler} from './runtime-core'
6 |
7 | function compileToFunction(template) {
8 | const { code } = baseCompile(template);
9 | const render = new Function("Vue", code)(runtimeDom);
10 | return render;
11 | }
12 | registerRuntimeCompiler(compileToFunction)
13 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "../shared/index";
2 | import { track, trigger } from "./effect";
3 | import { reactive, ReactiveFlegs, readonly } from "./reactive";
4 |
5 | const get = createGetter();
6 | const set = createSetter();
7 | const readonlyGet = createGetter(true);
8 | const shallowReadonlyGet = createGetter(true, true);
9 |
10 | // shallow 浅层次
11 | function createGetter(isReadOnly = false, shallow = false) {
12 | return function get(target, key) {
13 | if (key === ReactiveFlegs.IS_REACTIVE) {
14 | return !isReadOnly;
15 | } else if (key === ReactiveFlegs.IS_READONLY) {
16 | return isReadOnly;
17 | }
18 | let res = Reflect.get(target, key);
19 | if (shallow) {
20 | return res
21 | }
22 | if (isObject(res)) {
23 | return isReadOnly ? readonly(res) : reactive(res);
24 | }
25 | // TODO 收集依赖
26 | if (!isReadOnly) {
27 | track(target, key);
28 | }
29 | return res;
30 | };
31 | }
32 | function createSetter() {
33 | return function set(target, key, value) {
34 | let res = Reflect.set(target, key, value);
35 | trigger(target, key);
36 | return res;
37 | };
38 | }
39 |
40 | export const mutableHandles = {
41 | get,
42 | set,
43 | };
44 | export const readonlyHandles = {
45 | get: readonlyGet,
46 | set(target, key, value) {
47 | console.warn(`${key} 不能set,readonly!`);
48 | return true;
49 | },
50 | };
51 | export const shallowReadonlyHandles = extend({}, readonlyHandles, {
52 | get: shallowReadonlyGet,
53 | });
54 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 | /**
4 | * computed 接受一个函数
5 | * 返回一个类似ref的类型,也就是需要调用 .value 取值
6 | * 可以缓存
7 | * 缓存的原理就是通过设置一个标记_dirty,调用一次把值缓存_value,
8 | * 第二次调用不用触发getter(),直接把缓存的值_value返回
9 | */
10 | class ComputedRefImpl {
11 | private _getter: any;
12 | private _dirty: boolean = true;
13 | private _value: any;
14 | private _effect: ReactiveEffect;
15 |
16 | /**
17 | * 结合测试1 看
18 | * 下面get value 实现了 .value, 缓存
19 | */
20 | // constructor(getter) {
21 | // this._getter = getter;
22 | // }
23 | // get value() {
24 | // if (this._dirty) {
25 | // this._dirty = false;
26 | // this._value = this._getter()
27 | // }
28 | // return this._value
29 | // }
30 | /**
31 | * 结合测试2 看
32 | * 当执行value.foo = 2 时候,由于 value 是响应式对象,所以触发set操作,也就是要进行trigger,但是
33 | * getter是一个函数,相当于effect(fn)中的fn,这里只是执行了,const cValue = computed(getter)
34 | * 并没有执行effect(getter),也就是没有进行依赖收集,trigger操作肯定报错
35 | * 所以我们要收集这个 fn ,也就是收集getter,改写ComputedRefImpl 的 get value() {}
36 | * 目标:收集 getter
37 | */
38 | // constructor(getter) {
39 | // this._getter = getter;
40 | // this._effect = new ReactiveEffect(getter);
41 | // }
42 | // get value() {
43 | // if (this._dirty) {
44 | // this._dirty = false;
45 | // this._value = this._effect.run();
46 | // }
47 | // return this._value;
48 | // }
49 | /**
50 | * 结合测试2,测试3 看
51 | * 先执行 value.foo = 2;触发trigger,也就是遍历执行effect.run(),也就是又执行了一次getter()
52 | * 实际上getter() 有执行了一次
53 | * 所以 expect(getter).toHaveBeenCalledTimes(1) 失败,实际为 2
54 | * 怎么才能在trigger时候不执行run方法呢?想想effect(fn, scheduler), scheduler 的的作用
55 | * 当执行 triggerEffect 时候,effect.scheduler ?effect.scheduler() : effect.run(),
56 | * ok 随便给scheduler 一个值就行了
57 | *
58 | */
59 | // export function triggerEffect(dep: any) {
60 | // for (const effect of dep) {
61 | // if (effect.scheduler) {
62 | // effect.scheduler();
63 | // } else {
64 | // effect.run();
65 | // }
66 | // }
67 | // }
68 |
69 | // constructor(getter) {
70 | // this._getter = getter;
71 | // this._effect = new ReactiveEffect(getter, () => {});
72 | // }
73 | // get value() {
74 | // if (this._dirty) {
75 | // this._dirty = false;
76 | // this._value = this._effect.run();
77 | // }
78 | // return this._value;
79 | // }
80 |
81 | /**
82 | * 结合测试2,测试3,测试4 看
83 | * 当原始reactive类型value改变后,value.foo = 2
84 | * 希望cValue.foo也改变,毕竟是响应式的
85 | * 测试4 取 cValue.foo 时候,触发 get value(){} 的操作,
86 | * 发现_dirty还锁着呢,_value 没有更新,问题在这里
87 | * 那什么时候打开_dirty 呢?
88 | * 没错就是他, schedulers !!!
89 | *
90 | */
91 | constructor(getter) {
92 | this._getter = getter;
93 | this._effect = new ReactiveEffect(getter, () => {
94 | if (!this._dirty) {
95 | this._dirty = true;
96 | }
97 | });
98 | }
99 | get value() {
100 | if (this._dirty) {
101 | this._dirty = false;
102 | this._value = this._effect.run();
103 | }
104 | return this._value;
105 | }
106 | }
107 | export function computed(getter) {
108 | return new ComputedRefImpl(getter);
109 | }
110 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared";
2 |
3 | export class ReactiveEffect {
4 | private _fn: any;
5 | deps = [];
6 | // active 是处理重复调用stop的
7 | active = true;
8 | onStop?: () => void;
9 | // pbulic 是为了给外部获取到
10 | constructor(fn, public scheduler?) {
11 | this._fn = fn;
12 | }
13 | run() {
14 | if (!this.active) {
15 | // 不应该收集依赖
16 | // 如果调用了stop,active 为 false
17 | // 只调用第一次的 _fn, 不进行下面的依赖赋值,也就是不进行依赖收集的 track 操作
18 | return this._fn();
19 | }
20 | // this 就是依赖的,依赖的run 方法就是执行fn
21 |
22 | // 应该收集依赖逻辑
23 | activeEffect = this;
24 | shouldTract = true;
25 | const r = this._fn();
26 | shouldTract = false;
27 | return r;
28 | }
29 | stop() {
30 | // 1个 dep 对应多个 effect,同一个effect可能存在多个dep里面
31 | // 现在要清除所有 dep 里面的 目标effect,也就是先遍历depsMap得到dep,在delete每一个dep里面的effect
32 | // 但是depsMap 与 effect不存在关联关系,也就是说当前的effect 不能关系到 所有的depsMap
33 | // 这样处理,
34 | /**
35 | * 1. dep 与 effect 的关系的 dep.add(effect)
36 | * 2. 我们给每一个effect 添加一个deps 的数组空间,用来存储谁 add 当前端的effect 了
37 | * 3. 那么,我们就能从effect 本身关联到与他有关的所有dep了,也就是 deps 数组
38 | * 4. 返回来,只要遍历当前的的efect的deps属性(deps这里面的每一个dep都存在effect),dep是Set,deps是数组
39 | * 5. effect.deps.forEach(dep => dep.delete(effect))
40 | */
41 | if (this.active) {
42 | if (this.onStop) {
43 | this.onStop();
44 | }
45 | cleanUpEffect(this);
46 | this.active = false;
47 | }
48 | }
49 | }
50 | function cleanUpEffect(effect) {
51 | effect.deps.forEach((dep: any) => {
52 | dep.delete(effect);
53 | });
54 | effect.deps.length = 0;
55 | }
56 | let targetMap = new Map();
57 | let activeEffect;
58 | let shouldTract;
59 |
60 | export function track(target, key) {
61 | if (!isTracking()) return;
62 | // target key dep
63 | // 对象-- key -- 依赖
64 | let depsMap = targetMap.get(target);
65 | if (!depsMap) {
66 | depsMap = new Map();
67 | targetMap.set(target, depsMap);
68 | }
69 |
70 | let dep = depsMap.get(key);
71 | if (!dep) {
72 | dep = new Set();
73 | depsMap.set(key, dep);
74 | }
75 | // 这不光光是抽离一个函数那么简单,为ref做准备
76 | trackEffects(dep);
77 | // if(dep.has(activeEffect)) return
78 | // dep.add(activeEffect);
79 | // activeEffect.deps.push(dep);
80 | }
81 | export function trackEffects(dep) {
82 | // 看看 dep 之前有没有添加过,添加过的话 那么就不添加了
83 | if (dep.has(activeEffect)) return;
84 |
85 | dep.add(activeEffect);
86 | activeEffect.deps.push(dep);
87 | }
88 |
89 | export function isTracking() {
90 | return shouldTract && activeEffect !== undefined;
91 | }
92 |
93 | export function effect(fn, options: any = {}) {
94 | const _effect = new ReactiveEffect(fn, options.scheduler);
95 | _effect.onStop = options.onStop;
96 | extend(_effect, options);
97 | _effect.run();
98 |
99 | const runner: any = _effect.run.bind(_effect);
100 | runner.effect = _effect;
101 | return runner;
102 | }
103 | export function trigger(target, key) {
104 | let depsMap = targetMap.get(target);
105 | let dep = depsMap.get(key);
106 |
107 | triggerEffect(dep);
108 | }
109 | export function triggerEffect(dep: any) {
110 | for (const effect of dep) {
111 | if (effect.scheduler) {
112 | effect.scheduler();
113 | } else {
114 | effect.run();
115 | }
116 | }
117 | }
118 |
119 | export function stop(runner) {
120 | runner.effect.stop();
121 | // 指向类 的stop方法
122 | }
123 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ref"
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import { mutableHandles, readonlyHandles,shallowReadonlyHandles } from "./baseHandlers";
2 | export const enum ReactiveFlegs {
3 | IS_REACTIVE = '__v_isReactive',
4 | IS_READONLY = '__v_isREADONLY'
5 | }
6 | // raw 生的
7 | export function reactive(raw) {
8 | return createReactiveObject(raw, mutableHandles)
9 | }
10 | export function readonly(raw) {
11 | return createReactiveObject(raw, readonlyHandles)
12 | }
13 | function createReactiveObject(raw:any, baseHandlers: ProxyHandler) {
14 | return new Proxy(raw, baseHandlers)
15 | }
16 | export function isReadOnly(raw) {
17 | return !!raw[ReactiveFlegs.IS_READONLY]
18 | }
19 | export function isReactive(raw) {
20 | return !!raw[ReactiveFlegs.IS_REACTIVE]
21 | }
22 | export function shallowReadonly(raw) {
23 | return createReactiveObject(raw, shallowReadonlyHandles)
24 | }
25 | export function isProxy (raw) {
26 | return isReadOnly(raw) || isReactive(raw)
27 | }
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "../shared";
2 | import { isTracking, trackEffects, triggerEffect } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | class RefImpl {
6 | private _value: any;
7 | public dep;
8 | private _rawValue: any;
9 | public __v_isRef = true;
10 | constructor(value) {
11 | // 存一下原始值,当value 为reactive时候使用
12 | this._rawValue = value;
13 | this._value = convert(value);
14 | this.dep = new Set();
15 | }
16 | get value() {
17 | trackRefValue(this);
18 | return this._value;
19 | }
20 | set value(newValue: any) {
21 | // 如果value 是个reactive类型,那么需要用他的原始值作比较
22 |
23 | if (hasChanged(newValue, this._rawValue)) {
24 | this._rawValue = newValue;
25 | this._value = convert(newValue);
26 | this._rawValue = newValue;
27 | triggerEffect(this.dep);
28 | }
29 | }
30 | }
31 | function convert(value) {
32 | return isObject(value) ? reactive(value) : value;
33 |
34 | }
35 | export function ref(value) {
36 | return new RefImpl(value);
37 | }
38 | function trackRefValue(ref) {
39 | if (isTracking()) {
40 | trackEffects(ref.dep);
41 | }
42 | }
43 | export function isRef(value) {
44 | return !!value.__v_isRef
45 | }
46 | export function unRef(value) {
47 | return !!value.__v_isRef ? value.value : value
48 | }
49 | export function proxyRefs(objectWithRefs) {
50 | return new Proxy(objectWithRefs, {
51 | get (target, key) {
52 | return unRef(Reflect.get(target, key))
53 | },
54 | set (target, key, value) {
55 | if (isRef(target[key]) && !isRef(value)) {
56 | return target[key].value = value
57 | } else {
58 | return Reflect.set(target, key, value)
59 | }
60 | }
61 | })
62 | }
63 |
--------------------------------------------------------------------------------
/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 | // 缓存
9 |
10 | const user = reactive({
11 | age: 1,
12 | })
13 |
14 | const age = computed(() => {
15 | return user.age
16 | })
17 |
18 | expect(age.value).toBe(1)
19 | })
20 | it('should computed lazily', () => {
21 | const value = reactive({
22 | foo: 1
23 | })
24 | const getter = jest.fn(() => {
25 | return value.foo
26 | })
27 | const cValue = computed(getter)
28 |
29 | // 测试 1
30 | // lazy
31 | /**
32 | * 如果没有调用 cValue 的话,getter 不会执行
33 | */
34 | expect(getter).not.toHaveBeenCalled()
35 | // 调用一次 cValue
36 | expect((cValue.value)).toBe(1)
37 | // 触发一次函数
38 | expect(getter).toHaveBeenCalledTimes(1)
39 |
40 | // 再次调用
41 | cValue.value;
42 | expect(getter).toHaveBeenCalledTimes(1)
43 |
44 | // 测试 2
45 | value.foo = 2
46 |
47 | // 测试 3 触发set 操作,同样不想再次调用一次getter(),不然缓存有什么用
48 | expect(getter).toHaveBeenCalledTimes(1)
49 |
50 | // 测试 4
51 | // // now it should computed
52 | expect(cValue.value).toBe(2)
53 | expect(getter).toHaveBeenCalledTimes(2)
54 |
55 | // // // sgould not computed again
56 | cValue.value;
57 | expect(getter).toHaveBeenCalledTimes(2)
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect, stop } from "../effect";
2 | import { reactive } from "../reactive";
3 | describe("effect", () => {
4 | it("happy path", () => {
5 | const user = reactive({
6 | age:10
7 | })
8 | let nextAge
9 | effect(() => {
10 | nextAge = user.age + 1
11 | })
12 | expect(nextAge).toBe(11)
13 |
14 | // update
15 | user.age++
16 | expect(nextAge).toBe(12)
17 | });
18 | it('should return runner when call effect', () => {
19 | let foo = 10
20 | const runner = effect(() => {
21 | foo++
22 | return foo
23 | })
24 | expect(foo).toBe(11)
25 | const r = runner()
26 | expect(foo).toBe(12)
27 | expect(r).toBe(foo)
28 | })
29 |
30 |
31 | it('scheduler', () => {
32 | let dummy
33 | let run: any
34 | const scheduler = jest.fn(() => {
35 | run = runner
36 | })
37 | const obj = reactive({ foo: 1 })
38 | const runner = effect(
39 | () => {
40 | dummy = obj.foo
41 | },
42 | {
43 | scheduler,
44 | }
45 | )
46 | expect(scheduler).not.toHaveBeenCalled()
47 | expect(dummy).toBe(1)
48 |
49 | obj.foo++
50 | expect(scheduler).toHaveBeenCalledTimes(1)
51 | expect(dummy).toBe(1)
52 | run()
53 | expect(dummy).toBe(2)
54 | })
55 |
56 | it("stop", () => {
57 | let dummy;
58 | const obj = reactive({prop: 1});
59 | const runner = effect(() => {
60 | dummy = obj.prop;
61 | })
62 | obj.prop = 2
63 | expect(dummy).toBe(2)
64 |
65 | stop(runner)
66 | // 如果stop 包裹这个reunner, 数据不再是响应式的,
67 | // 也就是说需要把 对应的effect 从 deps 里删掉
68 | // 根据单测,stop参数就是runner
69 |
70 | // 只执行一次set操作
71 | // obj.prop = 5;
72 |
73 | // 先执行get 在执行set
74 | obj.prop++;
75 | expect(dummy).toBe(2)
76 |
77 | runner()
78 | expect(dummy).toBe(3)
79 | })
80 | it("onStop", () => {
81 | const obj = reactive({
82 | foo:1
83 | })
84 | const onStop = jest.fn();
85 | let dummy;
86 | const runner = effect(() => {
87 | dummy = obj.foo;
88 | }, {
89 | onStop
90 | })
91 | stop(runner)
92 | expect(onStop).toBeCalledTimes(1)
93 | })
94 |
95 | });
96 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reaadonly.spec.ts:
--------------------------------------------------------------------------------
1 |
2 | import {isProxy, isReadOnly, readonly} from '../reactive'
3 | describe("reactive", () => {
4 | it("happy path", () => {
5 | // no set
6 | const orginal = {foo: 1, bar: {baz:2}}
7 | const wrapped = readonly(orginal)
8 |
9 | expect(wrapped).not.toBe(orginal)
10 | expect(isReadOnly(wrapped)).toBe(true)
11 | expect(isProxy(wrapped)).toBe(true)
12 | expect(isReadOnly(wrapped.bar)).toBe(true)
13 | expect(isReadOnly(orginal.bar)).toBe(false)
14 | expect(isReadOnly(orginal)).toBe(false)
15 | expect(orginal.foo).toBe(1)
16 | }) // set 警告
17 | it('warn then call set', () => {
18 | console.warn = jest.fn()
19 | const user = readonly({
20 | age: 10,
21 | })
22 | user.age++;
23 | expect(console.warn).toBeCalled()
24 | })
25 | })
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReactive, reactive } from "../reactive";
2 | describe("reactive", () => {
3 | it("happy path", () => {
4 | const original = {foo: 1}
5 | const observed = reactive(original)
6 | expect(observed).not.toBe(original)
7 | expect(observed.foo).toBe(1)
8 | expect(isProxy(observed)).toBe(true)
9 | expect(isReactive(observed)).toBe(true)
10 | expect(isReactive(original)).toBe(false)
11 | original.foo = 2
12 | expect(observed.foo).toBe(2)
13 | });
14 | it("nested reactive", () => {
15 | const orginal = {
16 | nested: {
17 | foo: 1
18 | },
19 | array: [
20 | {
21 | bar: 2
22 | }
23 | ]
24 | }
25 |
26 | const observed = reactive(orginal)
27 | expect(isReactive(observed)).toBe(true)
28 | expect(isReactive(observed.nested)).toBe(true)
29 | expect(isReactive(observed.array)).toBe(true)
30 | expect(isReactive(observed.array[0])).toBe(true)
31 | })
32 | });
33 |
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from '../effect'
2 | import { reactive } from '../reactive'
3 | import { isRef, proxyRefs, ref, unRef } from '../ref'
4 |
5 | describe('ref', () => {
6 | it('happy path', () => {
7 | const a = ref(1)
8 | expect(a.value).toBe(1)
9 | })
10 | it('should be reactive', () => {
11 | const a = ref(1)
12 | let dummy;
13 | let calls = 0;
14 | effect(() => {
15 | calls++;
16 | dummy = a.value;
17 | })
18 | expect(calls).toBe(1)
19 | expect(dummy).toBe(1)
20 | // a.value = 2;
21 | a.value++
22 | expect(calls).toBe(2)
23 | expect(dummy).toBe(2)
24 |
25 | a.value = 2;
26 | expect(calls).toBe(2)
27 | expect(dummy).toBe(2)
28 | })
29 | it('should be nestes properties reactive', () => {
30 | const a = ref({
31 | count: 1,
32 | })
33 | let dummy;
34 | effect(() => {
35 | dummy = a.value.count
36 | })
37 | expect(dummy).toBe(1)
38 | a.value.count = 2
39 | expect(dummy).toBe(2)
40 | })
41 | it('isRef', () => {
42 | const a = ref(1)
43 | const aa= ref({
44 | value: 11
45 | })
46 | const user = reactive({
47 | age: 10
48 | })
49 | expect(isRef(a)).toBe(true)
50 | expect(isRef(aa)).toBe(true)
51 | expect(aa.value).toStrictEqual({"value": 11})
52 | expect(isRef(1)).toBe(false)
53 | expect(isRef(user)).toBe(false)
54 | })
55 |
56 | it('unRef', () => {
57 | const a = ref(1)
58 | expect(unRef(a)).toBe(1)
59 | expect(unRef(1)).toBe(1)
60 | })
61 |
62 | it('proxyRefs', () => {
63 | const user= {
64 | age: ref(10),
65 | name: 'xxx'
66 | }
67 | const proxyUser = proxyRefs(user)
68 | expect(user.age.value).toBe(10)
69 | expect(proxyUser.age).toBe(10)
70 | expect(proxyUser.name).toBe('xxx')
71 |
72 | proxyUser.age = 20
73 | expect(proxyUser.age).toBe(20)
74 | expect(user.age.value).toBe(20)
75 |
76 | proxyUser.age = ref(10)
77 | expect(proxyUser.age).toBe(10)
78 | expect(user.age.value).toBe(10)
79 | })
80 | })
81 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadOnly, shallowReadonly } from "../reactive"
2 | describe("shallowReadonly", () => {
3 | it("should not make non-reactive propertive reactive", () => {
4 | const props = shallowReadonly({
5 | n: {
6 | foo: 1
7 | }
8 | })
9 | expect(isReadOnly(props)).toBe(true)
10 | expect(isReadOnly(props.n)).toBe(false)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | export function provide(key, value) {
4 | const currentInstance: any = getCurrentInstance();
5 |
6 | if (currentInstance) {
7 | let { provides } = currentInstance;
8 | const parentProvides = currentInstance.parent.provides;
9 |
10 | // 初始化
11 | if (provides === parentProvides) {
12 | provides = currentInstance.provides = Object.create(parentProvides);
13 | }
14 | provides[key] = value;
15 | }
16 | }
17 | export function inject(key, defaultValue) {
18 | const currentInstance: any = getCurrentInstance();
19 | if (currentInstance) {
20 | const parentProvides = currentInstance.parent.provides;
21 | if (key in parentProvides) {
22 | return parentProvides[key];
23 | } else if (defaultValue) {
24 | if (typeof defaultValue === "function") {
25 | return defaultValue();
26 | }
27 | return defaultValue;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { shallowReadonly } from "../reactivity/reactive";
2 | import { proxyRefs } from "../reactivity/ref";
3 | import { emit } from "./componentEmit";
4 | import { initProps } from "./componentProps";
5 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
6 | import { initSlots } from "./componentSlots";
7 |
8 | export function createComponentInstance(vnode, parent) {
9 | // instance component
10 | const instance = {
11 | vnode,
12 | // 下次要更新的虚拟节点
13 | next: null,
14 | type: vnode.type,
15 | setupState: {},
16 | isMounted: false,
17 | // subTree:'',
18 | emit: () => {},
19 | slots: {},
20 | provides: parent ? parent.provides : {},
21 | parent,
22 | props: {},
23 | };
24 | instance.emit = emit.bind(null, instance) as any;
25 | return instance;
26 | }
27 |
28 | export function setupComponent(instance) {
29 | // 初始化
30 | // props
31 | initProps(instance, instance.vnode.props);
32 | initSlots(instance, instance.vnode.children);
33 |
34 | // 创建有状态的组件
35 | setupStatefulComponent(instance);
36 | }
37 |
38 | function setupStatefulComponent(instance: any) {
39 | // 调用setup 函数,拿到setup函数的返回值
40 |
41 | const Component = instance.vnode.type;
42 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
43 |
44 | const { setup } = Component;
45 | if (setup) {
46 | setCurrentInstance(instance);
47 | const setupResult = setup(shallowReadonly(instance.props), {
48 | emit: instance.emit,
49 | });
50 | setCurrentInstance(null);
51 |
52 | handleSetupResult(instance, setupResult);
53 | }
54 | }
55 | function handleSetupResult(instance: any, setupResult: any) {
56 | // 返回值是function,那就是render函数
57 | // 返回值是Object,那需要把这个对象挂到组件上下文
58 | if (typeof setupResult === "object") {
59 | instance.setupState = proxyRefs(setupResult);
60 | }
61 |
62 | // 保证组件render有值
63 | // 组件 -> const App = {
64 | // render() {
65 | // return h("div", this.msg)
66 | // },
67 | // setup() {
68 | // return {
69 | // msg: "hello vue"
70 | // }
71 | // }
72 | // }
73 | finishComponentSetup(instance);
74 | }
75 | function finishComponentSetup(instance: any) {
76 | const Component = instance.type;
77 | if (compiler && !Component.render) {
78 | if (Component.template) {
79 | Component.render = compiler(Component.template);
80 | }
81 | }
82 | instance.render = Component.render;
83 | // instance -> {
84 | // render:
85 | // setupState
86 | // vnode: {
87 | // type: App
88 | // }
89 | // }
90 | }
91 |
92 | let currentInstance = null;
93 | export function getCurrentInstance() {
94 | return currentInstance;
95 | }
96 | export function setCurrentInstance(instance) {
97 | currentInstance = instance;
98 | }
99 |
100 | let compiler;
101 | export function registerRuntimeCompiler(_compiler) {
102 | compiler = _compiler;
103 | }
104 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "../shared/index"
2 |
3 | export function emit(instance, event, ...arg) {
4 | const { props } = instance
5 | // add -> Add
6 | // add-add -> addAdd
7 |
8 | const handlerName = toHandlerKey(camelize(event))
9 | // console.log(handlerName)
10 | const handler = props[handlerName]
11 | handler && handler(...arg)
12 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 |
2 | export function initProps(instance, rawProps) {
3 | instance.props = rawProps || {}
4 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "../shared/index";
2 |
3 | const publicPropertiesMap = {
4 | $el: (i) => i.vnode.el,
5 | $slots: (i) => i.slots,
6 | $props: (i) => i.props,
7 | };
8 | export const PublicInstanceProxyHandlers = {
9 | get({ _: instance }, key) {
10 | // console.log(instance)
11 | const { setupState, props } = instance;
12 |
13 | if (hasOwn(setupState, key)) {
14 | return setupState[key];
15 | } else if (hasOwn(props, key)) {
16 | return props[key];
17 | }
18 | // $el
19 | const publicGetter = publicPropertiesMap[key];
20 | if (publicGetter) {
21 | return publicGetter(instance);
22 | }
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from '../shared/ShapeFlags'
2 |
3 | export function initSlots(instance, children) {
4 | const { vnode } = instance
5 |
6 | if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
7 | normalizeObjectSlots(children, instance.slots)
8 | }
9 | }
10 |
11 | function normalizeObjectSlots(children, slots) {
12 | for (const key in children) {
13 | const value = children[key]
14 | slots[key] = props => normalizeSlotValue(value(props))
15 | }
16 | }
17 | function normalizeSlotValue(value) {
18 | return Array.isArray(value) ? value : [value]
19 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(prevVNode, nextVNode) {
2 | const { props: prevProps } = prevVNode;
3 | const { props: nextProps } = nextVNode;
4 |
5 | for (const key in nextProps) {
6 | if (nextProps[key] !== prevProps[key]) {
7 | return true;
8 | }
9 | }
10 |
11 | return false;
12 | }
13 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 | // import { render } from "./renderer";
3 |
4 | export function createAppAPI(render) {
5 | return function createApp(rootComponent) {
6 | return {
7 | mount(rootContainer) {
8 | // 先创建 vnode
9 | // component -> vnode
10 | // 所有逻辑操作 都会基于 vnode 做处理
11 | const vnode = createVNode(rootComponent);
12 | // 渲染虚拟节点
13 | render(vnode, rootContainer);
14 | },
15 | };
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import {createVNode} from "./vnode"
2 | export function h(type, props?, children?) {
3 | return createVNode(type, props, children)
4 | }
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSolts.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | const slot = slots[name];
5 | if (slot) {
6 | if (typeof slot === "function") {
7 | return createVNode(Fragment, {}, slot(props));
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | // export {createApp} from './createApp'
2 | export { h } from "./h";
3 | export { renderSlots } from "./helpers/renderSolts";
4 | export { createTextVNode, createElementVNode } from "./vnode";
5 | export { getCurrentInstance, registerRuntimeCompiler } from "./component";
6 | export { provide, inject } from "./apiInject";
7 | export { createRenderer } from "./renderer";
8 | export { nextTick } from "./scheduler";
9 | export { toDisplayString } from "../shared";
10 | export * from "../reactivity";
11 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect";
2 | import { ShapeFlags } from "../shared/ShapeFlags";
3 | import { createComponentInstance, setupComponent } from "./component";
4 | import { shouldUpdateComponent } from "./componentUpdateUtils";
5 | import { createAppAPI } from "./createApp";
6 | import { queueJobs } from "./scheduler";
7 | import { Fragment, Text } from "./vnode";
8 |
9 | export function createRenderer(options) {
10 | const {
11 | createElement: hostCreateElement,
12 | patchProp: hostPatchProp,
13 | insert: hostInsert,
14 | setElementText: hostSetElementText,
15 | remove: hostRemove,
16 | } = options;
17 |
18 | // 查查初始化时候调用render了么?
19 | function render(vnode, container) {
20 | // patch
21 | patch(null, vnode, container, null, null);
22 | }
23 |
24 | /**
25 | * n1 老的
26 | * n2 新的
27 | */
28 | function patch(n1, n2: any, container: any, parentComponent, anchor) {
29 | // 当vnode.type的值时,组件是object,element是string,这样区分组件和元素
30 | const { type, shapeFlag } = n2;
31 | switch (type) {
32 | case Fragment:
33 | processFragment(n1, n2, container, parentComponent, anchor);
34 | break;
35 | case Text:
36 | processText(n1, n2, container);
37 | break;
38 | default:
39 | // if (typeof vnode.type === "string") {
40 | if (shapeFlag & ShapeFlags.ELEMENT) {
41 | // patch element
42 | processElement(n1, n2, container, parentComponent, anchor);
43 | // } else if (isObject(vnode.type)) {
44 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
45 | // patch 组件
46 | console.log("组件逻辑");
47 | processComponent(n1, n2, container, parentComponent, anchor);
48 | }
49 | }
50 | }
51 |
52 | function processText(n1, n2: any, container: any) {
53 | const { children } = n2;
54 | const text = document.createTextNode(children);
55 | container.append(text);
56 | }
57 |
58 | function processFragment(
59 | n1,
60 | n2: any,
61 | container: any,
62 | parentComponent,
63 | anchor
64 | ) {
65 | mountChildren(n2.children, container, parentComponent, anchor);
66 | }
67 |
68 | function processElement(
69 | n1,
70 | n2: any,
71 | container: any,
72 | parentComponent,
73 | anchor
74 | ) {
75 | // 包含初始化和更新流程
76 | // init
77 | if (!n1) {
78 | mountElement(n2, container, parentComponent, anchor);
79 | } else {
80 | patchElement(n1, n2, container, parentComponent, anchor);
81 | }
82 | }
83 | function patchElement(n1, n2, container, parentComponent, anchor) {
84 | // 获取新,老 prosp
85 | const oldProps = n1.props || {};
86 | const newProps = n2.props || {};
87 | // 对比新老props
88 | const el = (n2.el = n1.el);
89 | patchProps(el, oldProps, newProps);
90 |
91 | // 对比children
92 | patchChildren(n1, n2, el, parentComponent, anchor);
93 | }
94 | function patchChildren(n1, n2, container, parentComponent, anchor) {
95 | // 子节点只有两种类型 文本节点 数组
96 |
97 | /*
98 | 1 新的是text,老的是array
99 | 2 删除老的array 添加 文本节点
100 | */
101 |
102 | /*
103 | 1 新的 老的都是 文本节点
104 | 2 对比是否相同,不相同的话 替换老的节点
105 | */
106 |
107 | /*
108 | 1 新的是数组,老的是文本
109 | 2 删除老的,挂载新的
110 | */
111 | const { shapeFlag } = n2;
112 | const c2 = n2.children;
113 | const c1 = n1.children;
114 | const prevshapeFlag = n1.shapeFlag;
115 |
116 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
117 | // if (prevshapeFlag & ShapeFlags.ARRAY_CHILDREN) {
118 | // // 1 把老的 children 删除
119 | // unmountChildren(n1.children);
120 | // // 2 添加 text
121 | // hostSetElementText(container, c2);
122 | // } else {
123 | // // 新老都是文本节点
124 | // if(c1 !== c2) {
125 | // hostSetElementText(container, c2);
126 | // }
127 | // }
128 | // 重构一下
129 | if (prevshapeFlag & ShapeFlags.ARRAY_CHILDREN) {
130 | unmountChildren(n1.children);
131 | }
132 | if (c1 !== c2) {
133 | hostSetElementText(container, c2);
134 | }
135 | } else {
136 | // 新的是array 老的是text
137 |
138 | if (prevshapeFlag & ShapeFlags.TEXT_CHILDREN) {
139 | hostSetElementText(container, "");
140 | mountChildren(c2, container, parentComponent, anchor);
141 | } else {
142 | // array diff array
143 | patchKeyedChildren(c1, c2, container, parentComponent, anchor);
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * @description array diff array
150 | * @author Werewolf
151 | * @date 2021-12-20
152 | * @param {*} c1 老
153 | * @param {*} c2 新
154 | * @param {*} container 容器
155 | * @param {*} parentComponent 父组件
156 | * @param {*} parentAnthor 在这个元素之前插入。原由:插入有位置的要求
157 | */
158 |
159 | function patchKeyedChildren(
160 | c1,
161 | c2,
162 | container,
163 | parentComponent,
164 | parentAnthor
165 | ) {
166 | // 初始指针 i
167 | let i = 0;
168 | let l2 = c2.length;
169 | let e1 = c1.length - 1;
170 | let e2 = l2 - 1;
171 |
172 | function isSameNodeType(n1, n2) {
173 | // 相同节点 type key 相同
174 | return n1.type === n2.type && n1.key === n2.key;
175 | }
176 | // 初始指针不能超过两个数组
177 |
178 | /**
179 | * 第一种情况
180 | * 左侧对吧
181 | * ab c
182 | * ab de
183 | */
184 | while (i <= e1 && i <= e2) {
185 | const n1 = c1[i];
186 | const n2 = c2[i];
187 |
188 | if (isSameNodeType(n1, n2)) {
189 | patch(n1, n2, container, parentComponent, parentAnthor);
190 | } else {
191 | break;
192 | }
193 | i++;
194 | }
195 | /**
196 | * 第二种情况
197 | * 右侧对比
198 | * a bc
199 | * de bc
200 | */
201 | while (i <= e1 && i <= e2) {
202 | const n1 = c1[e1];
203 | const n2 = c2[e2];
204 |
205 | if (isSameNodeType(n1, n2)) {
206 | patch(n1, n2, container, parentComponent, parentAnthor);
207 | } else {
208 | break;
209 | }
210 | e1--;
211 | e2--;
212 | }
213 |
214 | /**
215 | * 第三种情况
216 | * 新的比老的多,两种情况
217 | * ab ab
218 | * ab c c ab
219 | */
220 | if (i > e1) {
221 | if (i <= e2) {
222 | const nextPos = i + 1;
223 | const anchor = i + 1 > l2 ? null : c2[nextPos].el;
224 | while (i <= e2) {
225 | patch(null, c2[i], container, parentComponent, anchor);
226 | i++;
227 | }
228 | }
229 | } else if (i > e2) {
230 | /**
231 | * 第四种情况
232 | * 新的比老的少, 两种情况
233 | * ab c a bc
234 | * ab bc
235 | */
236 | while (i <= e1) {
237 | hostRemove(c1[i].el);
238 | i++;
239 | }
240 | } else {
241 | // 中间对比,经过以上逻辑已经找到了两个临界点
242 | /**
243 | * 第五种情况-1。删除老的d,修改c
244 | * 旧 ab cd fg
245 | * 新 ab ec fg
246 | * 1 旧的里面存在,新的不存在(d),那么需要删除 d。
247 | * 如果在ec里面遍历看是否存在d,那么时间复杂度是O(n),如果用 key 映射,那么时间复杂度是O(1)
248 | *
249 | */
250 |
251 | /**
252 | * 根据新的节点建立关于key的映射关系 keyToNewIndexMap
253 | * 在老的节点里根据key查找是否存在值,也就是是否存在 keyToNewIndexMap[oldChild.key]
254 | * 存在说明是相同节点,拿到索引,进行深度 patch,不存在直接在老的节点里删除
255 | * 注意:老的节点可能是用户没有写key属性,那只能 for 遍历了
256 | *
257 | */
258 |
259 | // s1 s2 新老节点中间不同的起始位置
260 | let s1 = i;
261 | let s2 = i;
262 |
263 | /**
264 | * 优化点:当新节点的个数小于老节点点个数,也就是新的已经patch完毕,但是老节点还存在,那么老节点剩下的无需在对比,直接删除
265 | * 老 ab cedm fg,新 ab ec fg,当新节点的ec对比完毕,老节点还剩dm,那么直接删除,无需对比
266 | *
267 | * toBePatched 新节点需要patch的个数
268 | * patched 已经处理的个数
269 | *
270 | */
271 | const toBePatched = e2 - s2 + 1;
272 | let patched = 0;
273 |
274 | // 映射关系
275 | const keyToNewIndexMap = new Map();
276 |
277 | // 节点位置移动的逻辑
278 | /**
279 | * 旧 ab cde fg
280 | * 新 ab ecd fg
281 | * newIndexToOldIndexMap的长度是3, 指的是新的 ecd 的映射
282 | * 我们要把 e 在老数组的的位置(4)映射到 newIndexToOldIndexMap 里面。newIndexToOldIndexMap[0] = 4
283 | *
284 | */
285 |
286 | // 建立 初始化映射表 定长数组性能相对要好
287 | const newIndexToOldIndexMap = new Array(toBePatched);
288 |
289 | /**
290 | * 优化逻辑
291 | * moved
292 | * maxNewIndexSoFar
293 | */
294 | let moved = false;
295 | let maxNewIndexSoFar = 0;
296 |
297 | for (let i = 0; i < toBePatched; i++) {
298 | newIndexToOldIndexMap[i] = 0;
299 | }
300 |
301 | // 建立 新的映射关系
302 | for (let i = s2; i <= e2; i++) {
303 | const nextChild = c2[i];
304 | keyToNewIndexMap.set(nextChild.key, i);
305 | }
306 |
307 | // 老的映射关系
308 | for (let i = s1; i <= e1; i++) {
309 | // 老节点 prevChild
310 | const prevChild = c1[i];
311 | if (patched >= toBePatched) {
312 | // 新的已经对比完,但是老的还没完事。直接删除
313 | hostRemove(prevChild.el);
314 | // 进入下一次循环
315 | continue;
316 | }
317 | let newIndex;
318 | /**
319 | * 如果 newIndex 存在,说明 prevChild 在新的里面存在。
320 | * 如果用户写了key,用key映射查找。如果没写key,用循环查找
321 | */
322 | if (prevChild.key !== null) {
323 | newIndex = keyToNewIndexMap.get(prevChild.key);
324 | } else {
325 | for (let j = s2; j <= e2; j++) {
326 | if (isSameNodeType(c2[j], prevChild)) {
327 | newIndex = j;
328 | break;
329 | }
330 | }
331 | }
332 |
333 | if (newIndex === undefined) {
334 | // 说明不存在prevChild,删掉老的 prevChild
335 | hostRemove(prevChild.el);
336 | } else {
337 | /**
338 | * 优化点
339 | * 思路:
340 | * 1 首先最长递归子序列是递增,那么我们想要 newIndex 也应该是递增,也就不用遍历递增序列了,优化了性能
341 | * 2 如果不是递增,那么肯定需要 移动并插入
342 | *
343 | */
344 | if (newIndex >= maxNewIndexSoFar) {
345 | maxNewIndexSoFar = newIndex;
346 | } else {
347 | moved = true;
348 | }
349 |
350 | /**
351 | * ab ecd fg
352 | * 从e开始映射 e 为 0,newIndex - s2 减去前面相同的 s2 部分
353 | * 由于 newIndexToOldIndexMap[i] 的初始化都为 0,0的意义代表 新的存在,老的不存在,需要创建新的
354 | * 这里的 e 为 0,有歧义,所以用 i+1 处理,最小 为 1,不会有歧义
355 | *
356 | * */
357 |
358 | /**
359 | * newIndexToOldIndexMap 逻辑是这样的
360 | * 老的 ab cde fg
361 | * 新的 ad ecd fg
362 | * 初始 newIndexToOldIndexMap -> [0, 0, 0]
363 | * 遍历老节点,老c存在新节点创建的 Map 中,即 老c 的索引是0,所以newIndexToOldIndexMap[1] = 1(0+1)
364 | * 同理,老d存在新节点创建的Map中,即 老d 的索引是 1,所以 newIndexToOldIndexMap[2] = 2(1+1)
365 | * 老e的索引是2,所以 newIndexToOldIndexMap[0] = 3(2+1)
366 | * newIndexToOldIndexMap -> [3, 1, 2]
367 | */
368 | // 图12
369 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
370 | // 存在,继续进行深度对比
371 |
372 | patch(prevChild, c2[newIndex], container, parentComponent, null);
373 | patched++;
374 | }
375 | }
376 | /**
377 | * 移动节点
378 | * 新老都存在,只需要移动节点
379 | * 找到一个固定的序列cd,减少对比插入次数
380 | * 算法:最长递增子序列
381 | * [4,2,3] => [1,2], [4,2,3,5]=>[1,2,4]
382 | * a[i]= 0; i--) {
423 | // 拿到一个倒序的索引
424 | const nextIndex = i + s2;
425 | // 新节点树c2对应的 节点
426 | const nextChild = c2[nextIndex];
427 | // 这个节点的下一个节点的el,如果需要移动,那么就插入到这个节点之前,这就是他为锚点
428 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null;
429 | if (newIndexToOldIndexMap[i] === 0) {
430 | // 创建逻辑
431 |
432 | patch(null, nextChild, container, parentComponent, anchor);
433 | } else if (moved) {
434 | /**
435 | * i 是 c2 的中间部分的索引
436 | * 如果倒序的索引 i 跟当前的 最长递归子序列的倒序索引 j 相同,那么说明是这个节点的位置不用移动
437 | * 如果不相同,那么需要插入这个节点
438 | * 需要找到这个节点,和锚点
439 | *
440 | * */
441 | if (j < 0 || i !== increasingNewSequence[j]) {
442 | hostInsert(nextChild.el, container, anchor);
443 | // 不在最长递归子序列
444 | console.log("移动位置");
445 | } else {
446 | j--;
447 | }
448 | }
449 | }
450 | }
451 | }
452 |
453 | /**
454 | * @description 删除children 节点
455 | * @author Werewolf
456 | * @date 2021-12-17
457 | * @param {*} children
458 | */
459 | function unmountChildren(children) {
460 | for (var i = 0; i < children.length; i++) {
461 | const el = children[i].el;
462 | hostRemove(el);
463 | }
464 | }
465 | /**
466 | * @description patch 属性
467 | * @author Werewolf
468 | * @date 2021-12-20
469 | * @param {*} el
470 | * @param {*} oldProps
471 | * @param {*} newProps
472 | */
473 | function patchProps(el, oldProps, newProps) {
474 | if (oldProps !== newProps) {
475 | // newProps 里面的 prop 不在 oldProps 里面,遍历新的
476 | for (const key in newProps) {
477 | // 对比props对象的属性
478 | const prveProp = oldProps[key];
479 | const nextprop = newProps[key];
480 | if (prveProp !== nextprop) {
481 | // 调用之前的 添加属性方法,需要一个 el
482 | // 多传一个参数,同时需要修改 hostPatchProp 方法
483 | // hostPatchProp(el, key, prveProp, nextprop)
484 | hostPatchProp(el, key, prveProp, nextprop);
485 | }
486 | }
487 |
488 | // oldProps 里的 prop 不在 newProps 里面,遍历旧的
489 | if (oldProps !== {}) {
490 | for (const key in oldProps) {
491 | if (!(key in newProps)) {
492 | hostPatchProp(el, key, oldProps[key], null);
493 | }
494 | }
495 | }
496 | }
497 | }
498 |
499 | function mountElement(vnode: any, container: any, parentComponent, anchor) {
500 | // canvas new Element
501 | // const el = (vnode.el = document.createElement(vnode.type));
502 | const el = (vnode.el = hostCreateElement(vnode.type));
503 |
504 | const { props, children, shapeFlag } = vnode;
505 | // string array
506 | // if (typeof children === "string") {
507 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
508 | el.textContent = children;
509 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
510 | mountChildren(vnode.children, el, parentComponent, anchor);
511 | }
512 | for (const key in props) {
513 | const val = props[key];
514 |
515 | hostPatchProp(el, key, null, val);
516 | }
517 | // canvas el.x = 10
518 | // container.append(el);
519 | hostInsert(el, container, anchor);
520 | // canvas addChild()
521 | }
522 | /**
523 | * @description 挂载数组节点
524 | * @author Werewolf
525 | * @date 2021-12-17
526 | * @param {*} children [vnode1,vnode2]
527 | * @param {*} container
528 | * @param {*} parentComponent
529 | */
530 | function mountChildren(children, container, parentComponent, anchor) {
531 | children.forEach((v) => {
532 | patch(null, v, container, parentComponent, anchor);
533 | });
534 | }
535 | function processComponent(
536 | n1,
537 | n2: any,
538 | container: any,
539 | parentComponent,
540 | anchor
541 | ) {
542 | if (!n1) {
543 | // 初始化
544 | mountComponent(n2, container, parentComponent, anchor);
545 | } else {
546 | // 更新组件 调用当前组件的render 函数,重新 vnode 重新 patch, 也就是走 setupRenderEffect 逻辑
547 | updateComponent(n1, n2);
548 | }
549 | }
550 | /**
551 | * @description 组件更新
552 | * @author Werewolf
553 | * @date 2021-12-24
554 | * @param {*} n1
555 | * @param {*} n2
556 | */
557 | function updateComponent(n1, n2) {
558 | // 利用effect runner 逻辑
559 | /**
560 | * 怎么找instance,现在只有n 虚拟节点
561 | * 那么把实例挂载到虚拟节点
562 | *
563 | */
564 |
565 | const instance = (n2.component = n1.component);
566 | if (shouldUpdateComponent(n1, n2)) {
567 | instance.next = n2;
568 | instance.update();
569 | } else {
570 | // 不需要更新也要重置虚拟节点 和 el
571 | n2.el = n1.el;
572 | n2.vnode = n2;
573 | }
574 | }
575 |
576 | function mountComponent(
577 | initialVNode: any,
578 | container: any,
579 | parentComponent,
580 | anchor
581 | ) {
582 | // 根据虚拟节点创建组件实例
583 |
584 | // 将组件实例 挂载到虚拟接节点
585 |
586 | const instance = (initialVNode.component = createComponentInstance(
587 | initialVNode,
588 | parentComponent
589 | ));
590 |
591 | // 初始化,收集信息,instance挂载相关属性,方法, 装箱
592 | setupComponent(instance);
593 |
594 | // 渲染组件,调用组件的render方法
595 | // 组件 -> const App = {
596 | // render() {
597 | // return h("div", this.msg)
598 | // },
599 | // setup() {
600 | // return {
601 | // msg: "hello vue"
602 | // }
603 | // }
604 | // }
605 |
606 | // 一个组件不会真实渲染出来,渲染的是组件的render函数内部的element值,拆箱过程
607 | // render 返回的subTree 给patch,如果是组件继续递归,如果是element 则渲染
608 | setupRenderEffect(instance, initialVNode, container, anchor);
609 | }
610 |
611 | /**
612 | * @description 调用render,也就是生成虚拟节点,进行patch。包括 初始化和更新流程
613 | * @author Werewolf
614 | * @date 2021-12-24
615 | * @param {*} instance
616 | * @param {*} initialVNode
617 | * @param {*} container
618 | * @param {*} anchor
619 | */
620 | function setupRenderEffect(instance: any, initialVNode, container, anchor) {
621 | instance.update = effect(
622 | () => {
623 | if (!instance.isMounted) {
624 | console.log("init 初始化");
625 | const { proxy } = instance;
626 | const subTree = (instance.subTree = instance.render.call(
627 | proxy,
628 | proxy
629 | ));
630 |
631 | patch(null, subTree, container, instance, anchor);
632 |
633 | initialVNode.el = subTree.el;
634 |
635 | instance.isMounted = true;
636 | } else {
637 | console.log("update 更新");
638 |
639 | // next 新的虚拟节点
640 | // vnode 老的虚拟节点
641 | const { next, vnode } = instance;
642 | // 更新el
643 | if (next) {
644 | next.el = vnode.el;
645 | // 更新属性
646 | updateComponentPreRender(instance, next);
647 | }
648 |
649 | const { proxy } = instance;
650 | const subTree = instance.render.call(proxy, proxy);
651 | const prevSubTree = instance.subTree;
652 | instance.subTree = subTree;
653 |
654 | patch(prevSubTree, subTree, container, instance, anchor);
655 | }
656 | },
657 | {
658 | scheduler() {
659 | console.log("effect 的 scheduler 逻辑,数据更新,视图不更新");
660 | queueJobs(instance.update);
661 | },
662 | }
663 | );
664 | }
665 |
666 | return {
667 | createApp: createAppAPI(render),
668 | };
669 | }
670 |
671 | /**
672 | * @description 更新属性
673 | * @author Werewolf
674 | * @date 2021-12-24
675 | * @param {*} instance
676 | * @param {*} nextVNode
677 | */
678 | function updateComponentPreRender(instance, nextVNode) {
679 | // 更新实例的虚拟节点
680 | instance.vnode = nextVNode;
681 | instance.next = null;
682 |
683 | // 更新props
684 | instance.props = nextVNode.props;
685 | }
686 |
687 | /**
688 | * @description 最长递增子序列
689 | * @author Werewolf
690 | * @date 2021-12-24
691 | * @param {*} arr
692 | * @return {*}
693 | */
694 | function getSequence(arr) {
695 | const p = arr.slice();
696 | const result = [0];
697 | let i, j, u, v, c;
698 | const len = arr.length;
699 | for (i = 0; i < len; i++) {
700 | const arrI = arr[i];
701 | if (arrI !== 0) {
702 | j = result[result.length - 1];
703 | if (arr[j] < arrI) {
704 | p[i] = j;
705 | result.push(i);
706 | continue;
707 | }
708 | u = 0;
709 | v = result.length - 1;
710 | while (u < v) {
711 | c = (u + v) >> 1;
712 | if (arr[result[c]] < arrI) {
713 | u = c + 1;
714 | } else {
715 | v = c;
716 | }
717 | }
718 | if (arrI < arr[result[u]]) {
719 | if (u > 0) {
720 | p[i] = result[u - 1];
721 | }
722 | result[u] = i;
723 | }
724 | }
725 | }
726 | u = result.length;
727 | v = result[u - 1];
728 | while (u-- > 0) {
729 | result[u] = v;
730 | v = p[v];
731 | }
732 | return result;
733 | }
734 |
--------------------------------------------------------------------------------
/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 | export function queueJobs(job) {
9 | if (!queue.includes(job)) {
10 | queue.push(job);
11 | }
12 | queueFlush();
13 | }
14 | function queueFlush() {
15 | if (isFlushPending) {
16 | return;
17 | }
18 | isFlushPending = true;
19 | nextTick(flushJobs);
20 | }
21 | function flushJobs() {
22 | Promise.resolve().then(() => {
23 | isFlushPending = false;
24 | let job;
25 |
26 | while ((job = queue.shift())) {
27 | job && job();
28 | }
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 | export const Fragment = Symbol("Fragment");
3 | export const Text = Symbol("Text");
4 | export { createVNode as createElementVNode };
5 | export function createVNode(type, props?, children?) {
6 | // console.log(props)
7 | // 相同的节点 type key 相同
8 | const vnode = {
9 | type,
10 | key: props && props.key,
11 | props,
12 |
13 | // 组件实例 instance
14 | component: null,
15 | children,
16 | shapeFlag: getShapeFlag(type),
17 | el: null,
18 | };
19 |
20 | // 为处理children准备,给vnode再次添加一个flag
21 | // 这里的逻辑是这样的
22 | /**
23 | * a,b,c,d 为二进制数
24 | * 如果 c = a | b,那么 c&b 和 c&a 后转为十进制为非0, c&d 后转为10进制为0
25 | *
26 | */
27 | if (typeof children === "string") {
28 | // 0001 | 0100 -> 0101
29 | // 0010 | 0100 -> 0110
30 | vnode.shapeFlag = vnode.shapeFlag | ShapeFlags.TEXT_CHILDREN;
31 | } else if (Array.isArray(children)) {
32 | // 0001 | 1000 -> 1001
33 | // 0010 | 1000 -> 1010
34 | vnode.shapeFlag = vnode.shapeFlag | ShapeFlags.ARRAY_CHILDREN;
35 | }
36 |
37 | // slots children
38 | // 组件 + children object
39 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
40 | if (typeof children === "object") {
41 | vnode.shapeFlag |= ShapeFlags.SLOTS_CHILDREN;
42 | }
43 | }
44 | return vnode;
45 | }
46 | export function createTextVNode(text: string) {
47 | return createVNode(Text, {}, text);
48 | }
49 |
50 | function getShapeFlag(type: any) {
51 | // vnode 是element元素 还是 组件 0001 0010
52 | return typeof type === "string"
53 | ? ShapeFlags.ELEMENT
54 | : ShapeFlags.STATEFUL_COMPONENT;
55 | }
56 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../runtime-core";
2 | function createElement(type: string) {
3 | // console.log("dom ------api")
4 | return document.createElement(type);
5 | }
6 | function patchProp(el, key, prevVal, nextVal) {
7 | const isOn = (key: string) => /^on[A-Z]/.test(key);
8 | if (isOn(key)) {
9 | const event = key.slice(2).toLowerCase();
10 | el.addEventListener(event, nextVal);
11 | } else {
12 | if (nextVal === undefined || nextVal === null) {
13 | el.removeAttribute(key);
14 | } else {
15 | el.setAttribute(key, nextVal);
16 | }
17 | }
18 | }
19 | /**
20 | * @description 将子节点插入到指定位置anchor,没有指定位置默认插入到最后
21 | * @author Werewolf
22 | * @date 2021-12-20
23 | * @param {*} child
24 | * @param {*} parent
25 | * @param {*} anchor 将要插在这个节点之前
26 | */
27 | function insert(child, parent, anchor) {
28 | // console.log("dom ------api")
29 |
30 | // 插入到最后
31 | // parent.append(child) 等价于 parent.insertBefore(child, parent, null)
32 | // console.log()
33 | parent.insertBefore(child, anchor || null);
34 | }
35 | /**
36 | * @description 删除子节点
37 | * @author Werewolf
38 | * @date 2021-12-17
39 | * @param {*} child 子节点
40 | */
41 | function remove(child) {
42 | const parent = child.parentNode;
43 | if (parent) {
44 | parent.removeChild(child);
45 | }
46 | }
47 |
48 | /**
49 | * @description 设置text 节点
50 | * @author Werewolf
51 | * @date 2021-12-17
52 | * @param {*} el 父容器
53 | * @param {*} text 子节点
54 | */
55 | function setElementText(el, text) {
56 | el.textContent = text;
57 | }
58 |
59 | const renderer: any = createRenderer({
60 | createElement,
61 | patchProp,
62 | setElementText,
63 | remove,
64 | insert,
65 | });
66 |
67 | // return {
68 | // createApp: createAppAPI(render)
69 | // }
70 | export function createApp(...args) {
71 | return renderer.createApp(...args);
72 |
73 | // 调用流程
74 | // return createAppAPI(render)(...args);
75 | // export function createAppAPI(render) {
76 | // return function createApp(rootComponent) {
77 | // return {
78 | // mount(rootContainer) {
79 | // // 先创建 vnode
80 | // // component -> vnode
81 | // // 所有逻辑操作 都会基于 vnode 做处理
82 | // const vnode = createVNode(rootComponent);
83 | // // 渲染虚拟节点
84 | // render(vnode, rootContainer);
85 | // },
86 | // };
87 | // }
88 | // }
89 | }
90 |
91 | export * from "../runtime-core";
92 |
--------------------------------------------------------------------------------
/src/shared/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, // 0001 1
3 | STATEFUL_COMPONENT = 1 << 1, // 0010 2
4 | TEXT_CHILDREN = 1 << 2, // 0100 4
5 | ARRAY_CHILDREN = 1 << 3, // 1000 8
6 | SLOTS_CHILDREN = 1 << 4, // 10000 16
7 | }
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export const extend = Object.assign;
2 | export const isObject = (value) => {
3 | return value !== null && typeof value === "object";
4 | };
5 | export const hasChanged = (v1, v2) => {
6 | return !Object.is(v1, v2);
7 | };
8 | export const camelize = (str: string) => {
9 | return str.replace(/-(\w)/g, (_, c: string) => {
10 | return c ? c.toUpperCase() : "";
11 | });
12 | };
13 | export const isString = (value) => typeof value === "string";
14 | export const hasOwn = (val, key) =>
15 | Object.prototype.hasOwnProperty.call(val, key);
16 | const capitalize = (str: string) => {
17 | return str.charAt(0).toUpperCase() + str.slice(1);
18 | };
19 |
20 | export const toHandlerKey = (str: string) => {
21 | return str ? "on" + capitalize(str) : "";
22 | };
23 | export * from "./toDisplayString";
24 |
--------------------------------------------------------------------------------
/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 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": ["DOM","ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "esnext", /* Specify what module code is generated. */
28 | // "rootDir": "./", /* Specify the root folder within your source files. */
29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | // "outDir": "./", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/yarn-error.log:
--------------------------------------------------------------------------------
1 | Arguments:
2 | C:\Program Files\nodejs\node.exe C:\Program Files (x86)\Yarn\bin\yarn.js add rollup --dev
3 |
4 | PATH:
5 | C:\Users\fe\Desktop\cmder_mini\vendor\conemu-maximus5\ConEmu\Scripts;C:\Users\fe\Desktop\cmder_mini\vendor\conemu-maximus5;C:\Users\fe\Desktop\cmder_mini\vendor\conemu-maximus5\ConEmu;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\Git\cmd;C:\ProgramData\chocolatey\bin;C:\Program Files (x86)\Yarn\bin\;D:\mingw\bin;C:\Program Files\nodejs\;C:\Users\fe\AppData\Local\Microsoft\WindowsApps;C:\Users\fe\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\fe\AppData\Local\Yarn\bin;C:\Users\fe\AppData\Roaming\npm;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Users\fe\Desktop\cmder_mini\vendor\bin;C:\Users\fe\Desktop\cmder_mini
6 |
7 | Yarn version:
8 | 1.22.5
9 |
10 | Node version:
11 | 16.13.1
12 |
13 | Platform:
14 | win32 x64
15 |
16 | Trace:
17 | Error: ENOENT: no such file or directory, copyfile 'C:\Users\fe\AppData\Local\Yarn\Cache\v6\npm-ansi-styles-3.2.1-41fbb20243e50b12be0f04b8dedbf07520ce841d-integrity\node_modules\ansi-styles\index.js' -> 'C:\Users\fe\Desktop\mini-vue-again\node_modules\@babel\highlight\node_modules\ansi-styles\index.js'
18 |
19 | npm manifest:
20 | {
21 | "name": "mini-vue-again",
22 | "version": "1.0.0",
23 | "description": "",
24 | "main": "index.js",
25 | "scripts": {
26 | "test": "jest"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/fengjinlong/mini-vue-again.git"
31 | },
32 | "keywords": [],
33 | "author": "",
34 | "license": "ISC",
35 | "bugs": {
36 | "url": "https://github.com/fengjinlong/mini-vue-again/issues"
37 | },
38 | "homepage": "https://github.com/fengjinlong/mini-vue-again#readme",
39 | "devDependencies": {
40 | "@babel/core": "^7.16.0",
41 | "@babel/preset-env": "^7.16.4",
42 | "@babel/preset-typescript": "^7.16.0",
43 | "@types/jest": "^27.0.3",
44 | "jest": "^27.4.3",
45 | "typescript": "^4.5.2"
46 | }
47 | }
48 |
49 | yarn manifest:
50 | No manifest
51 |
52 | Lockfile:
53 | No lockfile
54 |
--------------------------------------------------------------------------------