├── .gitignore ├── README.md ├── babel.config.js ├── example ├── apiInject │ ├── App.js │ └── index.html ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ ├── index.html │ └── main.js ├── currentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── customRenderer │ └── App.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 │ ├── getSequence.js │ ├── index.html │ └── main.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── lib ├── k-mini-vue3.cjs.js └── k-mini-vue3.esm.js ├── package.json ├── rollup.config.js ├── src ├── compiler-core │ ├── src │ │ ├── ast.ts │ │ └── parse.ts │ └── tests │ │ └── parse.spec.ts ├── index.ts ├── reactivity │ ├── baseHandles.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── componentUpdateUtils.ts │ ├── createApp.ts │ ├── h.ts │ ├── helpers │ │ └── renderSlots.ts │ ├── index.ts │ ├── renderer.ts │ ├── scheduler.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ ├── ShapeFlags.ts │ └── index.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-vue3 2 | 3 | 本项目采用 TDD 驱动测试的开发方式,抽离出了 **vue3** 的核心逻辑,完成了一个最小 **vue3** 模型的实现。 4 | 5 | ## 使用了哪些技术栈 6 | 7 | :rocket: Jest 8 | 9 | :rocket: TypeScript 10 | 11 | :rocket: rollup 12 | 13 | ## 实现了什么功能 14 | 15 | ### reactivity 16 | 17 | :white_check_mark: reactive 的实现 18 | 19 | :white_check_mark: ref 的实现 20 | 21 | :white_check_mark: readonly 的实现 22 | 23 | :white_check_mark: effect 的实现 24 | 25 | :white_check_mark: track 依赖收集 26 | 27 | :white_check_mark: trigger 触发依赖 28 | 29 | :white_check_mark: 支持 effect.scheduler 30 | 31 | :white_check_mark: 支持 effect.stop 32 | 33 | :white_check_mark: 支持 isReactive 34 | 35 | :white_check_mark: 支持嵌套 reactive 36 | 37 | :white_check_mark: 支持 isProxy 38 | 39 | :white_check_mark: 支持 isReadonly 40 | 41 | :white_check_mark: 支持 shallowReadonly 42 | 43 | :white_check_mark: 支持 isRef 44 | 45 | :white_check_mark: 支持 unRef 46 | 47 | :white_check_mark: 支持 proxyRefs 48 | 49 | :white_check_mark: computed 的实现 50 | 51 | ### runtime-core 52 | 53 | :white_check_mark: 支持组件类型 54 | 55 | :white_check_mark: 支持 element 类型 56 | 57 | :white_check_mark: 初始化 props 58 | 59 | :white_check_mark: setup 可获取 props 和 context 60 | 61 | :white_check_mark: 支持 component emit 62 | 63 | :white_check_mark: 支持 proxy 64 | 65 | :white_check_mark: 可以在 render 函数中获取 setup 返回的对象 66 | 67 | :white_check_mark: nextTick 的实现 68 | 69 | :white_check_mark: 支持 getCurrentInstance 70 | 71 | :white_check_mark: 支持 provide/inject 72 | 73 | :white_check_mark: 支持最基础的 slots 74 | 75 | :white_check_mark: 支持 Text 类型节点 76 | 77 | :white_check_mark: 支持 $el api 78 | 79 | ### runtime-dom 80 | 81 | :white_check_mark: 支持 custom renderer 82 | 83 | ### compiler-core 84 | 85 | :white_large_square: 解析插值 86 | 87 | :white_large_square: 解析 element 88 | 89 | :white_large_square: 解析 text 90 | 91 | ### TODO 92 | 93 | :white_large_square: 组件代理对象/$el 抽离优化 94 | 95 | :white_large_square: custom renderer 示例:canvas 平台 96 | 97 | :white_large_square: patchChildren() 转换成 text 的优化 98 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | import { h, provide, inject } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | const Provider = { 4 | name: "Provider", 5 | setup() { 6 | provide("foo", "fooVal"); 7 | provide("bar", "barVal"); 8 | }, 9 | render() { 10 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]); 11 | }, 12 | }; 13 | 14 | const ProviderTwo = { 15 | name: "ProviderTwo", 16 | setup() { 17 | provide("foo", "fooTwo"); 18 | const foo = inject("foo"); 19 | 20 | return { 21 | foo, 22 | }; 23 | }, 24 | render() { 25 | return h("div", {}, [h("p", {}, `ProviderTwo: ${this.foo}`), h(Customer)]); 26 | }, 27 | }; 28 | 29 | const Customer = { 30 | name: "Customer", 31 | setup() { 32 | const foo = inject("foo"); 33 | const bar = inject("bar"); 34 | // 测试 inject 默认值 35 | // const baz = inject("baz", "defaultBaz"); // 默认值是 字符串 36 | const baz = inject("baz", () => "defaultBaz"); // 默认值是 函数 37 | 38 | return { 39 | foo, 40 | bar, 41 | baz, 42 | }; 43 | }, 44 | render() { 45 | return h("div", {}, `Customer: ${this.foo} - ${this.bar} - ${this.baz}`); 46 | }, 47 | }; 48 | 49 | export const App = { 50 | name: "App", 51 | setup() {}, 52 | render() { 53 | const p = h("p", {}, "App"); 54 | return h("div", { class: "app-border" }, [p, h(Provider)]); 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/k-mini-vue3.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | setup() { 7 | return {}; 8 | }, 9 | render() { 10 | const p = h("p", {}, "App"); 11 | return h("div", { class: "app-border" }, [ 12 | p, 13 | h(Foo, { 14 | // add -> onAdd 15 | onAdd(a, b) { 16 | console.log("~~onAdd"); 17 | console.log(`a+b=${a + b}`); 18 | }, 19 | // kebabe-case foo-add -> onFooAdd 20 | onFooAdd() { 21 | console.log(`~~onFooAdd`); 22 | }, 23 | }), 24 | ]); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | export const Foo = { 4 | setup(props, { emit }) { 5 | const add = () => { 6 | console.log(`click add`); 7 | emit("add", 1, 2); 8 | // kebab-case 9 | emit("foo-add"); 10 | }; 11 | 12 | console.log(props); 13 | return { 14 | add, 15 | }; 16 | }, 17 | render() { 18 | return h("button", { class: "w-100 h-30", onClick: this.add }, "foo"); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextVnode } from "../../lib/k-mini-vue3.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | setup() { 7 | return {}; 8 | }, 9 | render() { 10 | const p = h("p", {}, "App"); 11 | // 1. 单个slot clot都是 vnode 12 | // const testSlot = h("p", { class: "slot" }, "simple slot"); 13 | // 2. 多个slot放在数组里,需要renderSlot方法将所有slot -> vnode 14 | // const testSlot = [ 15 | // h("p", { class: "slot" }, "slot1"), 16 | // h("p", { class: "slot" }, "slot2"), 17 | // ]; 18 | // 3. 具名插槽 19 | // (1) 获取到要渲染的元素 20 | // (2) 获取到要渲染的位置 21 | // const testSlot = { 22 | // head: h("p", { class: "slot" }, "head-slot"), 23 | // foot: h("p", { class: "slot" }, "foot-slot"), 24 | // }; 25 | // 4. 作用域插槽 num 26 | const testSlot = { 27 | head: ({ num }) => h("p", { class: "slot" }, "head-slot" + num), 28 | // foot: () => h("p", { class: "slot" }, "foot-slot"), 29 | foot: () => [ 30 | h("p", { class: "slot" }, "foot-slot"), 31 | createTextVnode("哈哈哈"), 32 | ], 33 | }; 34 | 35 | const foo = h(Foo, {}, testSlot); 36 | return h("div", { class: "app-border" }, [p, foo]); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | export const Foo = { 4 | setup() {}, 5 | render() { 6 | const foo = h("p", {}, "foo"); 7 | console.log(this.$slot); 8 | // this.$slot 其实就是 children 9 | // 数组的情况:this.$slot array -> vnode 10 | // return h("div", { class: "child-border" }, [foo, renderSlots(this.$slot)]); 11 | 12 | // 具名插槽 -> 对象 13 | // return h("div", { class: "child-border" }, [ 14 | // renderSlots(this.$slot, "head"), 15 | // foo, 16 | // renderSlots(this.$slot, "foot"), 17 | // ]); 18 | 19 | // 作用域插槽 -> function 20 | const num = 18; 21 | return h("div", { class: "child-border" }, [ 22 | renderSlots(this.$slot, "head", { 23 | num, 24 | }), 25 | foo, 26 | renderSlots(this.$slot, "foot"), 27 | ]); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/k-mini-vue3.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; // 可以在浏览器控制台执行 msg.value = xxx 11 | 12 | const changeChildProps = () => { 13 | msg.value = "456"; 14 | }; 15 | 16 | const changeCount = () => { 17 | count.value++; 18 | }; 19 | 20 | return { 21 | msg, 22 | count, 23 | changeChildProps, 24 | changeCount, 25 | }; 26 | }, 27 | render() { 28 | const p = h("p", {}, "App"); 29 | return h("div", {}, [ 30 | p, 31 | h( 32 | "button", 33 | { 34 | onClick: this.changeChildProps, 35 | }, 36 | "changeChildProps" 37 | ), 38 | 39 | h(Child, { msg: this.msg }), 40 | h( 41 | "button", 42 | { 43 | onClick: this.changeCount, 44 | }, 45 | "changeCount更新和Child组件没有关系的数据" 46 | ), 47 | h("p", {}, `count: ${this.count}`), 48 | ]); 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | export const Child = { 4 | name: "Child", 5 | setup(props, { emit }) {}, 6 | render() { 7 | return h("p", {}, [h("div", {}, `child-props-msg: ${this.$props.msg}`)]); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/componentUpdate/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/k-mini-vue3.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | setup() { 7 | const instance = getCurrentInstance(); 8 | console.log("App:", instance); 9 | }, 10 | render() { 11 | const p = h("p", {}, "App"); 12 | return h("div", { class: "app-border" }, [p, h(Foo)]); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /example/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | export const Foo = { 4 | name: "Foo", 5 | setup() { 6 | const instance = getCurrentInstance(); 7 | console.log("Foo:", instance); 8 | }, 9 | render() { 10 | return h("p", { class: "child-border" }, "foo"); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | // TODO customRenderer 2 | -------------------------------------------------------------------------------- /example/helloworld/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/k-mini-vue3.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | // debug 控制台调试输出 self 5 | window.self = null; 6 | export const App = { 7 | render() { 8 | // debug 控制台调试输出 self 9 | window.self = this; 10 | return h( 11 | "div", 12 | { 13 | id: "root", 14 | class: ["font-30"], 15 | onClick() { 16 | console.log("click事件"); 17 | }, 18 | onMouseenter: () => { 19 | console.log("鼠标进入事件"); 20 | }, 21 | }, 22 | /** 23 | * 1. setupState 24 | * 2. this.$el -> get root element 25 | */ 26 | 27 | // children is string 28 | // "hello, " + this.msg + this.msg2 29 | 30 | // children is array 31 | // [ 32 | // h("p", { class: "red" }, "是一个p标签"), 33 | // h("span", { class: "blue" }, "是一个span标签"), 34 | // ] 35 | 36 | // 子组件 props 37 | [h(Foo, { count: 100 })] 38 | ); 39 | }, 40 | setup() { 41 | return { 42 | msg: "kongcodes", 43 | msg2: "/k-mini-vue3", 44 | }; 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /example/helloworld/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | export const Foo = { 4 | setup(props) { 5 | console.log(props); 6 | props.count++; // warn not allow update 7 | console.log(props); 8 | }, 9 | render() { 10 | return h("div", {}, "Foo:" + this.count); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/helloworld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/helloworld/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /example/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref, getCurrentInstance, nextTick } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | setup() { 6 | 7 | const instance = getCurrentInstance(); 8 | 9 | const count = ref(1); 10 | const onClick = () => { 11 | for (let i = 0; i < 100; i++) { 12 | console.log("update"); 13 | count.value = i; 14 | } 15 | 16 | console.log(instance); 17 | // nextTick 使用方式1 18 | nextTick(() => { 19 | console.log(instance) 20 | }) 21 | // nextTick 使用方式2 22 | // await nextTick(); 23 | // console.log(instance); 24 | }; 25 | 26 | return { 27 | count, 28 | onClick, 29 | }; 30 | }, 31 | render() { 32 | const button = h("button", { onClick: this.onClick }, "update"); 33 | const p = h("p", {}, `count: ${this.count}`); 34 | return h("div", {}, [button, p]); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /example/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from '../../lib/k-mini-vue3.esm.js'; 2 | 3 | import ArrayToText from './ArrayToText.js'; 4 | import ArrayToArray from './ArrayToArray.js'; 5 | import TextToText from './TextToText.js'; 6 | import TextToArray from './TextToArray.js'; 7 | 8 | export const App = { 9 | name: 'App', 10 | setup() {}, 11 | render() { 12 | // console.log(this.count); 13 | return h('div', { tId: 1 }, [ 14 | h('p', {}, '主页'), 15 | // 老的是 array, 新的是 text 16 | // h(ArrayToText), 17 | // 老的是 text, 新的是 text 18 | // h(TextToText), 19 | // 老的是 text, 新的是 text 20 | // h(TextToArray), 21 | // 老的是 array, 新的是 array 22 | h(ArrayToArray), 23 | ]); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/k-mini-vue3.esm.js'; 2 | 3 | /** 4 | * 1. 左侧的对比 5 | * (A, B) C 6 | * (A, B) D E 7 | */ 8 | // const prevChildren = [ 9 | // h('p', { key: 'A' }, 'A'), 10 | // h('p', { key: 'B' }, 'B'), 11 | // h('p', { key: 'C' }, 'C'), 12 | // ]; 13 | // const nextChildren = [ 14 | // h('p', { key: 'A' }, 'A'), 15 | // h('p', { key: 'B' }, 'B'), 16 | // h('p', { key: 'D' }, 'D'), 17 | // h('p', { key: 'E' }, 'E'), 18 | // ]; 19 | 20 | /** 21 | * 2. 右侧的对比 22 | * A (B, C) 23 | * D E (B, C) 24 | */ 25 | // const prevChildren = [ 26 | // h('p', { key: 'A' }, 'A'), 27 | // h('p', { key: 'B' }, 'B'), 28 | // h('p', { key: 'C' }, 'C'), 29 | // ]; 30 | // const nextChildren = [ 31 | // h('p', { key: 'D' }, 'D'), 32 | // h('p', { key: 'E' }, 'E'), 33 | // h('p', { key: 'B' }, 'B'), 34 | // h('p', { key: 'C' }, 'C'), 35 | // ]; 36 | 37 | /** 38 | * 3. 新的比老的长 -> 创建新的 39 | * 左侧 40 | * (A, B) 41 | * (A, B) C 42 | */ 43 | // const prevChildren = [h('p', { key: 'A' }, 'A'), h('p', { key: 'B' }, 'B')]; 44 | // const nextChildren = [ 45 | // h('p', { key: 'A' }, 'A'), 46 | // h('p', { key: 'B' }, 'B'), 47 | // h('p', { key: 'C' }, 'C'), 48 | // h('p', { key: 'D' }, 'D'), 49 | // ]; 50 | /** 51 | * 右侧 52 | * (A, B) 53 | * C (A, B) 54 | */ 55 | // const prevChildren = [h('p', { key: 'A' }, 'A'), h('p', { key: 'B' }, 'B')]; 56 | // const nextChildren = [ 57 | // h('p', { key: 'D' }, 'D'), 58 | // h('p', { key: 'C' }, 'C'), 59 | // h('p', { key: 'A' }, 'A'), 60 | // h('p', { key: 'B' }, 'B'), 61 | // ]; 62 | 63 | /** 64 | * 4. 老的比新的长 -> 删除老的 65 | * 左侧 66 | * (A, B) C 67 | * (A, B) 68 | */ 69 | // const prevChildren = [ 70 | // h('p', { key: 'A' }, 'A'), 71 | // h('p', { key: 'B' }, 'B'), 72 | // h('p', { key: 'C' }, 'C'), 73 | // ]; 74 | // const nextChildren = [h('p', { key: 'A' }, 'A'), h('p', { key: 'B' }, 'B')]; 75 | /** 76 | * 右侧 77 | * A (B, C) 78 | * (B, C) 79 | */ 80 | // const prevChildren = [ 81 | // h('p', { key: 'A' }, 'A'), 82 | // h('p', { key: 'B' }, 'B'), 83 | // h('p', { key: 'C' }, 'C'), 84 | // ]; 85 | // const nextChildren = [h('p', { key: 'B' }, 'B'), h('p', { key: 'C' }, 'C')]; 86 | 87 | /** 88 | * 5. 中间对比 89 | * 5.1 删除老的 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 | // const nextChildren = [ 102 | // h('p', { key: 'A' }, 'A'), 103 | // h('p', { key: 'B' }, 'B'), 104 | // h('p', { key: 'E' }, 'E'), 105 | // h('p', { key: 'C', id: 'c-next' }, 'C'), 106 | // h('p', { key: 'F' }, 'F'), 107 | // h('p', { key: 'G' }, 'G'), 108 | // ]; 109 | 110 | // 优化删除逻辑: 111 | // const prevChildren = [ 112 | // h('p', { key: 'A' }, 'A'), 113 | // h('p', { key: 'B' }, 'B'), 114 | // h('p', { key: 'C', id: 'c-prev' }, 'C'), 115 | // h('p', { key: 'E' }, 'E'), 116 | // h('p', { key: 'D' }, 'D'), 117 | // h('p', { key: 'F' }, 'F'), 118 | // h('p', { key: 'G' }, 'G'), 119 | // ]; 120 | // const nextChildren = [ 121 | // h('p', { key: 'A' }, 'A'), 122 | // h('p', { key: 'B' }, 'B'), 123 | // h('p', { key: 'E' }, 'E'), 124 | // h('p', { key: 'C', id: 'c-next' }, 'C'), 125 | // h('p', { key: 'F' }, 'F'), 126 | // h('p', { key: 'G' }, 'G'), 127 | // ]; 128 | 129 | /** 130 | * 5.2 移动 (节点在新的和老的里面,位置有变化) 131 | * a b (c d e) f g 132 | * a b (e c d) f g 133 | * 最长子序列 [1, 2] 134 | */ 135 | // const prevChildren = [ 136 | // h('p', { key: 'A' }, 'A'), 137 | // h('p', { key: 'B' }, 'B'), 138 | // h('p', { key: 'C' }, 'C'), 139 | // h('p', { key: 'D' }, 'D'), 140 | // h('p', { key: 'E' }, 'E'), 141 | // h('p', { key: 'F' }, 'F'), 142 | // h('p', { key: 'G' }, 'G'), 143 | // ]; 144 | // const nextChildren = [ 145 | // h('p', { key: 'A' }, 'A'), 146 | // h('p', { key: 'B' }, 'B'), 147 | // h('p', { key: 'E' }, 'E'), 148 | // h('p', { key: 'C' }, 'C'), 149 | // h('p', { key: 'D' }, 'D'), 150 | // h('p', { key: 'F' }, 'F'), 151 | // h('p', { key: 'G' }, 'G'), 152 | // ]; 153 | 154 | /** 155 | * 5.3 创建 (老的里面没有,新的里面有 需要创建) 156 | * a b (c e) f g 157 | * a b (e c d) f g 158 | */ 159 | // const prevChildren = [ 160 | // h('p', { key: 'A' }, 'A'), 161 | // h('p', { key: 'B' }, 'B'), 162 | // h('p', { key: 'C' }, 'C'), 163 | // h('p', { key: 'E' }, 'E'), 164 | // h('p', { key: 'F' }, 'F'), 165 | // h('p', { key: 'G' }, 'G'), 166 | // ]; 167 | // const nextChildren = [ 168 | // h('p', { key: 'A' }, 'A'), 169 | // h('p', { key: 'B' }, 'B'), 170 | // h('p', { key: 'E' }, 'E'), 171 | // h('p', { key: 'C' }, 'C'), 172 | // h('p', { key: 'D' }, 'D'), 173 | // h('p', { key: 'F' }, 'F'), 174 | // h('p', { key: 'G' }, 'G'), 175 | // ]; 176 | 177 | /** 178 | *中间对比综合示例 179 | * a b (c d e z) f g 180 | * a b (d c y e) f g 181 | */ 182 | // const prevChildren = [ 183 | // h('p', { key: 'A' }, 'A'), 184 | // h('p', { key: 'B' }, 'B'), 185 | // h('p', { key: 'C' }, 'C'), 186 | // h('p', { key: 'D' }, 'D'), 187 | // h('p', { key: 'E' }, 'E'), 188 | // h('p', { key: 'Z' }, 'Z'), 189 | // h('p', { key: 'F' }, 'F'), 190 | // h('p', { key: 'G' }, 'G'), 191 | // ]; 192 | // const nextChildren = [ 193 | // h('p', { key: 'A' }, 'A'), 194 | // h('p', { key: 'B' }, 'B'), 195 | // h('p', { key: 'D' }, 'D'), 196 | // h('p', { key: 'C' }, 'C'), 197 | // h('p', { key: 'Y' }, 'Y'), 198 | // h('p', { key: 'E' }, 'E'), 199 | // h('p', { key: 'F' }, 'F'), 200 | // h('p', { key: 'G' }, 'G'), 201 | // ]; 202 | 203 | /** 204 | * fix 205 | * 解决中间对比 用户没传key的情况下 会出现的问题 206 | * C 节点应该是移动的,而不是删除之后再创建的 207 | */ 208 | const prevChildren = [ 209 | h('p', { key: 'A' }, 'A'), 210 | h('p', {}, 'C'), 211 | h('p', { key: 'B' }, 'B'), 212 | h('p', { key: 'D' }, 'D'), 213 | ]; 214 | const nextChildren = [ 215 | h('p', { key: 'A' }, 'A'), 216 | h('p', { key: 'B' }, 'B'), 217 | h('p', {}, 'C'), 218 | h('p', { key: 'D' }, 'D'), 219 | ]; 220 | 221 | /** 222 | * 控制台输入 isChange.value = true; 223 | */ 224 | 225 | export default { 226 | name: 'ArrayToArray', 227 | setup() { 228 | const isChange = ref(false); 229 | window.isChange = isChange; 230 | 231 | return { 232 | isChange, 233 | }; 234 | }, 235 | render() { 236 | const self = this; 237 | 238 | return self.isChange === true 239 | ? h('div', {}, nextChildren) 240 | : h('div', {}, prevChildren); 241 | }, 242 | }; 243 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/k-mini-vue3.esm.js"; 2 | const nextChildren = "newChildren"; 3 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")]; 4 | 5 | export default { 6 | name: "ArrayToText", 7 | setup() { 8 | const isChange = ref(false); 9 | window.isChange = isChange; 10 | 11 | return { 12 | isChange, 13 | }; 14 | }, 15 | render() { 16 | const self = this; 17 | 18 | return self.isChange === true 19 | ? h("div", {}, nextChildren) 20 | : h("div", {}, prevChildren); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/k-mini-vue3.esm.js"; 2 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")]; 3 | const prevChildren = "oldChildren"; 4 | 5 | export default { 6 | name: "TextToArray", 7 | setup() { 8 | const isChange = ref(false); 9 | window.isChange = isChange; 10 | 11 | return { 12 | isChange, 13 | }; 14 | }, 15 | render() { 16 | const self = this; 17 | 18 | return self.isChange === true 19 | ? h("div", {}, nextChildren) 20 | : h("div", {}, prevChildren); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/k-mini-vue3.esm.js"; 2 | /** 3 | * text -> text 4 | */ 5 | const nextChildren = "newChildren"; 6 | const prevChildren = "oldChildren"; 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/getSequence.js: -------------------------------------------------------------------------------- 1 | // 最长递增子序列算法 2 | function getSequence(arr) { 3 | const p = arr.slice(); 4 | const result = [0]; 5 | let i, j, u, v, c; 6 | const len = arr.length; 7 | for (i = 0; i < len; i++) { 8 | const arrI = arr[i]; 9 | if (arrI !== 0) { 10 | j = result[result.length - 1]; 11 | if (arr[j] < arrI) { 12 | p[i] = j; 13 | result.push(i); 14 | continue; 15 | } 16 | u = 0; 17 | v = result.length - 1; 18 | while (u < v) { 19 | c = (u + v) >> 1; 20 | if (arr[result[c]] < arrI) { 21 | u = c + 1; 22 | } else { 23 | v = c; 24 | } 25 | } 26 | if (arrI < arr[result[u]]) { 27 | if (u > 0) { 28 | p[i] = result[u - 1]; 29 | } 30 | result[u] = i; 31 | } 32 | } 33 | } 34 | u = result.length; 35 | v = result[u - 1]; 36 | while (u-- > 0) { 37 | result[u] = v; 38 | v = p[v]; 39 | } 40 | return result; 41 | } 42 | 43 | const r = getSequence([4, 2, 3, 1, 5]); 44 | console.log(r); // 输出 [1, 2, 4] 是 数组中 2,3,5 的下标。 45 | -------------------------------------------------------------------------------- /example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/k-mini-vue3.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | setup() { 6 | const count = ref(1); 7 | const onClick = () => { 8 | count.value++; 9 | }; 10 | 11 | // 更新element的props 12 | const props = ref({ 13 | foo: "foo", 14 | bar: "bar", 15 | }); 16 | const updateProp1 = () => { 17 | console.log(1); 18 | props.value.foo = "newFoo"; 19 | }; 20 | const updateProp2 = () => { 21 | props.value.foo = undefined; 22 | }; 23 | const updateProp3 = () => { 24 | props.value = { 25 | foo: "foo", 26 | }; 27 | }; 28 | 29 | return { 30 | count, 31 | onClick, 32 | // 更新element的props 33 | updateProp1, 34 | updateProp2, 35 | updateProp3, 36 | props, 37 | }; 38 | }, 39 | render() { 40 | // console.log(this.count); 41 | return h( 42 | "div", 43 | { 44 | id: "root", 45 | ...this.props, 46 | }, 47 | [ 48 | h("div", {}, `count: ${this.count}`), // this.count触发get操作,收集依赖 49 | h("button", { onClick: this.onClick }, "+1"), 50 | // 更新element的props 51 | h("button", { onClick: this.updateProp1 }, "foo值改变-》修改"), 52 | h( 53 | "button", 54 | { onClick: this.updateProp2 }, 55 | "foo值变成null或undefined->删除" 56 | ), 57 | h("button", { onClick: this.updateProp3 }, "bar值消失-》删除"), 58 | ] 59 | ); 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/k-mini-vue3.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /lib/k-mini-vue3.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | const Fragment = Symbol("Fragment"); 6 | const Text = Symbol("Text"); 7 | function createVNode(type, props, children) { 8 | const vnode = { 9 | type, 10 | props, 11 | children, 12 | component: null, 13 | key: props && props.key, 14 | shapeFlag: getShapeFlag(type), 15 | el: null, // $el 16 | }; 17 | // 重构优化 ShapeFlags 18 | // 判断children类型 19 | if (typeof children === "string") { 20 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 21 | } 22 | else if (Array.isArray(children)) { 23 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 24 | } 25 | // 判断 children 是 slot(是slot的条件: 组件 + children是对象) 26 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 27 | if (typeof children === "object") { 28 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */; 29 | } 30 | } 31 | return vnode; 32 | } 33 | // 重构优化 ShapeFlags 34 | // 判断type类型 35 | function getShapeFlag(type) { 36 | return typeof type === "string" 37 | ? 1 /* ELEMENT */ 38 | : 2 /* STATEFUL_COMPONENT */; 39 | } 40 | function createTextVnode(text) { 41 | return createVNode(Text, {}, text); 42 | } 43 | 44 | function h(type, props, children) { 45 | // childred -> string or array 46 | return createVNode(type, props, children); 47 | } 48 | 49 | function renderSlots(slots, name, props) { 50 | // 非具名 不传name 51 | // if (!name) { 52 | // return createVNode("div", {}, slots); 53 | // } 54 | // 具名插槽 55 | const slot = slots[name]; 56 | if (slot) { 57 | if (typeof slot === "function") { 58 | // 处理作用域插槽 59 | return createVNode(Fragment, {}, slot(props)); 60 | } 61 | // return createVNode("div", {}, slot); 62 | } 63 | } 64 | 65 | const EMPTY_OBJ = {}; 66 | const extend = Object.assign; 67 | function isObject(val) { 68 | return val !== null && typeof val === "object"; 69 | } 70 | const hasChanged = (value, newValue) => { 71 | return !Object.is(value, newValue); 72 | }; 73 | // 事件注册 74 | const isOn = (key) => /^on[A-Z]/.test(key); 75 | // props 76 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 77 | // emit 78 | // 首字母大写 前面加on 79 | // add -> onAdd 80 | const capitalize = (str) => { 81 | return str.charAt(0).toUpperCase() + str.slice(1); 82 | }; 83 | // kebab-case foo-add -> fooAdd 84 | const camelize = (str) => { 85 | return str.replace(/-(\w)/g, (_, c) => { 86 | return c ? c.toUpperCase() : ""; 87 | }); 88 | }; 89 | const toHandlerKey = (str) => { 90 | return str ? "on" + capitalize(str) : ""; 91 | }; 92 | 93 | let activeEffect; 94 | let shouldTrack; 95 | class reactiveEffect { 96 | constructor(fn, scheduler) { 97 | // stop 98 | this.deps = []; 99 | this.active = true; 100 | this._fn = fn; 101 | this.scheduler = scheduler; 102 | } 103 | run() { 104 | if (!this.active) { 105 | // runner方法需要得到fn的返回值 106 | return this._fn(); 107 | } 108 | // 应该收集 109 | shouldTrack = true; 110 | activeEffect = this; 111 | const r = this._fn(); 112 | // reset 113 | shouldTrack = false; 114 | return r; 115 | } 116 | stop() { 117 | if (this.active) { 118 | cleanupEffect(this); 119 | // stop时 调用onStop方法 120 | if (this.onStop) { 121 | this.onStop(); 122 | } 123 | this.active = false; 124 | } 125 | } 126 | } 127 | function cleanupEffect(effect) { 128 | effect.deps.forEach((dep) => { 129 | dep.delete(effect); 130 | }); 131 | effect.deps.length = 0; 132 | } 133 | const targetMap = new Map(); 134 | function track(target, key) { 135 | // target -> key -> dep 136 | // targetMap -> { target: depsMap } 137 | // depsMap -> {key: dep} 138 | // dep -> activeEffect 139 | if (!isTracking()) 140 | return; // 抽离-优化 141 | let depsMap = targetMap.get(target); 142 | if (!depsMap) { 143 | depsMap = new Map(); 144 | targetMap.set(target, depsMap); 145 | } 146 | let dep = depsMap.get(key); 147 | if (!dep) { 148 | dep = new Set(); 149 | depsMap.set(key, dep); 150 | } 151 | effectTracks(dep); 152 | } 153 | function effectTracks(dep) { 154 | if (dep.has(activeEffect)) 155 | return; // 防止重复收集 156 | dep.add(activeEffect); 157 | // track的时候收集dep,stop会用到 158 | activeEffect.deps.push(dep); 159 | } 160 | function isTracking() { 161 | // if (!activeEffect) return; //解决只用reactive时,deps undefined的情况 162 | // if (!shouldTrack) return; //解决stop后还会track的问题 163 | return shouldTrack && activeEffect !== undefined; 164 | } 165 | function trigger(target, key) { 166 | const depsMap = targetMap.get(target); 167 | const dep = depsMap.get(key); 168 | effectTriggers(dep); 169 | } 170 | function effectTriggers(dep) { 171 | for (const effect of dep) { 172 | if (effect.scheduler) { 173 | effect.scheduler(); 174 | } 175 | else { 176 | effect.run(); 177 | } 178 | } 179 | } 180 | function effect(fn, options = {}) { 181 | const _effect = new reactiveEffect(fn, options.scheduler); 182 | _effect.run(); 183 | // _effect.onStop = options.onStop; // 抽离-优化 184 | extend(_effect, options); 185 | // 返回的 runner函数 186 | // run方法用到了this,使用bind处理指针问题 187 | const runner = _effect.run.bind(_effect); 188 | // stop 要用到 _effect上面的方法 189 | runner.effect = _effect; 190 | return runner; 191 | } 192 | 193 | const get = createdGetter(); 194 | const set = createdSetter(); 195 | const readonlyGet = createdGetter(true); 196 | const shallowReadonlyGet = createdGetter(true, true); 197 | function createdGetter(isReadonly = false, shallow = false) { 198 | return function get(target, key) { 199 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 200 | return !isReadonly; 201 | } 202 | else if (key === "__v_isReadonly" /* IS_READONLY */) { 203 | return isReadonly; 204 | } 205 | const res = Reflect.get(target, key); 206 | if (shallow) { 207 | return res; 208 | } 209 | // 对象的嵌套转换 -> 如果是普通对象就转换成reactive或者readonly对象 210 | if (isObject(res)) { 211 | return isReadonly ? readonly(res) : reactive(res); 212 | } 213 | if (!isReadonly) { 214 | // 依赖收集 215 | track(target, key); 216 | } 217 | return res; 218 | }; 219 | } 220 | function createdSetter() { 221 | return function set(target, key, value) { 222 | const res = Reflect.set(target, key, value); 223 | // 触发依赖 224 | trigger(target, key); 225 | return res; 226 | }; 227 | } 228 | const mutableHanders = { 229 | get, 230 | set, 231 | }; 232 | const readonlyHanders = { 233 | get: readonlyGet, 234 | set(target, key) { 235 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target); 236 | return true; 237 | }, 238 | }; 239 | const shallowReadonlyHanders = extend({}, readonlyHanders, { 240 | get: shallowReadonlyGet, 241 | }); 242 | 243 | function reactive(raw) { 244 | return createReactiveObject(raw, mutableHanders); 245 | } 246 | function readonly(raw) { 247 | return createReactiveObject(raw, readonlyHanders); 248 | } 249 | function shallowReadonly(raw) { 250 | return createReactiveObject(raw, shallowReadonlyHanders); 251 | } 252 | function createReactiveObject(target, baseHandles) { 253 | if (!isObject(target)) { 254 | console.warn(`target: ${target} 必须是一个对象`); 255 | } 256 | return new Proxy(target, baseHandles); 257 | } 258 | 259 | // {} -> value -> get set 260 | class RefImpl { 261 | constructor(value) { 262 | this.__v_isRef = true; 263 | this._rawValue = value; 264 | this._value = convert(value); 265 | // value 是对象的话需要转换成 reactive 266 | this.dep = new Set(); 267 | } 268 | get value() { 269 | // 只用到 get value时,track里面的activeEffect.deps 为undefined,解决这个问题 270 | trackRefValue(this); 271 | return this._value; 272 | } 273 | set value(newValue) { 274 | // 新旧值不改变,就不执行 275 | // 使用_rawValue,因为这个值是没有被reactive处理过的,是一个普通obj 276 | // 如果传入的是对象,_value就是被处理过的 proxy对象,hasChanged只能传入普通对象做比较 277 | if (hasChanged(newValue, this._rawValue)) { 278 | // 要先修改value,再触发依赖 279 | this._rawValue = newValue; 280 | this._value = convert(newValue); 281 | effectTriggers(this.dep); 282 | } 283 | } 284 | } 285 | function trackRefValue(ref) { 286 | if (isTracking()) { 287 | effectTracks(ref.dep); 288 | } 289 | } 290 | // 如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换 291 | function convert(value) { 292 | return isObject(value) ? reactive(value) : value; 293 | } 294 | function ref(value) { 295 | return new RefImpl(value); 296 | } 297 | function isRef(ref) { 298 | // 如果传入数值 1 这种参数,ref.__v_isRef会是undefined,所以用!!转换成Boolean 299 | return !!ref.__v_isRef; 300 | } 301 | function unRef(ref) { 302 | return isRef(ref) ? ref.value : ref; 303 | } 304 | function proxyRefs(objectWithRefs) { 305 | return new Proxy(objectWithRefs, { 306 | get(target, key) { 307 | return unRef(Reflect.get(target, key)); 308 | }, 309 | set(target, key, value) { 310 | if (isRef(target[key]) && !isRef(value)) { 311 | return (target[key].value = value); 312 | } 313 | else { 314 | return Reflect.set(target, key, value); 315 | } 316 | }, 317 | }); 318 | } 319 | 320 | function emit(instance, event, ...args) { 321 | console.log("event--", event); 322 | // props里面找 emit 绑定的参数 323 | // emit('add') -> onAdd(){} 324 | const { props } = instance; 325 | /** 326 | * TPP 327 | * 特定行为 -> 通用行为 328 | */ 329 | const handlerName = toHandlerKey(camelize(event)); 330 | const handler = props[handlerName]; 331 | handler && handler(...args); 332 | } 333 | 334 | function initProps(instance, rawProps) { 335 | instance.props = rawProps || {}; // shallowReadonly 时参数必须为对象,如果没有传props, rawProps === undefined 报错 336 | } 337 | 338 | function initSlots(instance, children) { 339 | // 判断是slot的时候才执行函数,组件 && children 是 object 才是 slots 340 | const { shapeFlag } = instance.vnode; 341 | // if (typeof instance.type === "object" && typeof children === "object") { 342 | if (shapeFlag & 16 /* SLOT_CHILDREN */) { 343 | normalizeObjectSlots(instance, children); 344 | } 345 | } 346 | function normalizeObjectSlots(instance, children) { 347 | // children -> array 348 | // instance.slots = Array.isArray(children) ? children : [children]; 349 | console.log(instance); 350 | // 具名插槽 -> children object 351 | const slots = {}; 352 | for (const key in children) { 353 | const value = children[key]; 354 | // slots[key] = normalizeSlotValue(value); 355 | // 作用域插槽 function 356 | slots[key] = (props) => normalizeSlotValue(value(props)); 357 | } 358 | instance.slots = slots; 359 | } 360 | function normalizeSlotValue(value) { 361 | return Array.isArray(value) ? value : [value]; 362 | } 363 | 364 | function createComponentInstance(vnode, parent) { 365 | // console.log("parent", parent); 366 | const component = { 367 | vnode, 368 | type: vnode.type, 369 | setupState: {}, 370 | props: {}, 371 | slots: {}, 372 | provides: parent ? parent.provides : {}, 373 | parent, 374 | isMounted: false, 375 | subTree: {}, 376 | emit: () => { }, 377 | update: null, 378 | next: null, 379 | }; 380 | // 处理emit方法,需要event和instance两个参数,但用户只传一个 add 381 | // 如:emit('add') 382 | // 所以在这里使用bind处理这个问题 383 | component.emit = emit.bind(null, component); 384 | return component; 385 | } 386 | function setupComponent(instance) { 387 | /** 388 | * init 389 | */ 390 | initProps(instance, instance.vnode.props); 391 | initSlots(instance, instance.vnode.children); 392 | setupStatefulComponent(instance); 393 | } 394 | function setupStatefulComponent(instance) { 395 | const Component = instance.type; 396 | // ctx 397 | instance.proxy = new Proxy({}, { 398 | get(target, key) { 399 | const { setupState, props } = instance; 400 | // if (key in setupState) { 401 | // return setupState[key]; 402 | // } 403 | if (hasOwn(setupState, key)) { 404 | return setupState[key]; 405 | } 406 | else if (hasOwn(props, key)) { 407 | // props 408 | return props[key]; 409 | } 410 | // key -> $el 411 | if (key === "$el") { 412 | return instance.vnode.el; 413 | } 414 | // key -> $slot 415 | if (key === "$slot") { 416 | return instance.slots; 417 | } 418 | // key -> $props 419 | if (key === "$props") { 420 | return instance.props; 421 | } 422 | }, 423 | }); 424 | // 拿到 setup 返回值 425 | const { setup } = Component; 426 | if (setup) { 427 | setCurrentInstance(instance); 428 | // 用户可能不写setup 429 | // setup() 可能返回 function 或者 object 430 | // 如果是function 就认为组件返回了render函数 431 | // 如果是object 会把object注入到组件上下文中 432 | const setupResult = setup(shallowReadonly(instance.props), { 433 | emit: instance.emit, 434 | }); 435 | setCurrentInstance(null); 436 | handleSetupResult(instance, setupResult); 437 | } 438 | } 439 | function handleSetupResult(instance, setupResult) { 440 | // TODO function 441 | if (typeof setupResult === "object") { 442 | instance.setupState = proxyRefs(setupResult); 443 | } 444 | finishComponentSetup(instance); 445 | } 446 | function finishComponentSetup(instance) { 447 | const Component = instance.type; 448 | if (Component.render) { 449 | instance.render = Component.render; 450 | } 451 | } 452 | // getCurrentInstance Api 453 | let currentInstance = null; 454 | function setCurrentInstance(instance) { 455 | currentInstance = instance; 456 | } 457 | function getCurrentInstance() { 458 | return currentInstance; 459 | } 460 | 461 | function provide(key, value) { 462 | // 存 463 | const currentInstance = getCurrentInstance(); 464 | if (currentInstance) { 465 | let { provides } = currentInstance; 466 | const parentProvides = currentInstance.parent.provides; 467 | // 判断是初始化的时候才执行 468 | if (provides === parentProvides) { 469 | // 给 provides 指定原型链对象为 父级 470 | provides = currentInstance.provides = Object.create(parentProvides); 471 | } 472 | provides[key] = value; 473 | } 474 | } 475 | function inject(key, defaultValue) { 476 | // 取 477 | const currentInstance = getCurrentInstance(); 478 | if (currentInstance) { 479 | const parentProvides = currentInstance.parent.provides; 480 | if (key in parentProvides) { 481 | return parentProvides[key]; 482 | } 483 | else if (defaultValue) { 484 | // 处理默认值 可能是字符串或函数 485 | if (typeof defaultValue === "function") { 486 | return defaultValue(); 487 | } 488 | return defaultValue; 489 | } 490 | } 491 | } 492 | 493 | function shouldUpdateComponent(prevVNode, nextVNode) { 494 | const { props: prevProps } = prevVNode; 495 | const { props: nextProps } = nextVNode; 496 | for (const key in nextProps) { 497 | if (nextProps[key] !== prevProps[key]) { 498 | return true; 499 | } 500 | } 501 | return false; 502 | } 503 | 504 | // import { render } from "./renderer"; 505 | function createAppAPI(render) { 506 | return function createApp(rootComponent) { 507 | return { 508 | mount(rootContainer) { 509 | /** 510 | * 先将内容解析成 vnode 511 | * component -> vnode 512 | * 后面的所有逻辑都会基于 vnode 进行操作 513 | */ 514 | // rootContainer -> dom 515 | if (typeof rootContainer === "string") { 516 | rootContainer = document.querySelector(rootContainer); 517 | } 518 | const vnode = createVNode(rootComponent); 519 | render(vnode, rootContainer); 520 | }, 521 | }; 522 | }; 523 | } 524 | // export function createApp(rootComponent) { 525 | // return { 526 | // mount(rootContainer) { 527 | // /** 528 | // * 先将内容解析成 vnode 529 | // * component -> vnode 530 | // * 后面的所有逻辑都会基于 vnode 进行操作 531 | // */ 532 | // // rootContainer -> dom 533 | // if (typeof rootContainer === "string") { 534 | // rootContainer = document.querySelector(rootContainer); 535 | // } 536 | // const vnode = createVNode(rootComponent); 537 | // render(vnode, rootContainer); 538 | // }, 539 | // }; 540 | // } 541 | 542 | const queue = []; 543 | let isFlushPending = false; // 优化每次都会创建promise的问题 544 | function queueJobs(job) { 545 | if (!queue.includes(job)) { 546 | queue.push(job); 547 | } 548 | queueFlush(); 549 | } 550 | function nextTick(fn) { 551 | return fn ? Promise.resolve().then(fn) : Promise.resolve(); 552 | } 553 | function queueFlush() { 554 | if (isFlushPending) 555 | return; 556 | isFlushPending = true; 557 | Promise.resolve().then(() => { 558 | isFlushPending = false; 559 | let job; 560 | while ((job = queue.shift())) { 561 | job && job(); 562 | } 563 | }); 564 | } 565 | 566 | function createRenderer(options) { 567 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options; 568 | function render(vnode, container) { 569 | // patch 570 | // console.log("vnode-----", vnode); 571 | patch(null, vnode, container, null, null); // 处理根组件不传 parentComponent 参数 572 | } 573 | // 优化 patch 架构 574 | // n1 -> 老的,如果不存在则是初始化,存在就是更新逻辑 575 | // n2 -> 新的 576 | // function patch(vnode, container, parentComponent) { 577 | function patch(n1, n2, container, parentComponent, anchor) { 578 | /** 579 | * 区分是 element 还是 component 580 | * 判断两种类型 581 | */ 582 | // debugger; 583 | // console.log("patch-------", vnode); 584 | // 使用 shapeFlag 判断类型 585 | const { type, shapeFlag } = n2; 586 | switch (type) { 587 | case Fragment: 588 | processFragment(n1, n2, container, parentComponent, anchor); 589 | break; 590 | case Text: 591 | processText(n1, n2, container); 592 | break; 593 | default: 594 | // shapeFlag & ShapeFlags.STATEFUL_COMPONENT 等同于 typeof vnode.type === "object" 595 | if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 596 | // 处理component 597 | processComponent(n1, n2, container, parentComponent, anchor); 598 | } 599 | else if (shapeFlag & 1 /* ELEMENT */) { 600 | // 处理 element 601 | processElement(n1, n2, container, parentComponent, anchor); 602 | } 603 | break; 604 | } 605 | } 606 | // element 类型 607 | function processElement(n1, n2, container, parentComponent, anchor) { 608 | if (!n1) { 609 | mountElement(n2, container, parentComponent, anchor); 610 | } 611 | else { 612 | patchElement(n1, n2, container, parentComponent, anchor); 613 | } 614 | } 615 | function patchElement(n1, n2, container, parentComponent, anchor) { 616 | // console.log("n1", n1); 617 | // console.log("n2", n2); 618 | // console.log("container", container); 619 | // update props 620 | const oldProps = n1.props || EMPTY_OBJ; 621 | const newProps = n2.props || EMPTY_OBJ; 622 | const el = n1.el; 623 | n2.el = el; // 本轮n2就是下一轮的n1,不赋值的话 下轮n1中就没有el 624 | patchChildren(n1, n2, el, parentComponent, anchor); 625 | patchProps(el, oldProps, newProps); 626 | } 627 | function patchChildren(n1, n2, container, parentComponent, anchor) { 628 | const prevShapeFlag = n1.shapeFlag; 629 | const nextShapeFlag = n2.shapeFlag; 630 | const c1 = n1.children; 631 | const c2 = n2.children; 632 | if (nextShapeFlag & 4 /* TEXT_CHILDREN */) { 633 | // TODO 优化 634 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) { 635 | // array -> text 636 | // 1. 把老的清空 637 | unmountChildren(n1.children); 638 | // 2. 设置text 639 | hostSetElementText(container, c2); 640 | } 641 | else { 642 | // text -> text 643 | // 前后节点不一样才需要改变 644 | if (c1 !== c2) { 645 | hostSetElementText(container, c2); 646 | } 647 | } 648 | } 649 | else { 650 | // text -> array 651 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) { 652 | hostSetElementText(container, ""); 653 | mountChildren(c2, container, parentComponent, anchor); 654 | } 655 | else { 656 | // array diff children 657 | patchKeyedChildren(c1, c2, container, parentComponent, anchor); 658 | } 659 | } 660 | } 661 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) { 662 | // const l2 = c2.length; 663 | let i = 0; 664 | let e1 = c1.length - 1; 665 | let e2 = c2.length - 1; 666 | // debugger 667 | function isSameVNodeType(n1, n2) { 668 | // type 和 key 判断两种 669 | return n1.type === n2.type && n1.key === n2.key; 670 | } 671 | // 1. 左侧对比 i指针->向右移动 672 | while (i <= e1 && i <= e2) { 673 | const n1 = c1[i]; 674 | const n2 = c2[i]; 675 | if (isSameVNodeType(n1, n2)) { 676 | patch(n1, n2, container, parentComponent, parentAnchor); 677 | } 678 | else { 679 | break; 680 | } 681 | i++; 682 | } 683 | // 2. 右侧对比 e1和e2指针->向左移动 684 | while (i <= e1 && i <= e2) { 685 | const n1 = c1[e1]; 686 | const n2 = c2[e2]; 687 | if (isSameVNodeType(n1, n2)) { 688 | patch(n1, n2, container, parentComponent, parentAnchor); 689 | } 690 | else { 691 | break; 692 | } 693 | e1--; 694 | e2--; 695 | } 696 | // 3. 新的比老的长 - 左侧和右侧 创建新的 697 | if (i > e1) { 698 | if (i <= e2) { 699 | // debugger; 700 | const nextPos = e2 + 1; // 锚点位置 701 | const anchor = nextPos < c2.length ? c2[nextPos].el : null; // 判断:左侧对比 -> null 还在后面插入节点 右侧对比 -> 找"A"节点传入 在"A"前面插入 702 | // 多个 child 遍历执行 patch 703 | while (i <= e2) { 704 | patch(null, c2[i], container, parentComponent, anchor); 705 | i++; 706 | } 707 | } 708 | } 709 | // 4. 老的比新的长 - 左侧和右侧 删除老的 710 | else if (i > e2) { 711 | while (i <= e1) { 712 | hostRemove(c1[i].el); 713 | i++; 714 | } 715 | } 716 | // 5. 中间对比 717 | else { 718 | let s1 = i; 719 | let s2 = i; 720 | /** 721 | * 删除的优化: 722 | * 新节点中 对比一次就记录一次;新节点全部对比完成后,如果老节点还有剩余元素的话 723 | * 就可以把这些全部删除 724 | */ 725 | const toBePatched = e2 - s2 + 1; // 新的中需要对比的全部数量 726 | let patched = 0; // 已经对比完成的 727 | /** 728 | * 最长递增子序列 729 | * 移动 730 | * 建立映射表 定长数组 731 | */ 732 | const newIndexToOldIndexMap = new Array(toBePatched); 733 | for (let i = 0; i < toBePatched; i++) 734 | newIndexToOldIndexMap[i] = 0; 735 | /** 736 | * 移动 优化 737 | * 什么时候需要移动 738 | */ 739 | let moved = false; 740 | let maxNewIndexSoFar = 0; 741 | // 基于新的 里面的 key 建立 映射表 742 | const keyToNewIndexMap = new Map(); 743 | for (let i = s2; i <= e2; i++) { 744 | const nextChild = c2[i]; 745 | keyToNewIndexMap.set(nextChild.key, i); 746 | } 747 | // 遍历老的 748 | for (let i = s1; i <= e1; i++) { 749 | // 拿到当前节点 750 | const prevChild = c1[i]; 751 | if (patched > toBePatched) { 752 | hostRemove(prevChild.el); 753 | continue; 754 | } 755 | let newIndex; 756 | // 根据 null 和 undefined 判断用户写没写key 757 | if (prevChild.key != null) { 758 | newIndex = keyToNewIndexMap.get(prevChild.key); 759 | } 760 | else { 761 | // 如果没有 key 就需要遍历判断 性能低 762 | for (let j = s2; j <= e2; j++) { 763 | if (isSameVNodeType(prevChild, c2[j])) { 764 | newIndex = j; 765 | break; 766 | } 767 | } 768 | } 769 | // 如果newIndex存在,就说明 新老里面都有该节点 770 | // 不存在 就删除老的 771 | if (newIndex === undefined) { 772 | hostRemove(prevChild.el); 773 | } 774 | else { 775 | if (newIndex >= maxNewIndexSoFar) { 776 | maxNewIndexSoFar = newIndex; 777 | } 778 | else { 779 | moved = true; 780 | } 781 | /** 782 | * 为什么是i+1,而不是i? 783 | * 因为i 可能是0,而0在这里有特殊含义,因为初始化赋的值就是0,需要用0判断是否需要创建元素 784 | * 所以就赋值为i+1 避免这个问题 785 | */ 786 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 787 | patch(prevChild, c2[newIndex], container, parentComponent, null); 788 | patched++; 789 | } 790 | } 791 | // 生成最长递增子序列 792 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; 793 | let j = increasingNewIndexSequence.length - 1; // 生成的子序列的下标 794 | // 从后往前遍历,因为 insertBefore插入元素需要在一个稳定元素的前面插入 795 | for (let i = toBePatched - 1; i >= 0; i--) { 796 | const nextIndex = i + s2; 797 | const nextChild = c2[nextIndex]; 798 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null; 799 | /** 800 | * 创建 801 | */ 802 | if (newIndexToOldIndexMap[i] === 0) { 803 | // 在老的里面没有需要创建 804 | patch(null, nextChild, container, parentComponent, anchor); 805 | } 806 | /** 807 | * 移动 808 | */ 809 | else if (moved) { 810 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 811 | // 移动位置 812 | hostInsert(nextChild.el, container, anchor); 813 | } 814 | else { 815 | j--; 816 | } 817 | } 818 | } 819 | } 820 | } 821 | function unmountChildren(children) { 822 | for (let i = 0; i < children.length; i++) { 823 | const el = children[i].el; 824 | // remove 825 | hostRemove(el); 826 | } 827 | } 828 | function patchProps(el, oldProps, newProps) { 829 | if (oldProps !== newProps) { 830 | // 健壮性 831 | for (const key in newProps) { 832 | const prevProp = oldProps[key]; 833 | const nextProp = newProps[key]; 834 | if (prevProp !== nextProp) { 835 | hostPatchProp(el, key, prevProp, nextProp); 836 | } 837 | } 838 | if (oldProps !== EMPTY_OBJ) { 839 | // 健壮性 840 | // update props 的第三种情况:属性被删除 841 | for (const key in oldProps) { 842 | if (!(key in newProps)) { 843 | hostPatchProp(el, key, oldProps[key], null); 844 | } 845 | } 846 | } 847 | } 848 | } 849 | function mountElement(vnode, container, parentComponent, anchor) { 850 | const { type, children, props } = vnode; 851 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 852 | // const el = document.createElement(type); 853 | const el = hostCreateElement(type); 854 | // $el 855 | // vnode -> element -> div 856 | vnode.el = el; 857 | // children -> string or array 858 | // 使用 shapeFlag 判断类型 859 | const { shapeFlag } = vnode; 860 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 861 | // children is string 862 | el.textContent = children; 863 | } 864 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 865 | // children is array 866 | mountChildren(vnode.children, el, parentComponent, anchor); // 抽离-优化 867 | } 868 | // props 869 | for (const key in props) { 870 | const val = props[key]; 871 | // console.log(key); 872 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 873 | // if (isOn(key)) { 874 | // const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写 875 | // el.addEventListener(eventName, val); 876 | // } else { 877 | // el.setAttribute(key, val); 878 | // } 879 | hostPatchProp(el, key, null, val); 880 | } 881 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 882 | // container.append(el); 883 | hostInsert(el, container, anchor); 884 | } 885 | function mountChildren(children, el, parentComponent, anchor) { 886 | children.forEach((v) => { 887 | patch(null, v, el, parentComponent, anchor); 888 | }); 889 | } 890 | // component 类型 891 | function processComponent(n1, n2, container, parentComponent, anchor) { 892 | if (!n1) { 893 | mountComponent(n2, container, parentComponent, anchor); 894 | } 895 | else { 896 | updateComponent(n1, n2); 897 | } 898 | } 899 | function updateComponent(n1, n2) { 900 | const instance = (n2.component = n1.component); 901 | // 判断是否需要更新 902 | if (shouldUpdateComponent(n1, n2)) { 903 | // 新的vnode保存起来 下次要更新的 904 | instance.next = n2; 905 | instance.update(); 906 | } 907 | else { 908 | n2.el = n1.el; 909 | instance.vnode = n2; 910 | } 911 | } 912 | function mountComponent(initialVNode, container, parentComponent, anchor) { 913 | // 创建组件实例 914 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)); 915 | setupComponent(instance); 916 | setupRenderEffect(instance, container, anchor); 917 | } 918 | function setupRenderEffect(instance, container, anchor) { 919 | // 使用effect包裹原来的逻辑,收集依赖 920 | instance.update = effect(() => { 921 | console.log("setupRenderEffect"); 922 | if (!instance.isMounted) { 923 | // 未挂载就是 init 初始化 924 | const { proxy } = instance; 925 | const subTree = instance.render.call(proxy); // 第一次执行App.js根组件中的render函数,这个函数返回由h创建的vnode 926 | // 保存老的vnode prevSubTree 927 | instance.subTree = subTree; 928 | // console.log("--subTree", subTree); 929 | // vnode -> patch 930 | // vnode -> element -> mountElement 931 | patch(null, subTree, container, instance, anchor); // parentComponent -> instance 932 | // all element -> mount 933 | // $el根节点赋值到当前组件vnode的el上面 934 | instance.vnode.el = subTree.el; 935 | // init完成 936 | instance.isMounted = true; 937 | } 938 | else { 939 | // update 940 | // vnode:更新之前的 next:下次要更新的 941 | const { next, vnode } = instance; 942 | if (next) { 943 | next.el = vnode.el; 944 | updataComponentPreRender(instance, next); 945 | } 946 | const { proxy } = instance; 947 | const subTree = instance.render.call(proxy); 948 | const prevSubTree = instance.subTree; 949 | // 把老的更新,保证下次进入是正确的 950 | instance.subTree = subTree; 951 | patch(prevSubTree, subTree, container, instance, anchor); 952 | } 953 | }, { 954 | scheduler() { 955 | console.log("update-scheduler"); 956 | queueJobs(instance.update); 957 | } 958 | }); 959 | } 960 | // slot 的 Fragment 和 Text 961 | function processFragment(n1, n2, container, parentComponent, anchor) { 962 | mountChildren(n2.children, container, parentComponent, anchor); 963 | } 964 | function processText(n1, n2, container) { 965 | const { children } = n2; 966 | const textNode = (n2.el = document.createTextNode(children)); // 需要赋值给vnode的el 967 | container.append(textNode); 968 | } 969 | // 解决 createRenderer之后,createApp 无法再使用 render 的问题 970 | return { 971 | createApp: createAppAPI(render), 972 | }; 973 | } 974 | function updataComponentPreRender(instance, nextVNode) { 975 | instance.vnode = nextVNode; 976 | instance.next = null; 977 | instance.props = nextVNode.props; 978 | } 979 | // 最长递增子序列算法 980 | function getSequence(arr) { 981 | const p = arr.slice(); 982 | const result = [0]; 983 | let i, j, u, v, c; 984 | const len = arr.length; 985 | for (i = 0; i < len; i++) { 986 | const arrI = arr[i]; 987 | if (arrI !== 0) { 988 | j = result[result.length - 1]; 989 | if (arr[j] < arrI) { 990 | p[i] = j; 991 | result.push(i); 992 | continue; 993 | } 994 | u = 0; 995 | v = result.length - 1; 996 | while (u < v) { 997 | c = (u + v) >> 1; 998 | if (arr[result[c]] < arrI) { 999 | u = c + 1; 1000 | } 1001 | else { 1002 | v = c; 1003 | } 1004 | } 1005 | if (arrI < arr[result[u]]) { 1006 | if (u > 0) { 1007 | p[i] = result[u - 1]; 1008 | } 1009 | result[u] = i; 1010 | } 1011 | } 1012 | } 1013 | u = result.length; 1014 | v = result[u - 1]; 1015 | while (u-- > 0) { 1016 | result[u] = v; 1017 | v = p[v]; 1018 | } 1019 | return result; 1020 | } 1021 | 1022 | function createElement(type) { 1023 | console.log("createElement------------"); 1024 | return document.createElement(type); 1025 | } 1026 | /** 1027 | * @param el 1028 | * @param key 1029 | * @param prevVal 1030 | * @param val -> nextVal 当前的值 1031 | */ 1032 | function patchProp(el, key, prevVal, val) { 1033 | // console.log("patchProp------------"); 1034 | if (isOn(key)) { 1035 | const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写 1036 | el.addEventListener(eventName, val); 1037 | } 1038 | else { 1039 | if (val === undefined || val === null) { 1040 | el.removeAttribute(key); 1041 | } 1042 | else { 1043 | el.setAttribute(key, val); 1044 | } 1045 | } 1046 | } 1047 | function insert(el, parent, anchor) { 1048 | // console.log("insert------------"); 1049 | // parent.append(el); 1050 | parent.insertBefore(el, anchor || null); 1051 | } 1052 | function remove(child) { 1053 | const parent = child.parentNode; 1054 | if (parent) { 1055 | parent.removeChild(child); 1056 | } 1057 | } 1058 | function setElementText(el, text) { 1059 | el.textContent = text; 1060 | } 1061 | const renderer = createRenderer({ 1062 | createElement, 1063 | patchProp, 1064 | insert, 1065 | remove, 1066 | setElementText, 1067 | }); 1068 | function createApp(...args) { 1069 | return renderer.createApp(...args); 1070 | } 1071 | 1072 | exports.createApp = createApp; 1073 | exports.createRenderer = createRenderer; 1074 | exports.createTextVnode = createTextVnode; 1075 | exports.getCurrentInstance = getCurrentInstance; 1076 | exports.h = h; 1077 | exports.inject = inject; 1078 | exports.nextTick = nextTick; 1079 | exports.provide = provide; 1080 | exports.proxyRefs = proxyRefs; 1081 | exports.ref = ref; 1082 | exports.renderSlots = renderSlots; 1083 | -------------------------------------------------------------------------------- /lib/k-mini-vue3.esm.js: -------------------------------------------------------------------------------- 1 | const Fragment = Symbol("Fragment"); 2 | const Text = Symbol("Text"); 3 | function createVNode(type, props, children) { 4 | const vnode = { 5 | type, 6 | props, 7 | children, 8 | component: null, 9 | key: props && props.key, 10 | shapeFlag: getShapeFlag(type), 11 | el: null, // $el 12 | }; 13 | // 重构优化 ShapeFlags 14 | // 判断children类型 15 | if (typeof children === "string") { 16 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 17 | } 18 | else if (Array.isArray(children)) { 19 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 20 | } 21 | // 判断 children 是 slot(是slot的条件: 组件 + children是对象) 22 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 23 | if (typeof children === "object") { 24 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */; 25 | } 26 | } 27 | return vnode; 28 | } 29 | // 重构优化 ShapeFlags 30 | // 判断type类型 31 | function getShapeFlag(type) { 32 | return typeof type === "string" 33 | ? 1 /* ELEMENT */ 34 | : 2 /* STATEFUL_COMPONENT */; 35 | } 36 | function createTextVnode(text) { 37 | return createVNode(Text, {}, text); 38 | } 39 | 40 | function h(type, props, children) { 41 | // childred -> string or array 42 | return createVNode(type, props, children); 43 | } 44 | 45 | function renderSlots(slots, name, props) { 46 | // 非具名 不传name 47 | // if (!name) { 48 | // return createVNode("div", {}, slots); 49 | // } 50 | // 具名插槽 51 | const slot = slots[name]; 52 | if (slot) { 53 | if (typeof slot === "function") { 54 | // 处理作用域插槽 55 | return createVNode(Fragment, {}, slot(props)); 56 | } 57 | // return createVNode("div", {}, slot); 58 | } 59 | } 60 | 61 | const EMPTY_OBJ = {}; 62 | const extend = Object.assign; 63 | function isObject(val) { 64 | return val !== null && typeof val === "object"; 65 | } 66 | const hasChanged = (value, newValue) => { 67 | return !Object.is(value, newValue); 68 | }; 69 | // 事件注册 70 | const isOn = (key) => /^on[A-Z]/.test(key); 71 | // props 72 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 73 | // emit 74 | // 首字母大写 前面加on 75 | // add -> onAdd 76 | const capitalize = (str) => { 77 | return str.charAt(0).toUpperCase() + str.slice(1); 78 | }; 79 | // kebab-case foo-add -> fooAdd 80 | const camelize = (str) => { 81 | return str.replace(/-(\w)/g, (_, c) => { 82 | return c ? c.toUpperCase() : ""; 83 | }); 84 | }; 85 | const toHandlerKey = (str) => { 86 | return str ? "on" + capitalize(str) : ""; 87 | }; 88 | 89 | let activeEffect; 90 | let shouldTrack; 91 | class reactiveEffect { 92 | constructor(fn, scheduler) { 93 | // stop 94 | this.deps = []; 95 | this.active = true; 96 | this._fn = fn; 97 | this.scheduler = scheduler; 98 | } 99 | run() { 100 | if (!this.active) { 101 | // runner方法需要得到fn的返回值 102 | return this._fn(); 103 | } 104 | // 应该收集 105 | shouldTrack = true; 106 | activeEffect = this; 107 | const r = this._fn(); 108 | // reset 109 | shouldTrack = false; 110 | return r; 111 | } 112 | stop() { 113 | if (this.active) { 114 | cleanupEffect(this); 115 | // stop时 调用onStop方法 116 | if (this.onStop) { 117 | this.onStop(); 118 | } 119 | this.active = false; 120 | } 121 | } 122 | } 123 | function cleanupEffect(effect) { 124 | effect.deps.forEach((dep) => { 125 | dep.delete(effect); 126 | }); 127 | effect.deps.length = 0; 128 | } 129 | const targetMap = new Map(); 130 | function track(target, key) { 131 | // target -> key -> dep 132 | // targetMap -> { target: depsMap } 133 | // depsMap -> {key: dep} 134 | // dep -> activeEffect 135 | if (!isTracking()) 136 | return; // 抽离-优化 137 | let depsMap = targetMap.get(target); 138 | if (!depsMap) { 139 | depsMap = new Map(); 140 | targetMap.set(target, depsMap); 141 | } 142 | let dep = depsMap.get(key); 143 | if (!dep) { 144 | dep = new Set(); 145 | depsMap.set(key, dep); 146 | } 147 | effectTracks(dep); 148 | } 149 | function effectTracks(dep) { 150 | if (dep.has(activeEffect)) 151 | return; // 防止重复收集 152 | dep.add(activeEffect); 153 | // track的时候收集dep,stop会用到 154 | activeEffect.deps.push(dep); 155 | } 156 | function isTracking() { 157 | // if (!activeEffect) return; //解决只用reactive时,deps undefined的情况 158 | // if (!shouldTrack) return; //解决stop后还会track的问题 159 | return shouldTrack && activeEffect !== undefined; 160 | } 161 | function trigger(target, key) { 162 | const depsMap = targetMap.get(target); 163 | const dep = depsMap.get(key); 164 | effectTriggers(dep); 165 | } 166 | function effectTriggers(dep) { 167 | for (const effect of dep) { 168 | if (effect.scheduler) { 169 | effect.scheduler(); 170 | } 171 | else { 172 | effect.run(); 173 | } 174 | } 175 | } 176 | function effect(fn, options = {}) { 177 | const _effect = new reactiveEffect(fn, options.scheduler); 178 | _effect.run(); 179 | // _effect.onStop = options.onStop; // 抽离-优化 180 | extend(_effect, options); 181 | // 返回的 runner函数 182 | // run方法用到了this,使用bind处理指针问题 183 | const runner = _effect.run.bind(_effect); 184 | // stop 要用到 _effect上面的方法 185 | runner.effect = _effect; 186 | return runner; 187 | } 188 | 189 | const get = createdGetter(); 190 | const set = createdSetter(); 191 | const readonlyGet = createdGetter(true); 192 | const shallowReadonlyGet = createdGetter(true, true); 193 | function createdGetter(isReadonly = false, shallow = false) { 194 | return function get(target, key) { 195 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 196 | return !isReadonly; 197 | } 198 | else if (key === "__v_isReadonly" /* IS_READONLY */) { 199 | return isReadonly; 200 | } 201 | const res = Reflect.get(target, key); 202 | if (shallow) { 203 | return res; 204 | } 205 | // 对象的嵌套转换 -> 如果是普通对象就转换成reactive或者readonly对象 206 | if (isObject(res)) { 207 | return isReadonly ? readonly(res) : reactive(res); 208 | } 209 | if (!isReadonly) { 210 | // 依赖收集 211 | track(target, key); 212 | } 213 | return res; 214 | }; 215 | } 216 | function createdSetter() { 217 | return function set(target, key, value) { 218 | const res = Reflect.set(target, key, value); 219 | // 触发依赖 220 | trigger(target, key); 221 | return res; 222 | }; 223 | } 224 | const mutableHanders = { 225 | get, 226 | set, 227 | }; 228 | const readonlyHanders = { 229 | get: readonlyGet, 230 | set(target, key) { 231 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target); 232 | return true; 233 | }, 234 | }; 235 | const shallowReadonlyHanders = extend({}, readonlyHanders, { 236 | get: shallowReadonlyGet, 237 | }); 238 | 239 | function reactive(raw) { 240 | return createReactiveObject(raw, mutableHanders); 241 | } 242 | function readonly(raw) { 243 | return createReactiveObject(raw, readonlyHanders); 244 | } 245 | function shallowReadonly(raw) { 246 | return createReactiveObject(raw, shallowReadonlyHanders); 247 | } 248 | function createReactiveObject(target, baseHandles) { 249 | if (!isObject(target)) { 250 | console.warn(`target: ${target} 必须是一个对象`); 251 | } 252 | return new Proxy(target, baseHandles); 253 | } 254 | 255 | // {} -> value -> get set 256 | class RefImpl { 257 | constructor(value) { 258 | this.__v_isRef = true; 259 | this._rawValue = value; 260 | this._value = convert(value); 261 | // value 是对象的话需要转换成 reactive 262 | this.dep = new Set(); 263 | } 264 | get value() { 265 | // 只用到 get value时,track里面的activeEffect.deps 为undefined,解决这个问题 266 | trackRefValue(this); 267 | return this._value; 268 | } 269 | set value(newValue) { 270 | // 新旧值不改变,就不执行 271 | // 使用_rawValue,因为这个值是没有被reactive处理过的,是一个普通obj 272 | // 如果传入的是对象,_value就是被处理过的 proxy对象,hasChanged只能传入普通对象做比较 273 | if (hasChanged(newValue, this._rawValue)) { 274 | // 要先修改value,再触发依赖 275 | this._rawValue = newValue; 276 | this._value = convert(newValue); 277 | effectTriggers(this.dep); 278 | } 279 | } 280 | } 281 | function trackRefValue(ref) { 282 | if (isTracking()) { 283 | effectTracks(ref.dep); 284 | } 285 | } 286 | // 如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换 287 | function convert(value) { 288 | return isObject(value) ? reactive(value) : value; 289 | } 290 | function ref(value) { 291 | return new RefImpl(value); 292 | } 293 | function isRef(ref) { 294 | // 如果传入数值 1 这种参数,ref.__v_isRef会是undefined,所以用!!转换成Boolean 295 | return !!ref.__v_isRef; 296 | } 297 | function unRef(ref) { 298 | return isRef(ref) ? ref.value : ref; 299 | } 300 | function proxyRefs(objectWithRefs) { 301 | return new Proxy(objectWithRefs, { 302 | get(target, key) { 303 | return unRef(Reflect.get(target, key)); 304 | }, 305 | set(target, key, value) { 306 | if (isRef(target[key]) && !isRef(value)) { 307 | return (target[key].value = value); 308 | } 309 | else { 310 | return Reflect.set(target, key, value); 311 | } 312 | }, 313 | }); 314 | } 315 | 316 | function emit(instance, event, ...args) { 317 | console.log("event--", event); 318 | // props里面找 emit 绑定的参数 319 | // emit('add') -> onAdd(){} 320 | const { props } = instance; 321 | /** 322 | * TPP 323 | * 特定行为 -> 通用行为 324 | */ 325 | const handlerName = toHandlerKey(camelize(event)); 326 | const handler = props[handlerName]; 327 | handler && handler(...args); 328 | } 329 | 330 | function initProps(instance, rawProps) { 331 | instance.props = rawProps || {}; // shallowReadonly 时参数必须为对象,如果没有传props, rawProps === undefined 报错 332 | } 333 | 334 | function initSlots(instance, children) { 335 | // 判断是slot的时候才执行函数,组件 && children 是 object 才是 slots 336 | const { shapeFlag } = instance.vnode; 337 | // if (typeof instance.type === "object" && typeof children === "object") { 338 | if (shapeFlag & 16 /* SLOT_CHILDREN */) { 339 | normalizeObjectSlots(instance, children); 340 | } 341 | } 342 | function normalizeObjectSlots(instance, children) { 343 | // children -> array 344 | // instance.slots = Array.isArray(children) ? children : [children]; 345 | console.log(instance); 346 | // 具名插槽 -> children object 347 | const slots = {}; 348 | for (const key in children) { 349 | const value = children[key]; 350 | // slots[key] = normalizeSlotValue(value); 351 | // 作用域插槽 function 352 | slots[key] = (props) => normalizeSlotValue(value(props)); 353 | } 354 | instance.slots = slots; 355 | } 356 | function normalizeSlotValue(value) { 357 | return Array.isArray(value) ? value : [value]; 358 | } 359 | 360 | function createComponentInstance(vnode, parent) { 361 | // console.log("parent", parent); 362 | const component = { 363 | vnode, 364 | type: vnode.type, 365 | setupState: {}, 366 | props: {}, 367 | slots: {}, 368 | provides: parent ? parent.provides : {}, 369 | parent, 370 | isMounted: false, 371 | subTree: {}, 372 | emit: () => { }, 373 | update: null, 374 | next: null, 375 | }; 376 | // 处理emit方法,需要event和instance两个参数,但用户只传一个 add 377 | // 如:emit('add') 378 | // 所以在这里使用bind处理这个问题 379 | component.emit = emit.bind(null, component); 380 | return component; 381 | } 382 | function setupComponent(instance) { 383 | /** 384 | * init 385 | */ 386 | initProps(instance, instance.vnode.props); 387 | initSlots(instance, instance.vnode.children); 388 | setupStatefulComponent(instance); 389 | } 390 | function setupStatefulComponent(instance) { 391 | const Component = instance.type; 392 | // ctx 393 | instance.proxy = new Proxy({}, { 394 | get(target, key) { 395 | const { setupState, props } = instance; 396 | // if (key in setupState) { 397 | // return setupState[key]; 398 | // } 399 | if (hasOwn(setupState, key)) { 400 | return setupState[key]; 401 | } 402 | else if (hasOwn(props, key)) { 403 | // props 404 | return props[key]; 405 | } 406 | // key -> $el 407 | if (key === "$el") { 408 | return instance.vnode.el; 409 | } 410 | // key -> $slot 411 | if (key === "$slot") { 412 | return instance.slots; 413 | } 414 | // key -> $props 415 | if (key === "$props") { 416 | return instance.props; 417 | } 418 | }, 419 | }); 420 | // 拿到 setup 返回值 421 | const { setup } = Component; 422 | if (setup) { 423 | setCurrentInstance(instance); 424 | // 用户可能不写setup 425 | // setup() 可能返回 function 或者 object 426 | // 如果是function 就认为组件返回了render函数 427 | // 如果是object 会把object注入到组件上下文中 428 | const setupResult = setup(shallowReadonly(instance.props), { 429 | emit: instance.emit, 430 | }); 431 | setCurrentInstance(null); 432 | handleSetupResult(instance, setupResult); 433 | } 434 | } 435 | function handleSetupResult(instance, setupResult) { 436 | // TODO function 437 | if (typeof setupResult === "object") { 438 | instance.setupState = proxyRefs(setupResult); 439 | } 440 | finishComponentSetup(instance); 441 | } 442 | function finishComponentSetup(instance) { 443 | const Component = instance.type; 444 | if (Component.render) { 445 | instance.render = Component.render; 446 | } 447 | } 448 | // getCurrentInstance Api 449 | let currentInstance = null; 450 | function setCurrentInstance(instance) { 451 | currentInstance = instance; 452 | } 453 | function getCurrentInstance() { 454 | return currentInstance; 455 | } 456 | 457 | function provide(key, value) { 458 | // 存 459 | const currentInstance = getCurrentInstance(); 460 | if (currentInstance) { 461 | let { provides } = currentInstance; 462 | const parentProvides = currentInstance.parent.provides; 463 | // 判断是初始化的时候才执行 464 | if (provides === parentProvides) { 465 | // 给 provides 指定原型链对象为 父级 466 | provides = currentInstance.provides = Object.create(parentProvides); 467 | } 468 | provides[key] = value; 469 | } 470 | } 471 | function inject(key, defaultValue) { 472 | // 取 473 | const currentInstance = getCurrentInstance(); 474 | if (currentInstance) { 475 | const parentProvides = currentInstance.parent.provides; 476 | if (key in parentProvides) { 477 | return parentProvides[key]; 478 | } 479 | else if (defaultValue) { 480 | // 处理默认值 可能是字符串或函数 481 | if (typeof defaultValue === "function") { 482 | return defaultValue(); 483 | } 484 | return defaultValue; 485 | } 486 | } 487 | } 488 | 489 | function shouldUpdateComponent(prevVNode, nextVNode) { 490 | const { props: prevProps } = prevVNode; 491 | const { props: nextProps } = nextVNode; 492 | for (const key in nextProps) { 493 | if (nextProps[key] !== prevProps[key]) { 494 | return true; 495 | } 496 | } 497 | return false; 498 | } 499 | 500 | // import { render } from "./renderer"; 501 | function createAppAPI(render) { 502 | return function createApp(rootComponent) { 503 | return { 504 | mount(rootContainer) { 505 | /** 506 | * 先将内容解析成 vnode 507 | * component -> vnode 508 | * 后面的所有逻辑都会基于 vnode 进行操作 509 | */ 510 | // rootContainer -> dom 511 | if (typeof rootContainer === "string") { 512 | rootContainer = document.querySelector(rootContainer); 513 | } 514 | const vnode = createVNode(rootComponent); 515 | render(vnode, rootContainer); 516 | }, 517 | }; 518 | }; 519 | } 520 | // export function createApp(rootComponent) { 521 | // return { 522 | // mount(rootContainer) { 523 | // /** 524 | // * 先将内容解析成 vnode 525 | // * component -> vnode 526 | // * 后面的所有逻辑都会基于 vnode 进行操作 527 | // */ 528 | // // rootContainer -> dom 529 | // if (typeof rootContainer === "string") { 530 | // rootContainer = document.querySelector(rootContainer); 531 | // } 532 | // const vnode = createVNode(rootComponent); 533 | // render(vnode, rootContainer); 534 | // }, 535 | // }; 536 | // } 537 | 538 | const queue = []; 539 | let isFlushPending = false; // 优化每次都会创建promise的问题 540 | function queueJobs(job) { 541 | if (!queue.includes(job)) { 542 | queue.push(job); 543 | } 544 | queueFlush(); 545 | } 546 | function nextTick(fn) { 547 | return fn ? Promise.resolve().then(fn) : Promise.resolve(); 548 | } 549 | function queueFlush() { 550 | if (isFlushPending) 551 | return; 552 | isFlushPending = true; 553 | Promise.resolve().then(() => { 554 | isFlushPending = false; 555 | let job; 556 | while ((job = queue.shift())) { 557 | job && job(); 558 | } 559 | }); 560 | } 561 | 562 | function createRenderer(options) { 563 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options; 564 | function render(vnode, container) { 565 | // patch 566 | // console.log("vnode-----", vnode); 567 | patch(null, vnode, container, null, null); // 处理根组件不传 parentComponent 参数 568 | } 569 | // 优化 patch 架构 570 | // n1 -> 老的,如果不存在则是初始化,存在就是更新逻辑 571 | // n2 -> 新的 572 | // function patch(vnode, container, parentComponent) { 573 | function patch(n1, n2, container, parentComponent, anchor) { 574 | /** 575 | * 区分是 element 还是 component 576 | * 判断两种类型 577 | */ 578 | // debugger; 579 | // console.log("patch-------", vnode); 580 | // 使用 shapeFlag 判断类型 581 | const { type, shapeFlag } = n2; 582 | switch (type) { 583 | case Fragment: 584 | processFragment(n1, n2, container, parentComponent, anchor); 585 | break; 586 | case Text: 587 | processText(n1, n2, container); 588 | break; 589 | default: 590 | // shapeFlag & ShapeFlags.STATEFUL_COMPONENT 等同于 typeof vnode.type === "object" 591 | if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 592 | // 处理component 593 | processComponent(n1, n2, container, parentComponent, anchor); 594 | } 595 | else if (shapeFlag & 1 /* ELEMENT */) { 596 | // 处理 element 597 | processElement(n1, n2, container, parentComponent, anchor); 598 | } 599 | break; 600 | } 601 | } 602 | // element 类型 603 | function processElement(n1, n2, container, parentComponent, anchor) { 604 | if (!n1) { 605 | mountElement(n2, container, parentComponent, anchor); 606 | } 607 | else { 608 | patchElement(n1, n2, container, parentComponent, anchor); 609 | } 610 | } 611 | function patchElement(n1, n2, container, parentComponent, anchor) { 612 | // console.log("n1", n1); 613 | // console.log("n2", n2); 614 | // console.log("container", container); 615 | // update props 616 | const oldProps = n1.props || EMPTY_OBJ; 617 | const newProps = n2.props || EMPTY_OBJ; 618 | const el = n1.el; 619 | n2.el = el; // 本轮n2就是下一轮的n1,不赋值的话 下轮n1中就没有el 620 | patchChildren(n1, n2, el, parentComponent, anchor); 621 | patchProps(el, oldProps, newProps); 622 | } 623 | function patchChildren(n1, n2, container, parentComponent, anchor) { 624 | const prevShapeFlag = n1.shapeFlag; 625 | const nextShapeFlag = n2.shapeFlag; 626 | const c1 = n1.children; 627 | const c2 = n2.children; 628 | if (nextShapeFlag & 4 /* TEXT_CHILDREN */) { 629 | // TODO 优化 630 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) { 631 | // array -> text 632 | // 1. 把老的清空 633 | unmountChildren(n1.children); 634 | // 2. 设置text 635 | hostSetElementText(container, c2); 636 | } 637 | else { 638 | // text -> text 639 | // 前后节点不一样才需要改变 640 | if (c1 !== c2) { 641 | hostSetElementText(container, c2); 642 | } 643 | } 644 | } 645 | else { 646 | // text -> array 647 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) { 648 | hostSetElementText(container, ""); 649 | mountChildren(c2, container, parentComponent, anchor); 650 | } 651 | else { 652 | // array diff children 653 | patchKeyedChildren(c1, c2, container, parentComponent, anchor); 654 | } 655 | } 656 | } 657 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) { 658 | // const l2 = c2.length; 659 | let i = 0; 660 | let e1 = c1.length - 1; 661 | let e2 = c2.length - 1; 662 | // debugger 663 | function isSameVNodeType(n1, n2) { 664 | // type 和 key 判断两种 665 | return n1.type === n2.type && n1.key === n2.key; 666 | } 667 | // 1. 左侧对比 i指针->向右移动 668 | while (i <= e1 && i <= e2) { 669 | const n1 = c1[i]; 670 | const n2 = c2[i]; 671 | if (isSameVNodeType(n1, n2)) { 672 | patch(n1, n2, container, parentComponent, parentAnchor); 673 | } 674 | else { 675 | break; 676 | } 677 | i++; 678 | } 679 | // 2. 右侧对比 e1和e2指针->向左移动 680 | while (i <= e1 && i <= e2) { 681 | const n1 = c1[e1]; 682 | const n2 = c2[e2]; 683 | if (isSameVNodeType(n1, n2)) { 684 | patch(n1, n2, container, parentComponent, parentAnchor); 685 | } 686 | else { 687 | break; 688 | } 689 | e1--; 690 | e2--; 691 | } 692 | // 3. 新的比老的长 - 左侧和右侧 创建新的 693 | if (i > e1) { 694 | if (i <= e2) { 695 | // debugger; 696 | const nextPos = e2 + 1; // 锚点位置 697 | const anchor = nextPos < c2.length ? c2[nextPos].el : null; // 判断:左侧对比 -> null 还在后面插入节点 右侧对比 -> 找"A"节点传入 在"A"前面插入 698 | // 多个 child 遍历执行 patch 699 | while (i <= e2) { 700 | patch(null, c2[i], container, parentComponent, anchor); 701 | i++; 702 | } 703 | } 704 | } 705 | // 4. 老的比新的长 - 左侧和右侧 删除老的 706 | else if (i > e2) { 707 | while (i <= e1) { 708 | hostRemove(c1[i].el); 709 | i++; 710 | } 711 | } 712 | // 5. 中间对比 713 | else { 714 | let s1 = i; 715 | let s2 = i; 716 | /** 717 | * 删除的优化: 718 | * 新节点中 对比一次就记录一次;新节点全部对比完成后,如果老节点还有剩余元素的话 719 | * 就可以把这些全部删除 720 | */ 721 | const toBePatched = e2 - s2 + 1; // 新的中需要对比的全部数量 722 | let patched = 0; // 已经对比完成的 723 | /** 724 | * 最长递增子序列 725 | * 移动 726 | * 建立映射表 定长数组 727 | */ 728 | const newIndexToOldIndexMap = new Array(toBePatched); 729 | for (let i = 0; i < toBePatched; i++) 730 | newIndexToOldIndexMap[i] = 0; 731 | /** 732 | * 移动 优化 733 | * 什么时候需要移动 734 | */ 735 | let moved = false; 736 | let maxNewIndexSoFar = 0; 737 | // 基于新的 里面的 key 建立 映射表 738 | const keyToNewIndexMap = new Map(); 739 | for (let i = s2; i <= e2; i++) { 740 | const nextChild = c2[i]; 741 | keyToNewIndexMap.set(nextChild.key, i); 742 | } 743 | // 遍历老的 744 | for (let i = s1; i <= e1; i++) { 745 | // 拿到当前节点 746 | const prevChild = c1[i]; 747 | if (patched > toBePatched) { 748 | hostRemove(prevChild.el); 749 | continue; 750 | } 751 | let newIndex; 752 | // 根据 null 和 undefined 判断用户写没写key 753 | if (prevChild.key != null) { 754 | newIndex = keyToNewIndexMap.get(prevChild.key); 755 | } 756 | else { 757 | // 如果没有 key 就需要遍历判断 性能低 758 | for (let j = s2; j <= e2; j++) { 759 | if (isSameVNodeType(prevChild, c2[j])) { 760 | newIndex = j; 761 | break; 762 | } 763 | } 764 | } 765 | // 如果newIndex存在,就说明 新老里面都有该节点 766 | // 不存在 就删除老的 767 | if (newIndex === undefined) { 768 | hostRemove(prevChild.el); 769 | } 770 | else { 771 | if (newIndex >= maxNewIndexSoFar) { 772 | maxNewIndexSoFar = newIndex; 773 | } 774 | else { 775 | moved = true; 776 | } 777 | /** 778 | * 为什么是i+1,而不是i? 779 | * 因为i 可能是0,而0在这里有特殊含义,因为初始化赋的值就是0,需要用0判断是否需要创建元素 780 | * 所以就赋值为i+1 避免这个问题 781 | */ 782 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 783 | patch(prevChild, c2[newIndex], container, parentComponent, null); 784 | patched++; 785 | } 786 | } 787 | // 生成最长递增子序列 788 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; 789 | let j = increasingNewIndexSequence.length - 1; // 生成的子序列的下标 790 | // 从后往前遍历,因为 insertBefore插入元素需要在一个稳定元素的前面插入 791 | for (let i = toBePatched - 1; i >= 0; i--) { 792 | const nextIndex = i + s2; 793 | const nextChild = c2[nextIndex]; 794 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null; 795 | /** 796 | * 创建 797 | */ 798 | if (newIndexToOldIndexMap[i] === 0) { 799 | // 在老的里面没有需要创建 800 | patch(null, nextChild, container, parentComponent, anchor); 801 | } 802 | /** 803 | * 移动 804 | */ 805 | else if (moved) { 806 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 807 | // 移动位置 808 | hostInsert(nextChild.el, container, anchor); 809 | } 810 | else { 811 | j--; 812 | } 813 | } 814 | } 815 | } 816 | } 817 | function unmountChildren(children) { 818 | for (let i = 0; i < children.length; i++) { 819 | const el = children[i].el; 820 | // remove 821 | hostRemove(el); 822 | } 823 | } 824 | function patchProps(el, oldProps, newProps) { 825 | if (oldProps !== newProps) { 826 | // 健壮性 827 | for (const key in newProps) { 828 | const prevProp = oldProps[key]; 829 | const nextProp = newProps[key]; 830 | if (prevProp !== nextProp) { 831 | hostPatchProp(el, key, prevProp, nextProp); 832 | } 833 | } 834 | if (oldProps !== EMPTY_OBJ) { 835 | // 健壮性 836 | // update props 的第三种情况:属性被删除 837 | for (const key in oldProps) { 838 | if (!(key in newProps)) { 839 | hostPatchProp(el, key, oldProps[key], null); 840 | } 841 | } 842 | } 843 | } 844 | } 845 | function mountElement(vnode, container, parentComponent, anchor) { 846 | const { type, children, props } = vnode; 847 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 848 | // const el = document.createElement(type); 849 | const el = hostCreateElement(type); 850 | // $el 851 | // vnode -> element -> div 852 | vnode.el = el; 853 | // children -> string or array 854 | // 使用 shapeFlag 判断类型 855 | const { shapeFlag } = vnode; 856 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 857 | // children is string 858 | el.textContent = children; 859 | } 860 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 861 | // children is array 862 | mountChildren(vnode.children, el, parentComponent, anchor); // 抽离-优化 863 | } 864 | // props 865 | for (const key in props) { 866 | const val = props[key]; 867 | // console.log(key); 868 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 869 | // if (isOn(key)) { 870 | // const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写 871 | // el.addEventListener(eventName, val); 872 | // } else { 873 | // el.setAttribute(key, val); 874 | // } 875 | hostPatchProp(el, key, null, val); 876 | } 877 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 878 | // container.append(el); 879 | hostInsert(el, container, anchor); 880 | } 881 | function mountChildren(children, el, parentComponent, anchor) { 882 | children.forEach((v) => { 883 | patch(null, v, el, parentComponent, anchor); 884 | }); 885 | } 886 | // component 类型 887 | function processComponent(n1, n2, container, parentComponent, anchor) { 888 | if (!n1) { 889 | mountComponent(n2, container, parentComponent, anchor); 890 | } 891 | else { 892 | updateComponent(n1, n2); 893 | } 894 | } 895 | function updateComponent(n1, n2) { 896 | const instance = (n2.component = n1.component); 897 | // 判断是否需要更新 898 | if (shouldUpdateComponent(n1, n2)) { 899 | // 新的vnode保存起来 下次要更新的 900 | instance.next = n2; 901 | instance.update(); 902 | } 903 | else { 904 | n2.el = n1.el; 905 | instance.vnode = n2; 906 | } 907 | } 908 | function mountComponent(initialVNode, container, parentComponent, anchor) { 909 | // 创建组件实例 910 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)); 911 | setupComponent(instance); 912 | setupRenderEffect(instance, container, anchor); 913 | } 914 | function setupRenderEffect(instance, container, anchor) { 915 | // 使用effect包裹原来的逻辑,收集依赖 916 | instance.update = effect(() => { 917 | console.log("setupRenderEffect"); 918 | if (!instance.isMounted) { 919 | // 未挂载就是 init 初始化 920 | const { proxy } = instance; 921 | const subTree = instance.render.call(proxy); // 第一次执行App.js根组件中的render函数,这个函数返回由h创建的vnode 922 | // 保存老的vnode prevSubTree 923 | instance.subTree = subTree; 924 | // console.log("--subTree", subTree); 925 | // vnode -> patch 926 | // vnode -> element -> mountElement 927 | patch(null, subTree, container, instance, anchor); // parentComponent -> instance 928 | // all element -> mount 929 | // $el根节点赋值到当前组件vnode的el上面 930 | instance.vnode.el = subTree.el; 931 | // init完成 932 | instance.isMounted = true; 933 | } 934 | else { 935 | // update 936 | // vnode:更新之前的 next:下次要更新的 937 | const { next, vnode } = instance; 938 | if (next) { 939 | next.el = vnode.el; 940 | updataComponentPreRender(instance, next); 941 | } 942 | const { proxy } = instance; 943 | const subTree = instance.render.call(proxy); 944 | const prevSubTree = instance.subTree; 945 | // 把老的更新,保证下次进入是正确的 946 | instance.subTree = subTree; 947 | patch(prevSubTree, subTree, container, instance, anchor); 948 | } 949 | }, { 950 | scheduler() { 951 | console.log("update-scheduler"); 952 | queueJobs(instance.update); 953 | } 954 | }); 955 | } 956 | // slot 的 Fragment 和 Text 957 | function processFragment(n1, n2, container, parentComponent, anchor) { 958 | mountChildren(n2.children, container, parentComponent, anchor); 959 | } 960 | function processText(n1, n2, container) { 961 | const { children } = n2; 962 | const textNode = (n2.el = document.createTextNode(children)); // 需要赋值给vnode的el 963 | container.append(textNode); 964 | } 965 | // 解决 createRenderer之后,createApp 无法再使用 render 的问题 966 | return { 967 | createApp: createAppAPI(render), 968 | }; 969 | } 970 | function updataComponentPreRender(instance, nextVNode) { 971 | instance.vnode = nextVNode; 972 | instance.next = null; 973 | instance.props = nextVNode.props; 974 | } 975 | // 最长递增子序列算法 976 | function getSequence(arr) { 977 | const p = arr.slice(); 978 | const result = [0]; 979 | let i, j, u, v, c; 980 | const len = arr.length; 981 | for (i = 0; i < len; i++) { 982 | const arrI = arr[i]; 983 | if (arrI !== 0) { 984 | j = result[result.length - 1]; 985 | if (arr[j] < arrI) { 986 | p[i] = j; 987 | result.push(i); 988 | continue; 989 | } 990 | u = 0; 991 | v = result.length - 1; 992 | while (u < v) { 993 | c = (u + v) >> 1; 994 | if (arr[result[c]] < arrI) { 995 | u = c + 1; 996 | } 997 | else { 998 | v = c; 999 | } 1000 | } 1001 | if (arrI < arr[result[u]]) { 1002 | if (u > 0) { 1003 | p[i] = result[u - 1]; 1004 | } 1005 | result[u] = i; 1006 | } 1007 | } 1008 | } 1009 | u = result.length; 1010 | v = result[u - 1]; 1011 | while (u-- > 0) { 1012 | result[u] = v; 1013 | v = p[v]; 1014 | } 1015 | return result; 1016 | } 1017 | 1018 | function createElement(type) { 1019 | console.log("createElement------------"); 1020 | return document.createElement(type); 1021 | } 1022 | /** 1023 | * @param el 1024 | * @param key 1025 | * @param prevVal 1026 | * @param val -> nextVal 当前的值 1027 | */ 1028 | function patchProp(el, key, prevVal, val) { 1029 | // console.log("patchProp------------"); 1030 | if (isOn(key)) { 1031 | const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写 1032 | el.addEventListener(eventName, val); 1033 | } 1034 | else { 1035 | if (val === undefined || val === null) { 1036 | el.removeAttribute(key); 1037 | } 1038 | else { 1039 | el.setAttribute(key, val); 1040 | } 1041 | } 1042 | } 1043 | function insert(el, parent, anchor) { 1044 | // console.log("insert------------"); 1045 | // parent.append(el); 1046 | parent.insertBefore(el, anchor || null); 1047 | } 1048 | function remove(child) { 1049 | const parent = child.parentNode; 1050 | if (parent) { 1051 | parent.removeChild(child); 1052 | } 1053 | } 1054 | function setElementText(el, text) { 1055 | el.textContent = text; 1056 | } 1057 | const renderer = createRenderer({ 1058 | createElement, 1059 | patchProp, 1060 | insert, 1061 | remove, 1062 | setElementText, 1063 | }); 1064 | function createApp(...args) { 1065 | return renderer.createApp(...args); 1066 | } 1067 | 1068 | export { createApp, createRenderer, createTextVnode, getCurrentInstance, h, inject, nextTick, provide, proxyRefs, ref, renderSlots }; 1069 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue3", 3 | "version": "1.0.0", 4 | "main": "lib/k-mini-vue3.cjs.js", 5 | "module": "lib/k-mini-vue3.esm.js", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "rollup -c rollup.config.js" 9 | }, 10 | "repository": "https://github.com/kongcodes/mini-vue3.git", 11 | "author": "kongcodes", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@babel/core": "^7.17.5", 15 | "@babel/preset-env": "^7.16.11", 16 | "@babel/preset-typescript": "^7.16.7", 17 | "@rollup/plugin-typescript": "^8.3.1", 18 | "@types/jest": "^27.4.1", 19 | "babel-jest": "^27.5.1", 20 | "jest": "^27.5.1", 21 | "rollup": "^2.70.1", 22 | "tslib": "^2.3.1", 23 | "typescript": "^4.6.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import pkg from "./package.json"; 3 | 4 | export default { 5 | input: "./src/index.ts", 6 | output: [ 7 | // 1. cjs -> commonjs 8 | // 2. esm 9 | { 10 | format: "cjs", 11 | file: pkg.main, 12 | }, 13 | { 14 | format: "es", 15 | file: pkg.module, 16 | }, 17 | ], 18 | plugins: [typescript()], 19 | }; 20 | -------------------------------------------------------------------------------- /src/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | export const enum NodeTypes { 2 | INTERPOLATION, 3 | SIMPLE_EXPRESSION, 4 | ELEMENT, 5 | TEXT 6 | } 7 | export const enum TagTypes { 8 | START, 9 | END 10 | } -------------------------------------------------------------------------------- /src/compiler-core/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes, TagTypes } from "./ast"; 2 | 3 | export function baseParse(content: string) { 4 | 5 | const context = createParserContext(content); 6 | return createRoot(parseChildren(context, [])); // 初始时 ancestors 栈为空 7 | } 8 | 9 | function parseChildren(context, ancestors) { 10 | const nodes: any = []; 11 | 12 | // 重复执行,没有结束的时候就一直重复执行 13 | while(!isEnd(context, ancestors)){ 14 | let node; 15 | const s = context.source; 16 | if (s.startsWith("{{")) { 17 | node = parseInterpolation(context); 18 | } else if (s[0] === "<") { 19 | if (/[a-z]/i.test(s[1])) { 20 | node = parseElement(context, ancestors); 21 | } 22 | } 23 | 24 | // 解析 Text 25 | if(!node){ 26 | node = parseText(context); 27 | } 28 | 29 | nodes.push(node); 30 | } 31 | 32 | return nodes; 33 | } 34 | 35 | function parseText(context: any){ 36 | 37 | let endIndex = context.source.length; 38 | // let endToken = "{{"; 39 | // 处理 edge case 1 40 | let endTokens = ["{{", "<"]; 41 | for (let i = 0; i < endTokens.length; i++) { 42 | const index = context.source.indexOf(endTokens[i]); 43 | // 让 index 尽可能的小,靠左 44 | if (index !== -1 && endIndex > index) { 45 | endIndex = index; 46 | } 47 | } 48 | // 获取 content 49 | const content = parseTextData(context, endIndex); 50 | console.log("content -----------", content); 51 | 52 | return { 53 | type: NodeTypes.TEXT, 54 | content 55 | } 56 | } 57 | 58 | function parseElement(context: any, ancestors) { 59 | // 1. 解析两个 tag 60 | const element: any = parseTag(context, TagTypes.START); 61 | // 此处把 tag 存入栈中,下面的 parseChildren 会递归执行 parseElement,遇到 tag 再次存进来 62 | ancestors.push(element); 63 | // 2. 解析 tag 之间的 children 64 | element.children = parseChildren(context, ancestors); 65 | ancestors.pop(); // children 执行完成之后 66 | 67 | console.log(context.source); // 68 | console.log(element.tag); // span 69 | // 相等的时候 才能处理 70 | if (startsWithEndTagOpen(context.source, element.tag)) { 71 | parseTag(context, TagTypes.END); 72 | } else { 73 | throw new Error(`缺少结束标签:${element.tag}`); 74 | } 75 | 76 | return element; 77 | } 78 | 79 | function parseTag(context: any, type: TagTypes) { 80 | console.log(context); 81 | // 1.解析 tag 82 | const match: any = /^<\/?([a-z]*)/i.exec(context.source); // div 84 | 85 | // 2.删除处理过的内容 86 | advanceBy(context, match[0].length); 87 | // console.log(context.source); // ">" 88 | advanceBy(context, 1); // "" 89 | 90 | // 处理结束标签时不需要返回值 91 | if (type === TagTypes.END) return; 92 | 93 | return { 94 | type: NodeTypes.ELEMENT, 95 | tag, 96 | }; 97 | } 98 | 99 | function parseInterpolation(context) { 100 | // 解析 插值 {{message}} 101 | const openDelimiter = "{{"; 102 | const closeDelimiter = "}}"; 103 | 104 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length); 105 | advanceBy(context, openDelimiter.length); // -> message}} 106 | const rawContentLength = closeIndex - openDelimiter.length; 107 | const rawContent = parseTextData(context, rawContentLength); 108 | const content = rawContent.trim(); 109 | // console.log(context.source); 110 | // console.log(content); 111 | 112 | advanceBy(context, closeDelimiter.length); // -> message 113 | 114 | return { 115 | type: NodeTypes.INTERPOLATION, 116 | content: { 117 | type: NodeTypes.SIMPLE_EXPRESSION, 118 | content, 119 | }, 120 | } 121 | } 122 | 123 | function createParserContext(content: string) { 124 | return { 125 | source: content, 126 | }; 127 | } 128 | 129 | function createRoot(children) { 130 | return { 131 | children 132 | }; 133 | } 134 | 135 | // util 136 | function parseTextData(context: any, length) { 137 | // 1. 获取content 138 | const content = context.source.slice(0, length); 139 | // 2. 推进 140 | advanceBy(context, length); 141 | // console.log(context.source); // 空 142 | return content; 143 | } 144 | function advanceBy(context: any, length: number) { 145 | context.source = context.source.slice(length); 146 | } 147 | // 判断结束 148 | function isEnd(context, ancestors) { 149 | // 2. 遇到结束标签的时候 150 | const s = context.source; 151 | // if (parentTag && s.startsWith(``)){ // 初始化时 parentTag 为空,不需要走此流程 152 | // return true; 153 | // } 154 | // 155 | if(s.startsWith("= 0; i--){ 159 | const tag = ancestors[i].tag; 160 | if(startsWithEndTagOpen(s, tag)){ 161 | return true; 162 | } 163 | } 164 | } 165 | // 1. source 有值的时候,返回 false 不结束 166 | return !s; 167 | } 168 | function startsWithEndTagOpen(source, tag) { 169 | // return source.slice(2, 2 + tag.length) === tag; 170 | return source.startsWith(" { 5 | 6 | describe("interpolation", () => { 7 | it("simple interpolation", () => { 8 | const ast = baseParse("{{ message }}"); 9 | 10 | expect(ast.children[0]).toStrictEqual({ 11 | type: NodeTypes.INTERPOLATION, 12 | content: { 13 | type: NodeTypes.SIMPLE_EXPRESSION, 14 | content: "message", 15 | }, 16 | }); 17 | }); 18 | }); 19 | describe("simple element div", () => { 20 | it("simple ele", () => { 21 | const ast = baseParse("
"); 22 | 23 | expect(ast.children[0]).toStrictEqual({ 24 | type: NodeTypes.ELEMENT, 25 | tag: "div", 26 | children: [] 27 | }); 28 | }); 29 | }); 30 | 31 | describe("text", () => { 32 | it("simple text", () => { 33 | const ast = baseParse("some text"); 34 | 35 | expect(ast.children[0]).toStrictEqual({ 36 | type: NodeTypes.TEXT, 37 | content: "some text" 38 | }) 39 | }) 40 | }) 41 | 42 | /** 43 | * 解析三种联合类型 插值 ele text 44 | *
hi, {{message}}
45 | */ 46 | test('hello world', () => { 47 | const ast = baseParse("
hi,{{message}}
"); // 需要注意
hi, 前后的空格可能导致测试不通过 48 | 49 | expect(ast.children[0]).toStrictEqual({ 50 | type: NodeTypes.ELEMENT, 51 | tag: "div", 52 | children: [ 53 | { 54 | type: NodeTypes.TEXT, 55 | content: "hi," 56 | }, 57 | { 58 | type: NodeTypes.INTERPOLATION, 59 | content: { 60 | type: NodeTypes.SIMPLE_EXPRESSION, 61 | content: "message", 62 | }, 63 | } 64 | ] 65 | }) 66 | }); 67 | 68 | // 三种联合类型的 edge case 1 69 | test('Nested element', () => { 70 | const ast = baseParse("

hi,

{{message}}
"); 71 | 72 | expect(ast.children[0]).toStrictEqual({ 73 | type: NodeTypes.ELEMENT, 74 | tag: "div", 75 | children: [ 76 | { 77 | type: NodeTypes.ELEMENT, 78 | tag: "p", 79 | children: [ 80 | { 81 | type: NodeTypes.TEXT, 82 | content: "hi," 83 | } 84 | ] 85 | }, 86 | { 87 | type: NodeTypes.INTERPOLATION, 88 | content: { 89 | type: NodeTypes.SIMPLE_EXPRESSION, 90 | content: "message", 91 | }, 92 | } 93 | ] 94 | }) 95 | }); 96 | 97 | // edge case 2 处理没有结束标签的情况 98 | test('should throw error when lack end tag', () => { 99 | // baseParse("
"); 100 | expect(() => { 101 | baseParse("
"); 102 | }).toThrow(`缺少结束标签:span`); 103 | }); 104 | 105 | }); 106 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./runtime-dom"; 2 | export * from "./reactivity"; 3 | -------------------------------------------------------------------------------- /src/reactivity/baseHandles.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from "../shared"; 2 | import { track, trigger } from "./effect"; 3 | import { reactive, ReactiveFlags, readonly } from "./reactive"; 4 | 5 | const get = createdGetter(); 6 | const set = createdSetter(); 7 | const readonlyGet = createdGetter(true); 8 | const shallowReadonlyGet = createdGetter(true, true); 9 | 10 | function createdGetter(isReadonly = false, shallow = false) { 11 | return function get(target, key) { 12 | if (key === ReactiveFlags.IS_REACTIVE) { 13 | return !isReadonly; 14 | } else if (key === ReactiveFlags.IS_READONLY) { 15 | return isReadonly; 16 | } 17 | 18 | const res = Reflect.get(target, key); 19 | 20 | if (shallow) { 21 | return res; 22 | } 23 | 24 | // 对象的嵌套转换 -> 如果是普通对象就转换成reactive或者readonly对象 25 | if (isObject(res)) { 26 | return isReadonly ? readonly(res) : reactive(res); 27 | } 28 | 29 | if (!isReadonly) { 30 | // 依赖收集 31 | track(target, key); 32 | } 33 | return res; 34 | }; 35 | } 36 | 37 | function createdSetter() { 38 | return function set(target, key, value) { 39 | const res = Reflect.set(target, key, value); 40 | 41 | // 触发依赖 42 | trigger(target, key); 43 | return res; 44 | }; 45 | } 46 | 47 | export const mutableHanders = { 48 | get, 49 | set, 50 | }; 51 | 52 | export const readonlyHanders = { 53 | get: readonlyGet, 54 | set(target, key) { 55 | console.warn( 56 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, 57 | target 58 | ); 59 | return true; 60 | }, 61 | }; 62 | 63 | export const shallowReadonlyHanders = extend({}, readonlyHanders, { 64 | get: shallowReadonlyGet, 65 | }); 66 | -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { reactiveEffect } from "./effect"; 2 | 3 | class ComputedRefImpl { 4 | private _getter: any; 5 | private _dirty: boolean = true; 6 | private _value: any; 7 | private _effect: reactiveEffect; 8 | constructor(getter) { 9 | this._getter = getter; 10 | // effect & scheduler 11 | this._effect = new reactiveEffect(getter, () => { 12 | if (!this._dirty) { 13 | // 当依赖的响应式对象的值发生改变的时候 需要 _dirty = true,才能返回一个新的值 14 | this._dirty = true; 15 | } 16 | }); 17 | } 18 | 19 | get value() { 20 | // get 21 | if (this._dirty) { 22 | // 怎么知道变化了,需要使用effect 23 | this._dirty = false; 24 | this._value = this._effect.run(); 25 | } 26 | return this._value; 27 | } 28 | } 29 | 30 | export function computed(getter) { 31 | return new ComputedRefImpl(getter); 32 | } 33 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "../shared"; 2 | 3 | let activeEffect; 4 | let shouldTrack; 5 | 6 | export class reactiveEffect { 7 | private _fn: any; 8 | public scheduler: Function | undefined; 9 | // stop 10 | deps = []; 11 | active = true; 12 | onStop?: () => void; 13 | 14 | constructor(fn, scheduler?: Function) { 15 | this._fn = fn; 16 | this.scheduler = scheduler; 17 | } 18 | 19 | run() { 20 | if (!this.active) { 21 | // runner方法需要得到fn的返回值 22 | return this._fn(); 23 | } 24 | 25 | // 应该收集 26 | shouldTrack = true; 27 | activeEffect = this; 28 | 29 | const r = this._fn(); 30 | // reset 31 | shouldTrack = false; 32 | return r; 33 | } 34 | 35 | stop() { 36 | if (this.active) { 37 | cleanupEffect(this); 38 | // stop时 调用onStop方法 39 | if (this.onStop) { 40 | this.onStop(); 41 | } 42 | this.active = false; 43 | } 44 | } 45 | } 46 | 47 | function cleanupEffect(effect) { 48 | effect.deps.forEach((dep: any) => { 49 | dep.delete(effect); 50 | }); 51 | effect.deps.length = 0; 52 | } 53 | 54 | const targetMap = new Map(); 55 | export function track(target, key) { 56 | // target -> key -> dep 57 | // targetMap -> { target: depsMap } 58 | // depsMap -> {key: dep} 59 | // dep -> activeEffect 60 | 61 | if (!isTracking()) return; // 抽离-优化 62 | 63 | let depsMap = targetMap.get(target); 64 | if (!depsMap) { 65 | depsMap = new Map(); 66 | targetMap.set(target, depsMap); 67 | } 68 | 69 | let dep = depsMap.get(key); 70 | if (!dep) { 71 | dep = new Set(); 72 | depsMap.set(key, dep); 73 | } 74 | effectTracks(dep); 75 | } 76 | 77 | export function effectTracks(dep) { 78 | if (dep.has(activeEffect)) return; // 防止重复收集 79 | dep.add(activeEffect); 80 | // track的时候收集dep,stop会用到 81 | activeEffect.deps.push(dep); 82 | } 83 | 84 | export function isTracking() { 85 | // if (!activeEffect) return; //解决只用reactive时,deps undefined的情况 86 | // if (!shouldTrack) return; //解决stop后还会track的问题 87 | return shouldTrack && activeEffect !== undefined; 88 | } 89 | 90 | export function trigger(target, key) { 91 | const depsMap = targetMap.get(target); 92 | const dep = depsMap.get(key); 93 | effectTriggers(dep); 94 | } 95 | 96 | export function effectTriggers(dep) { 97 | for (const effect of dep) { 98 | if (effect.scheduler) { 99 | effect.scheduler(); 100 | } else { 101 | effect.run(); 102 | } 103 | } 104 | } 105 | 106 | export function effect(fn, options: any = {}) { 107 | const _effect = new reactiveEffect(fn, options.scheduler); 108 | 109 | _effect.run(); 110 | 111 | // _effect.onStop = options.onStop; // 抽离-优化 112 | extend(_effect, options); 113 | 114 | // 返回的 runner函数 115 | // run方法用到了this,使用bind处理指针问题 116 | const runner: any = _effect.run.bind(_effect); 117 | // stop 要用到 _effect上面的方法 118 | runner.effect = _effect; 119 | return runner; 120 | } 121 | 122 | export function stop(runner) { 123 | runner.effect.stop(); 124 | } 125 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export { ref, proxyRefs } from "./ref"; 2 | -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../shared"; 2 | import { 3 | mutableHanders, 4 | readonlyHanders, 5 | shallowReadonlyHanders, 6 | } from "./baseHandles"; 7 | 8 | export const enum ReactiveFlags { 9 | IS_REACTIVE = "__v_isReactive", 10 | IS_READONLY = "__v_isReadonly", 11 | } 12 | 13 | export function reactive(raw) { 14 | return createReactiveObject(raw, mutableHanders); 15 | } 16 | 17 | export function readonly(raw) { 18 | return createReactiveObject(raw, readonlyHanders); 19 | } 20 | 21 | export function isReactive(value) { 22 | return !!value[ReactiveFlags.IS_REACTIVE]; 23 | } 24 | 25 | export function isReadonly(value) { 26 | return !!value[ReactiveFlags.IS_READONLY]; 27 | } 28 | 29 | export function isProxy(value) { 30 | return isReactive(value) || isReadonly(value); 31 | } 32 | 33 | export function shallowReadonly(raw) { 34 | return createReactiveObject(raw, shallowReadonlyHanders); 35 | } 36 | 37 | function createReactiveObject(target, baseHandles) { 38 | if (!isObject(target)) { 39 | console.warn(`target: ${target} 必须是一个对象`); 40 | } 41 | return new Proxy(target, baseHandles); 42 | } 43 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from "../shared"; 2 | import { effectTracks, effectTriggers, isTracking } from "./effect"; 3 | import { reactive } from "./reactive"; 4 | 5 | // {} -> value -> get set 6 | class RefImpl { 7 | private _value: any; 8 | public dep: any; 9 | private _rawValue: any; // 备份原始值 10 | public __v_isRef = true; 11 | constructor(value) { 12 | this._rawValue = value; 13 | this._value = convert(value); 14 | // value 是对象的话需要转换成 reactive 15 | this.dep = new Set(); 16 | } 17 | get value() { 18 | // 只用到 get value时,track里面的activeEffect.deps 为undefined,解决这个问题 19 | trackRefValue(this); 20 | return this._value; 21 | } 22 | set value(newValue) { 23 | // 新旧值不改变,就不执行 24 | // 使用_rawValue,因为这个值是没有被reactive处理过的,是一个普通obj 25 | // 如果传入的是对象,_value就是被处理过的 proxy对象,hasChanged只能传入普通对象做比较 26 | if (hasChanged(newValue, this._rawValue)) { 27 | // 要先修改value,再触发依赖 28 | this._rawValue = newValue; 29 | this._value = convert(newValue); 30 | effectTriggers(this.dep); 31 | } 32 | } 33 | } 34 | 35 | function trackRefValue(ref) { 36 | if (isTracking()) { 37 | effectTracks(ref.dep); 38 | } 39 | } 40 | 41 | // 如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换 42 | function convert(value) { 43 | return isObject(value) ? reactive(value) : value; 44 | } 45 | 46 | export function ref(value) { 47 | return new RefImpl(value); 48 | } 49 | 50 | export function isRef(ref) { 51 | // 如果传入数值 1 这种参数,ref.__v_isRef会是undefined,所以用!!转换成Boolean 52 | return !!ref.__v_isRef; 53 | } 54 | 55 | export function unRef(ref) { 56 | return isRef(ref) ? ref.value : ref; 57 | } 58 | 59 | export function proxyRefs(objectWithRefs) { 60 | return new Proxy(objectWithRefs, { 61 | get(target, key) { 62 | return unRef(Reflect.get(target, key)); 63 | }, 64 | set(target, key, value) { 65 | if (isRef(target[key]) && !isRef(value)) { 66 | return (target[key].value = value); 67 | } else { 68 | return Reflect.set(target, key, value); 69 | } 70 | }, 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /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 | // 返回 用.value 7 | // 缓存 8 | const user = reactive({ 9 | age: 1, 10 | }); 11 | const age = computed(() => { 12 | return user.age; 13 | }); 14 | expect(age.value).toBe(1); 15 | }); 16 | 17 | it("should compute lazily", () => { 18 | const value = reactive({ 19 | foo: 1, 20 | }); 21 | const getter = jest.fn(() => { 22 | return value.foo; 23 | }); 24 | const cValue = computed(getter); 25 | 26 | // lazy 27 | expect(getter).not.toHaveBeenCalled(); 28 | 29 | expect(cValue.value).toBe(1); 30 | expect(getter).toHaveBeenCalledTimes(1); 31 | 32 | // should not compute again 33 | cValue.value; 34 | expect(getter).toHaveBeenCalledTimes(1); 35 | expect(cValue.value).toBe(1); 36 | 37 | // should not compute until needed 38 | value.foo = 2; // 触发trigger -> 此处需要用effect手动收集 39 | expect(getter).toHaveBeenCalledTimes(1); // 期望foo的值成功改变,还不允许getter被执行。所以在computed.ts中,用effect->scheduler完成 40 | 41 | // now should call computed 42 | expect(cValue.value).toBe(2); 43 | expect(getter).toHaveBeenCalledTimes(2); 44 | 45 | // should not compute again 46 | cValue.value; 47 | expect(getter).toHaveBeenCalledTimes(2); // 响应式值不改变,只用到了get,就不会调用函数,还是2次 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect, stop } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | 4 | describe("effect", () => { 5 | it("happy path", () => { 6 | const obj = reactive({ num: 10 }); 7 | let addnum; 8 | effect(() => { 9 | addnum = obj.num + 1; 10 | }); 11 | expect(addnum).toBe(11); 12 | 13 | // update 14 | obj.num++; 15 | expect(addnum).toBe(12); 16 | }); 17 | 18 | it("runner", () => { 19 | // effect(fn) -> runner -> const r = runner() -> fn() & r 20 | let foo = 10; 21 | const runner = effect(() => { 22 | foo++; 23 | return "foo"; 24 | }); 25 | expect(foo).toBe(11); 26 | const r = runner(); 27 | expect(foo).toBe(12); 28 | expect(r).toBe("foo"); 29 | }); 30 | 31 | it("scheduler", () => { 32 | // 1.给effect(fn)传入第二个参数 options -> { scheduler: fn2},也就是名为 scheduler 的一个函数 33 | // 2.effect 首次还会执行fn,不会执行fn2 34 | // 3.但如果scheduler存在,则响应式set updata 的时候不再执行fn而是执行fn2 35 | // 4.执行runner的时候会再次执行fn 36 | let dummy = 1; 37 | let run: any; 38 | const obj = reactive({ num: 1 }); 39 | const scheduler = jest.fn(() => { 40 | run = runner; 41 | }); 42 | const runner = effect( 43 | () => { 44 | dummy = obj.num; 45 | }, 46 | { scheduler } 47 | ); 48 | // first -> be called fn, not call scheduler 49 | expect(scheduler).not.toHaveBeenCalled(); 50 | expect(dummy).toBe(1); 51 | // should be called on first trigger 52 | obj.num++; 53 | expect(scheduler).toHaveBeenCalledTimes(1); 54 | // not call fn 55 | expect(dummy).toBe(1); 56 | // manually run -> fn() 57 | run(); 58 | expect(dummy).toBe(2); 59 | }); 60 | 61 | it("stop", () => { 62 | let dummy; 63 | const obj = reactive({ num: 1 }); 64 | const runner = effect(() => { 65 | dummy = obj.num; 66 | }); 67 | expect(dummy).toBe(1); 68 | obj.num = 2; 69 | expect(dummy).toBe(2); 70 | stop(runner); 71 | // obj.num = 3; 72 | obj.num++; // -> obj.num = obj.num + 1; 73 | expect(dummy).toBe(2); 74 | 75 | // stopped effect should still be manually called callable 76 | runner(); 77 | expect(dummy).toBe(3); 78 | }); 79 | 80 | it("onStop", () => { 81 | // 执行 stop 方法后,会调用onStop函数,和 scheduler 相似 82 | const obj = reactive({ foo: 1 }); 83 | const onStop = jest.fn(); 84 | let dummy; 85 | const runner = effect( 86 | () => { 87 | dummy = obj.foo; 88 | }, 89 | { onStop } 90 | ); 91 | 92 | stop(runner); 93 | expect(onStop).toBeCalledTimes(1); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive, isReactive, isProxy } from "../reactive"; 2 | 3 | describe("reactive", () => { 4 | it("happy path", () => { 5 | const original = { foo: 1 }; 6 | const observed = reactive(original); 7 | expect(observed).not.toBe(original); 8 | expect(observed.foo).toBe(1); 9 | 10 | // isReactive 11 | expect(isReactive(observed)).toBe(true); 12 | expect(isReactive(original)).toBe(false); 13 | 14 | // isProxy 15 | expect(isProxy(observed)).toBe(true); 16 | }); 17 | it("nested reactive", () => { 18 | const original = { 19 | obj: { foo: 1 }, 20 | arr: [{ bar: 2 }], 21 | }; 22 | const observed = reactive(original); 23 | 24 | expect(isReactive(observed.obj)).toBe(true); 25 | expect(isReactive(observed.arr)).toBe(true); 26 | expect(isReactive(observed.arr[0])).toBe(true); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReadonly, readonly } from "../reactive"; 2 | 3 | describe("readonly", () => { 4 | it("happy path", () => { 5 | // not allow set 6 | const original = { foo: 1, bar: { baz: 1 } }; 7 | const wrapped = readonly(original); 8 | expect(wrapped).not.toBe(original); 9 | expect(wrapped.foo).toBe(1); 10 | 11 | // isReadonly 12 | expect(isReadonly(wrapped)).toBe(true); 13 | expect(isReadonly(original)).toBe(false); 14 | 15 | // isProxy 16 | expect(isProxy(wrapped)).toBe(true); 17 | 18 | // 深层嵌套 19 | expect(isReadonly(wrapped.bar)).toBe(true); 20 | expect(isReadonly(original.bar)).toBe(false); 21 | }); 22 | 23 | it("warn when call set", () => { 24 | const user = readonly({ age: 10 }); 25 | // mock -> 验证 console.warn 有没有被调用 26 | console.warn = jest.fn(); 27 | user.age = 11; 28 | expect(console.warn).toBeCalled(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /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 count = ref(1); 8 | expect(count.value).toBe(1); 9 | }); 10 | 11 | it("shoule be reactive", () => { 12 | const a = ref(1); 13 | let dummy; 14 | let calls = 0; 15 | effect(() => { 16 | calls++; 17 | dummy = a.value; 18 | }); 19 | expect(calls).toBe(1); 20 | expect(dummy).toBe(1); 21 | a.value = 2; 22 | expect(calls).toBe(2); 23 | expect(dummy).toBe(2); 24 | // same value should not trigger 25 | a.value = 2; 26 | expect(calls).toBe(2); 27 | expect(dummy).toBe(2); 28 | }); 29 | 30 | it("should make nested properties reactive", () => { 31 | const a = ref({ 32 | count: 1, 33 | }); 34 | let dummy; 35 | effect(() => { 36 | dummy = a.value.count; 37 | }); 38 | expect(dummy).toBe(1); 39 | a.value.count = 2; 40 | expect(dummy).toBe(2); 41 | }); 42 | 43 | it("isRef", () => { 44 | const count = ref(1); 45 | const user = reactive({ 46 | age: 1, 47 | }); 48 | expect(isRef(count)).toBe(true); 49 | expect(isRef(user)).toBe(false); 50 | expect(isRef(1)).toBe(false); 51 | }); 52 | 53 | it("unRef", () => { 54 | // unRef() -> ref.value 55 | const count = ref(1); 56 | expect(unRef(count)).toBe(1); 57 | expect(unRef(1)).toBe(1); 58 | }); 59 | 60 | it("proxyRefs", () => { 61 | const user = { 62 | age: ref(10), 63 | name: "tom", 64 | }; 65 | 66 | const proxyUser = proxyRefs(user); 67 | 68 | // get 69 | expect(user.age.value).toBe(10); 70 | expect(proxyUser.age).toBe(10); 71 | expect(proxyUser.name).toBe("tom"); 72 | 73 | // set 74 | proxyUser.age = 20; 75 | expect(user.age.value).toBe(20); 76 | expect(proxyUser.age).toBe(20); 77 | 78 | // set 79 | proxyUser.age = ref(10); 80 | expect(user.age.value).toBe(10); 81 | expect(proxyUser.age).toBe(10); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadonly, shallowReadonly } from "../reactive"; 2 | 3 | describe("shallowReadonly", () => { 4 | it("should not make non-reactive properties reactive", () => { 5 | // not allow set 6 | const props = shallowReadonly({ n: { foo: 1 } }); 7 | expect(isReadonly(props)).toBe(true); 8 | expect(isReadonly(props.n)).toBe(false); 9 | }); 10 | 11 | it("warn when call set", () => { 12 | const user = shallowReadonly({ age: 10 }); 13 | // mock -> 验证 console.warn 有没有被调用 14 | console.warn = jest.fn(); 15 | user.age = 11; 16 | expect(console.warn).toBeCalled(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | export function provide(key, value) { 4 | // 存 5 | const currentInstance: any = getCurrentInstance(); 6 | if (currentInstance) { 7 | let { provides } = currentInstance; 8 | const parentProvides = currentInstance.parent.provides; 9 | // 判断是初始化的时候才执行 10 | if (provides === parentProvides) { 11 | // 给 provides 指定原型链对象为 父级 12 | provides = currentInstance.provides = Object.create(parentProvides); 13 | } 14 | provides[key] = value; 15 | } 16 | } 17 | 18 | export function inject(key, defaultValue) { 19 | // 取 20 | const currentInstance: any = getCurrentInstance(); 21 | if (currentInstance) { 22 | const parentProvides = currentInstance.parent.provides; 23 | if (key in parentProvides) { 24 | return parentProvides[key]; 25 | } else if (defaultValue) { 26 | // 处理默认值 可能是字符串或函数 27 | if (typeof defaultValue === "function") { 28 | return defaultValue(); 29 | } 30 | return defaultValue; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { proxyRefs } from "../reactivity"; 2 | import { shallowReadonly } from "../reactivity/reactive"; 3 | import { hasOwn } from "../shared"; 4 | import { emit } from "./componentEmit"; 5 | import { initProps } from "./componentProps"; 6 | import { initSlots } from "./componentSlots"; 7 | 8 | export function createComponentInstance(vnode, parent) { 9 | // console.log("parent", parent); 10 | const component = { 11 | vnode, 12 | type: vnode.type, 13 | setupState: {}, 14 | props: {}, 15 | slots: {}, // 就是vnode的children 16 | provides: parent ? parent.provides : {}, // 获取父级。ProviderTwo中再设置一个foo,会导致父级的foo发生变化,用原型链解决 17 | parent, 18 | isMounted: false, 19 | subTree: {}, 20 | emit: () => {}, 21 | update: null, 22 | next: null, 23 | }; 24 | 25 | // 处理emit方法,需要event和instance两个参数,但用户只传一个 add 26 | // 如:emit('add') 27 | // 所以在这里使用bind处理这个问题 28 | component.emit = emit.bind(null, component) as any; 29 | 30 | return component; 31 | } 32 | 33 | export function setupComponent(instance) { 34 | /** 35 | * init 36 | */ 37 | initProps(instance, instance.vnode.props); 38 | initSlots(instance, instance.vnode.children); 39 | 40 | setupStatefulComponent(instance); 41 | } 42 | 43 | function setupStatefulComponent(instance: any) { 44 | const Component = instance.type; 45 | 46 | // ctx 47 | instance.proxy = new Proxy( 48 | {}, 49 | { 50 | get(target, key) { 51 | const { setupState, props } = instance; 52 | 53 | // if (key in setupState) { 54 | // return setupState[key]; 55 | // } 56 | if (hasOwn(setupState, key)) { 57 | return setupState[key]; 58 | } else if (hasOwn(props, key)) { 59 | // props 60 | return props[key]; 61 | } 62 | // key -> $el 63 | if (key === "$el") { 64 | return instance.vnode.el; 65 | } 66 | 67 | // key -> $slot 68 | if (key === "$slot") { 69 | return instance.slots; 70 | } 71 | 72 | // key -> $props 73 | if (key === "$props") { 74 | return instance.props; 75 | } 76 | }, 77 | } 78 | ); 79 | 80 | // 拿到 setup 返回值 81 | const { setup } = Component; 82 | 83 | if (setup) { 84 | setCurrentInstance(instance); 85 | 86 | // 用户可能不写setup 87 | // setup() 可能返回 function 或者 object 88 | // 如果是function 就认为组件返回了render函数 89 | // 如果是object 会把object注入到组件上下文中 90 | const setupResult = setup(shallowReadonly(instance.props), { 91 | emit: instance.emit, 92 | }); 93 | 94 | setCurrentInstance(null); 95 | 96 | handleSetupResult(instance, setupResult); 97 | } 98 | } 99 | 100 | function handleSetupResult(instance, setupResult: any) { 101 | // TODO function 102 | 103 | if (typeof setupResult === "object") { 104 | instance.setupState = proxyRefs(setupResult); 105 | } 106 | 107 | finishComponentSetup(instance); 108 | } 109 | 110 | function finishComponentSetup(instance: any) { 111 | const Component = instance.type; 112 | 113 | if (Component.render) { 114 | instance.render = Component.render; 115 | } 116 | } 117 | 118 | // getCurrentInstance Api 119 | let currentInstance = null; 120 | function setCurrentInstance(instance) { 121 | currentInstance = instance; 122 | } 123 | export function getCurrentInstance() { 124 | return currentInstance; 125 | } 126 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerKey } from "../shared"; 2 | 3 | export function emit(instance, event, ...args) { 4 | console.log("event--", event); 5 | 6 | // props里面找 emit 绑定的参数 7 | // emit('add') -> onAdd(){} 8 | const { props } = instance; 9 | 10 | /** 11 | * TPP 12 | * 特定行为 -> 通用行为 13 | */ 14 | 15 | const handlerName = toHandlerKey(camelize(event)); 16 | 17 | const handler = props[handlerName]; 18 | handler && handler(...args); 19 | } 20 | -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | instance.props = rawProps || {}; // shallowReadonly 时参数必须为对象,如果没有传props, rawProps === undefined 报错 3 | } 4 | -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | // TODO 重构 setupStatefulComponent() -> proxy 2 | export const PublicInstanceProxyHandlers = {}; 3 | -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | 3 | export function initSlots(instance, children) { 4 | // 判断是slot的时候才执行函数,组件 && children 是 object 才是 slots 5 | const { shapeFlag } = instance.vnode; 6 | // if (typeof instance.type === "object" && typeof children === "object") { 7 | if (shapeFlag & ShapeFlags.SLOT_CHILDREN) { 8 | normalizeObjectSlots(instance, children); 9 | } 10 | } 11 | 12 | function normalizeObjectSlots(instance, children) { 13 | // children -> array 14 | // instance.slots = Array.isArray(children) ? children : [children]; 15 | 16 | console.log(instance); 17 | // 具名插槽 -> children object 18 | 19 | const slots = {}; 20 | for (const key in children) { 21 | const value = children[key]; 22 | 23 | // slots[key] = normalizeSlotValue(value); 24 | // 作用域插槽 function 25 | slots[key] = (props) => normalizeSlotValue(value(props)); 26 | } 27 | 28 | instance.slots = slots; 29 | } 30 | 31 | function normalizeSlotValue(value) { 32 | return Array.isArray(value) ? value : [value]; 33 | } 34 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | // import { render } from "./renderer"; 2 | import { createVNode } from "./vnode"; 3 | 4 | export function createAppAPI(render) { 5 | return function createApp(rootComponent) { 6 | return { 7 | mount(rootContainer) { 8 | /** 9 | * 先将内容解析成 vnode 10 | * component -> vnode 11 | * 后面的所有逻辑都会基于 vnode 进行操作 12 | */ 13 | 14 | // rootContainer -> dom 15 | if (typeof rootContainer === "string") { 16 | rootContainer = document.querySelector(rootContainer); 17 | } 18 | 19 | const vnode = createVNode(rootComponent); 20 | 21 | render(vnode, rootContainer); 22 | }, 23 | }; 24 | }; 25 | } 26 | 27 | // export function createApp(rootComponent) { 28 | // return { 29 | // mount(rootContainer) { 30 | // /** 31 | // * 先将内容解析成 vnode 32 | // * component -> vnode 33 | // * 后面的所有逻辑都会基于 vnode 进行操作 34 | // */ 35 | 36 | // // rootContainer -> dom 37 | // if (typeof rootContainer === "string") { 38 | // rootContainer = document.querySelector(rootContainer); 39 | // } 40 | 41 | // const vnode = createVNode(rootComponent); 42 | 43 | // render(vnode, rootContainer); 44 | // }, 45 | // }; 46 | // } 47 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | 3 | export function h(type, props?, children?) { 4 | // childred -> string or array 5 | return createVNode(type, props, children); 6 | } 7 | -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from "../vnode"; 2 | 3 | export function renderSlots(slots, name, props) { 4 | // 非具名 不传name 5 | // if (!name) { 6 | // return createVNode("div", {}, slots); 7 | // } 8 | 9 | // 具名插槽 10 | const slot = slots[name]; 11 | 12 | if (slot) { 13 | if (typeof slot === "function") { 14 | // 处理作用域插槽 15 | return createVNode(Fragment, {}, slot(props)); 16 | } 17 | // return createVNode("div", {}, slot); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | // export { createApp } from "./createApp"; 2 | export { h } from "./h"; 3 | export { renderSlots } from "./helpers/renderSlots"; 4 | export { createTextVnode } from "./vnode"; 5 | export { getCurrentInstance } from "./component"; 6 | export { provide, inject } from "./apiInject"; 7 | export { createRenderer } from "./renderer"; 8 | export { nextTick } from "./scheduler"; 9 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../reactivity/effect"; 2 | import { EMPTY_OBJ, isObject, isOn } from "../shared"; 3 | import { ShapeFlags } from "../shared/ShapeFlags"; 4 | import { createComponentInstance, setupComponent } from "./component"; 5 | import { shouldUpdateComponent } from "./componentUpdateUtils"; 6 | import { createAppAPI } from "./createApp"; 7 | import { queueJobs } from "./scheduler"; 8 | import { Fragment, Text } from "./vnode"; 9 | 10 | export function createRenderer(options) { 11 | const { 12 | createElement: hostCreateElement, 13 | patchProp: hostPatchProp, 14 | insert: hostInsert, 15 | remove: hostRemove, 16 | setElementText: hostSetElementText, 17 | } = options; 18 | 19 | function render(vnode, container) { 20 | // patch 21 | // console.log("vnode-----", vnode); 22 | patch(null, vnode, container, null, null); // 处理根组件不传 parentComponent 参数 23 | } 24 | 25 | // 优化 patch 架构 26 | // n1 -> 老的,如果不存在则是初始化,存在就是更新逻辑 27 | // n2 -> 新的 28 | // function patch(vnode, container, parentComponent) { 29 | function patch(n1, n2, container, parentComponent, anchor) { 30 | /** 31 | * 区分是 element 还是 component 32 | * 判断两种类型 33 | */ 34 | // debugger; 35 | // console.log("patch-------", vnode); 36 | 37 | // 使用 shapeFlag 判断类型 38 | const { type, shapeFlag } = n2; 39 | 40 | switch (type) { 41 | case Fragment: 42 | processFragment(n1, n2, container, parentComponent, anchor); 43 | break; 44 | 45 | case Text: 46 | processText(n1, n2, container); 47 | break; 48 | 49 | default: 50 | // shapeFlag & ShapeFlags.STATEFUL_COMPONENT 等同于 typeof vnode.type === "object" 51 | if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 52 | // 处理component 53 | processComponent(n1, n2, container, parentComponent, anchor); 54 | } else if (shapeFlag & ShapeFlags.ELEMENT) { 55 | // 处理 element 56 | processElement(n1, n2, container, parentComponent, anchor); 57 | } 58 | break; 59 | } 60 | } 61 | 62 | // element 类型 63 | function processElement(n1, n2: any, container: any, parentComponent, anchor) { 64 | if (!n1) { 65 | mountElement(n2, container, parentComponent, anchor); 66 | } else { 67 | patchElement(n1, n2, container, parentComponent, anchor); 68 | } 69 | } 70 | 71 | function patchElement(n1, n2, container, parentComponent, anchor) { 72 | // console.log("n1", n1); 73 | // console.log("n2", n2); 74 | // console.log("container", container); 75 | 76 | // update props 77 | const oldProps = n1.props || EMPTY_OBJ; 78 | const newProps = n2.props || EMPTY_OBJ; 79 | const el = n1.el; 80 | n2.el = el; // 本轮n2就是下一轮的n1,不赋值的话 下轮n1中就没有el 81 | 82 | patchChildren(n1, n2, el, parentComponent, anchor); 83 | patchProps(el, oldProps, newProps); 84 | } 85 | 86 | function patchChildren(n1, n2, container, parentComponent, anchor) { 87 | const prevShapeFlag = n1.shapeFlag; 88 | const nextShapeFlag = n2.shapeFlag; 89 | const c1 = n1.children; 90 | const c2 = n2.children; 91 | 92 | if (nextShapeFlag & ShapeFlags.TEXT_CHILDREN) { 93 | // TODO 优化 94 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { 95 | // array -> text 96 | // 1. 把老的清空 97 | unmountChildren(n1.children); 98 | // 2. 设置text 99 | hostSetElementText(container, c2); 100 | } else { 101 | // text -> text 102 | // 前后节点不一样才需要改变 103 | if (c1 !== c2) { 104 | hostSetElementText(container, c2); 105 | } 106 | } 107 | } else { 108 | // text -> array 109 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { 110 | hostSetElementText(container, ""); 111 | mountChildren(c2, container, parentComponent, anchor); 112 | } else { 113 | // array diff children 114 | patchKeyedChildren(c1, c2, container, parentComponent, anchor) 115 | } 116 | } 117 | } 118 | 119 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) { 120 | 121 | // const l2 = c2.length; 122 | 123 | let i = 0; 124 | let e1 = c1.length - 1; 125 | let e2 = c2.length - 1; 126 | // debugger 127 | function isSameVNodeType(n1, n2) { 128 | // type 和 key 判断两种 129 | return n1.type === n2.type && n1.key === n2.key; 130 | } 131 | 132 | // 1. 左侧对比 i指针->向右移动 133 | while (i <= e1 && i <= e2) { 134 | const n1 = c1[i]; 135 | const n2 = c2[i]; 136 | 137 | if (isSameVNodeType(n1, n2)) { 138 | patch(n1, n2, container, parentComponent, parentAnchor); 139 | } else { 140 | break; 141 | } 142 | i++; 143 | } 144 | // 2. 右侧对比 e1和e2指针->向左移动 145 | while (i <= e1 && i <= e2) { 146 | const n1 = c1[e1]; 147 | const n2 = c2[e2]; 148 | 149 | if (isSameVNodeType(n1, n2)) { 150 | patch(n1, n2, container, parentComponent, parentAnchor); 151 | } else { 152 | break; 153 | } 154 | e1--; 155 | e2--; 156 | } 157 | 158 | // 3. 新的比老的长 - 左侧和右侧 创建新的 159 | if (i > e1) { 160 | if (i <= e2) { 161 | // debugger; 162 | const nextPos = e2 + 1; // 锚点位置 163 | const anchor = nextPos < c2.length ? c2[nextPos].el : null; // 判断:左侧对比 -> null 还在后面插入节点 右侧对比 -> 找"A"节点传入 在"A"前面插入 164 | // 多个 child 遍历执行 patch 165 | while (i <= e2) { 166 | patch(null, c2[i], container, parentComponent, anchor); 167 | i++; 168 | } 169 | } 170 | } 171 | // 4. 老的比新的长 - 左侧和右侧 删除老的 172 | else if (i > e2) { 173 | while (i <= e1) { 174 | hostRemove(c1[i].el); 175 | i++; 176 | } 177 | } 178 | // 5. 中间对比 179 | else { 180 | let s1 = i; 181 | let s2 = i; 182 | 183 | /** 184 | * 删除的优化: 185 | * 新节点中 对比一次就记录一次;新节点全部对比完成后,如果老节点还有剩余元素的话 186 | * 就可以把这些全部删除 187 | */ 188 | const toBePatched = e2 - s2 + 1; // 新的中需要对比的全部数量 189 | let patched = 0; // 已经对比完成的 190 | 191 | /** 192 | * 最长递增子序列 193 | * 移动 194 | * 建立映射表 定长数组 195 | */ 196 | const newIndexToOldIndexMap = new Array(toBePatched); 197 | for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0; 198 | 199 | /** 200 | * 移动 优化 201 | * 什么时候需要移动 202 | */ 203 | let moved = false; 204 | let maxNewIndexSoFar = 0; 205 | 206 | 207 | // 基于新的 里面的 key 建立 映射表 208 | const keyToNewIndexMap = new Map() 209 | for (let i = s2; i <= e2; i++) { 210 | const nextChild = c2[i]; 211 | keyToNewIndexMap.set(nextChild.key, i); 212 | } 213 | 214 | // 遍历老的 215 | for (let i = s1; i <= e1; i++) { 216 | // 拿到当前节点 217 | const prevChild = c1[i]; 218 | 219 | if (patched > toBePatched) { 220 | hostRemove(prevChild.el); 221 | continue; 222 | } 223 | 224 | let newIndex; 225 | // 根据 null 和 undefined 判断用户写没写key 226 | if (prevChild.key != null) { 227 | newIndex = keyToNewIndexMap.get(prevChild.key); 228 | } else { 229 | // 如果没有 key 就需要遍历判断 性能低 230 | for (let j = s2; j <= e2; j++) { 231 | if (isSameVNodeType(prevChild, c2[j])) { 232 | newIndex = j; 233 | break; 234 | } 235 | } 236 | } 237 | 238 | // 如果newIndex存在,就说明 新老里面都有该节点 239 | // 不存在 就删除老的 240 | if (newIndex === undefined) { 241 | hostRemove(prevChild.el); 242 | } else { 243 | if (newIndex >= maxNewIndexSoFar) { 244 | maxNewIndexSoFar = newIndex; 245 | } else { 246 | moved = true; 247 | } 248 | /** 249 | * 为什么是i+1,而不是i? 250 | * 因为i 可能是0,而0在这里有特殊含义,因为初始化赋的值就是0,需要用0判断是否需要创建元素 251 | * 所以就赋值为i+1 避免这个问题 252 | */ 253 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 254 | patch(prevChild, c2[newIndex], container, parentComponent, null); 255 | patched++; 256 | } 257 | } 258 | 259 | // 生成最长递增子序列 260 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; 261 | let j = increasingNewIndexSequence.length - 1; // 生成的子序列的下标 262 | 263 | // 从后往前遍历,因为 insertBefore插入元素需要在一个稳定元素的前面插入 264 | for (let i = toBePatched - 1; i >= 0; i--) { 265 | const nextIndex = i + s2; 266 | const nextChild = c2[nextIndex]; 267 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null; 268 | 269 | /** 270 | * 创建 271 | */ 272 | if (newIndexToOldIndexMap[i] === 0) { 273 | // 在老的里面没有需要创建 274 | patch(null, nextChild, container, parentComponent, anchor); 275 | } 276 | /** 277 | * 移动 278 | */ 279 | else if (moved) { 280 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 281 | // 移动位置 282 | hostInsert(nextChild.el, container, anchor); 283 | } else { 284 | j--; 285 | } 286 | } 287 | } 288 | } 289 | } 290 | 291 | function unmountChildren(children) { 292 | for (let i = 0; i < children.length; i++) { 293 | const el = children[i].el; 294 | // remove 295 | hostRemove(el); 296 | } 297 | } 298 | 299 | function patchProps(el, oldProps, newProps) { 300 | if (oldProps !== newProps) { 301 | // 健壮性 302 | for (const key in newProps) { 303 | const prevProp = oldProps[key]; 304 | const nextProp = newProps[key]; 305 | if (prevProp !== nextProp) { 306 | hostPatchProp(el, key, prevProp, nextProp); 307 | } 308 | } 309 | 310 | if (oldProps !== EMPTY_OBJ) { 311 | // 健壮性 312 | // update props 的第三种情况:属性被删除 313 | for (const key in oldProps) { 314 | if (!(key in newProps)) { 315 | hostPatchProp(el, key, oldProps[key], null); 316 | } 317 | } 318 | } 319 | } 320 | } 321 | 322 | function mountElement(vnode, container, parentComponent, anchor) { 323 | const { type, children, props } = vnode; 324 | 325 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 326 | 327 | // const el = document.createElement(type); 328 | const el = hostCreateElement(type); 329 | 330 | // $el 331 | // vnode -> element -> div 332 | vnode.el = el; 333 | 334 | // children -> string or array 335 | // 使用 shapeFlag 判断类型 336 | const { shapeFlag } = vnode; 337 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { 338 | // children is string 339 | el.textContent = children; 340 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 341 | // children is array 342 | mountChildren(vnode.children, el, parentComponent, anchor); // 抽离-优化 343 | } 344 | 345 | // props 346 | for (const key in props) { 347 | const val = props[key]; 348 | // console.log(key); 349 | 350 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 351 | 352 | // if (isOn(key)) { 353 | // const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写 354 | // el.addEventListener(eventName, val); 355 | // } else { 356 | // el.setAttribute(key, val); 357 | // } 358 | hostPatchProp(el, key, null, val); 359 | } 360 | 361 | // 编写 api customRenderer,需要抽离document等web平台的相关函数 362 | 363 | // container.append(el); 364 | hostInsert(el, container, anchor); 365 | } 366 | 367 | function mountChildren(children: any, el: any, parentComponent, anchor) { 368 | children.forEach((v) => { 369 | patch(null, v, el, parentComponent, anchor); 370 | }); 371 | } 372 | 373 | // component 类型 374 | function processComponent(n1, n2: any, container: any, parentComponent, anchor) { 375 | if (!n1) { 376 | mountComponent(n2, container, parentComponent, anchor); 377 | } else { 378 | updateComponent(n1, n2); 379 | } 380 | } 381 | 382 | function updateComponent(n1, n2) { 383 | const instance = (n2.component = n1.component); 384 | // 判断是否需要更新 385 | if (shouldUpdateComponent(n1, n2)) { 386 | // 新的vnode保存起来 下次要更新的 387 | instance.next = n2; 388 | instance.update(); 389 | } else { 390 | n2.el = n1.el; 391 | instance.vnode = n2; 392 | } 393 | 394 | } 395 | 396 | function mountComponent(initialVNode: any, container, parentComponent, anchor) { 397 | // 创建组件实例 398 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)); 399 | 400 | setupComponent(instance); 401 | 402 | setupRenderEffect(instance, container, anchor); 403 | } 404 | 405 | function setupRenderEffect(instance: any, container, anchor) { 406 | // 使用effect包裹原来的逻辑,收集依赖 407 | instance.update = effect(() => { 408 | console.log("setupRenderEffect"); 409 | if (!instance.isMounted) { 410 | // 未挂载就是 init 初始化 411 | const { proxy } = instance; 412 | const subTree = instance.render.call(proxy); // 第一次执行App.js根组件中的render函数,这个函数返回由h创建的vnode 413 | 414 | // 保存老的vnode prevSubTree 415 | instance.subTree = subTree; 416 | 417 | // console.log("--subTree", subTree); 418 | 419 | // vnode -> patch 420 | // vnode -> element -> mountElement 421 | patch(null, subTree, container, instance, anchor); // parentComponent -> instance 422 | 423 | // all element -> mount 424 | // $el根节点赋值到当前组件vnode的el上面 425 | instance.vnode.el = subTree.el; 426 | 427 | // init完成 428 | instance.isMounted = true; 429 | } else { 430 | // update 431 | // vnode:更新之前的 next:下次要更新的 432 | const { next, vnode } = instance; 433 | if (next) { 434 | next.el = vnode.el; 435 | updataComponentPreRender(instance, next); 436 | } 437 | 438 | const { proxy } = instance; 439 | const subTree = instance.render.call(proxy); 440 | const prevSubTree = instance.subTree; 441 | // 把老的更新,保证下次进入是正确的 442 | instance.subTree = subTree; 443 | 444 | patch(prevSubTree, subTree, container, instance, anchor); 445 | } 446 | }, 447 | { 448 | scheduler() { 449 | console.log("update-scheduler"); 450 | queueJobs(instance.update); 451 | } 452 | } 453 | ); 454 | } 455 | 456 | // slot 的 Fragment 和 Text 457 | function processFragment(n1, n2: any, container: any, parentComponent, anchor) { 458 | mountChildren(n2.children, container, parentComponent, anchor); 459 | } 460 | function processText(n1, n2: any, container: any) { 461 | const { children } = n2; 462 | const textNode = (n2.el = document.createTextNode(children)); // 需要赋值给vnode的el 463 | container.append(textNode); 464 | } 465 | 466 | // 解决 createRenderer之后,createApp 无法再使用 render 的问题 467 | return { 468 | createApp: createAppAPI(render), 469 | }; 470 | } 471 | 472 | function updataComponentPreRender(instance: any, nextVNode: any) { 473 | instance.vnode = nextVNode; 474 | instance.next = null; 475 | instance.props = nextVNode.props; 476 | } 477 | 478 | // 最长递增子序列算法 479 | function getSequence(arr) { 480 | const p = arr.slice(); 481 | const result = [0]; 482 | let i, j, u, v, c; 483 | const len = arr.length; 484 | for (i = 0; i < len; i++) { 485 | const arrI = arr[i]; 486 | if (arrI !== 0) { 487 | j = result[result.length - 1]; 488 | if (arr[j] < arrI) { 489 | p[i] = j; 490 | result.push(i); 491 | continue; 492 | } 493 | u = 0; 494 | v = result.length - 1; 495 | while (u < v) { 496 | c = (u + v) >> 1; 497 | if (arr[result[c]] < arrI) { 498 | u = c + 1; 499 | } else { 500 | v = c; 501 | } 502 | } 503 | if (arrI < arr[result[u]]) { 504 | if (u > 0) { 505 | p[i] = result[u - 1]; 506 | } 507 | result[u] = i; 508 | } 509 | } 510 | } 511 | u = result.length; 512 | v = result[u - 1]; 513 | while (u-- > 0) { 514 | result[u] = v; 515 | v = p[v]; 516 | } 517 | return result; 518 | } 519 | 520 | -------------------------------------------------------------------------------- /src/runtime-core/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue: any[] = []; 2 | let isFlushPending = false; // 优化每次都会创建promise的问题 3 | 4 | export function queueJobs(job) { 5 | if (!queue.includes(job)) { 6 | queue.push(job); 7 | } 8 | 9 | queueFlush(); 10 | } 11 | 12 | export function nextTick(fn) { 13 | return fn ? Promise.resolve().then(fn) : Promise.resolve(); 14 | } 15 | 16 | function queueFlush() { 17 | if (isFlushPending) return; 18 | isFlushPending = true; 19 | Promise.resolve().then(() => { 20 | isFlushPending = false; 21 | let job; 22 | while ((job = queue.shift())) { 23 | job && job(); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | 3 | export const Fragment = Symbol("Fragment"); 4 | export const Text = Symbol("Text"); 5 | 6 | export function createVNode(type, props?, children?) { 7 | const vnode = { 8 | type, 9 | props, 10 | children, 11 | component: null, 12 | key: props && props.key, 13 | shapeFlag: getShapeFlag(type), 14 | el: null, // $el 15 | }; 16 | 17 | // 重构优化 ShapeFlags 18 | // 判断children类型 19 | if (typeof children === "string") { 20 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN; 21 | } else if (Array.isArray(children)) { 22 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN; 23 | } 24 | // 判断 children 是 slot(是slot的条件: 组件 + children是对象) 25 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 26 | if (typeof children === "object") { 27 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN; 28 | } 29 | } 30 | 31 | return vnode; 32 | } 33 | 34 | // 重构优化 ShapeFlags 35 | // 判断type类型 36 | function getShapeFlag(type) { 37 | return typeof type === "string" 38 | ? ShapeFlags.ELEMENT 39 | : ShapeFlags.STATEFUL_COMPONENT; 40 | } 41 | 42 | export function createTextVnode(text) { 43 | return createVNode(Text, {}, text); 44 | } 45 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../runtime-core"; 2 | import { isOn } from "../shared"; 3 | 4 | function createElement(type) { 5 | console.log("createElement------------"); 6 | return document.createElement(type); 7 | } 8 | 9 | /** 10 | * @param el 11 | * @param key 12 | * @param prevVal 13 | * @param val -> nextVal 当前的值 14 | */ 15 | function patchProp(el, key, prevVal, val) { 16 | // console.log("patchProp------------"); 17 | if (isOn(key)) { 18 | const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写 19 | el.addEventListener(eventName, val); 20 | } else { 21 | if (val === undefined || val === null) { 22 | el.removeAttribute(key); 23 | } else { 24 | el.setAttribute(key, val); 25 | } 26 | } 27 | } 28 | 29 | function insert(el, parent, anchor) { 30 | // console.log("insert------------"); 31 | // parent.append(el); 32 | parent.insertBefore(el, anchor || null); 33 | } 34 | 35 | function remove(child) { 36 | const parent = child.parentNode; 37 | if (parent) { 38 | parent.removeChild(child); 39 | } 40 | } 41 | function setElementText(el, text) { 42 | el.textContent = text; 43 | } 44 | 45 | const renderer: any = createRenderer({ 46 | createElement, 47 | patchProp, 48 | insert, 49 | remove, 50 | setElementText, 51 | }); 52 | 53 | export function createApp(...args) { 54 | return renderer.createApp(...args); 55 | } 56 | 57 | export * from "../runtime-core"; 58 | -------------------------------------------------------------------------------- /src/shared/ShapeFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags { 2 | ELEMENT = 1, // 01 -> 0001 vnode.type->element类型 3 | STATEFUL_COMPONENT = 1 << 1, // 10 -> 0010 vnode.type->component类型 4 | TEXT_CHILDREN = 1 << 2, // 100 -> 0100 vnode.children->string类型 5 | ARRAY_CHILDREN = 1 << 3, // 1000 -> 1000 vnode.children->array类型 6 | 7 | SLOT_CHILDREN = 1 << 4, // 判断children是slot 8 | } 9 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const EMPTY_OBJ = {}; 2 | 3 | export const extend = Object.assign; 4 | 5 | export function isObject(val) { 6 | return val !== null && typeof val === "object"; 7 | } 8 | 9 | export const hasChanged = (value, newValue) => { 10 | return !Object.is(value, newValue); 11 | }; 12 | 13 | // 事件注册 14 | export const isOn = (key) => /^on[A-Z]/.test(key); 15 | 16 | // props 17 | export const hasOwn = (val, key) => 18 | Object.prototype.hasOwnProperty.call(val, key); 19 | 20 | // emit 21 | // 首字母大写 前面加on 22 | // add -> onAdd 23 | const capitalize = (str: string) => { 24 | return str.charAt(0).toUpperCase() + str.slice(1); 25 | }; 26 | 27 | // kebab-case foo-add -> fooAdd 28 | export const camelize = (str: string) => { 29 | return str.replace(/-(\w)/g, (_, c: string) => { 30 | return c ? c.toUpperCase() : ""; 31 | }); 32 | }; 33 | 34 | export const toHandlerKey = (str: string) => { 35 | return str ? "on" + capitalize(str) : ""; 36 | }; 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | "lib": ["DOM", "es6", "ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "esnext", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | } 101 | } 102 | --------------------------------------------------------------------------------