├── .gitignore ├── README.MD ├── babel.config.js ├── example ├── apiInject │ ├── App.js │ └── index.html ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── createApp │ ├── App.js │ └── index.html ├── currentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── customRenderer │ ├── App.js │ ├── index.html │ ├── main.js │ └── pixi.js ├── patchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── package.json ├── rollup.config.js ├── src ├── index.ts ├── resctivite │ ├── baseHandler.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── createApp.ts │ ├── h.ts │ ├── helpers │ │ └── renderSlots.ts │ ├── index.ts │ ├── render.ts │ ├── vnode.ts │ └── vue.dt.ts ├── runtime-dom │ └── index.ts └── shared │ ├── ShapeFlages.ts │ └── shared.ts ├── tsconfig.json └── vueSetupFlow ├── INIT_VUE_FLOW.MD ├── 实现Fragment、Text.MD ├── 实现组件的emit.MD ├── 实现自定义渲染器.MD ├── 更新组件的渲染流程.MD ├── 注册事件.MD ├── 组件代理对象.MD └── 组件插槽.MD /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | lib -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | 配置开发环境 2 | - 1 yarn init 初始化环境 3 | - 2 yarn add typescript 添加ts 4 | - 3 npx tsc --init 初始化ts配置文件 5 | - 4 yarn add jest @types/jest --dev 添加jest测试环境 识别ts文件 6 | - 5 yarn add --dev babel-jest @babel/core @babel/preset-env 7 | - yarn add --dev @babel/preset-typescript 添加babel.config.js文件配置 8 | 9 | - [✓] 数据响应式功能已实现 10 | - [x] 虚拟节点转dom元素 11 | - [x] 虚拟节点diff 计算差异化更新 12 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; -------------------------------------------------------------------------------- /example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | // 组件 provide 和 inject 功能 2 | import { h, provide, inject } from "../../lib/guide-mini-vue.esm.js"; 3 | 4 | const Provider = { 5 | name: "Provider", 6 | setup() { 7 | provide("foo", "fooVal"); 8 | provide("bar", "barVal"); 9 | }, 10 | render() { 11 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]); 12 | }, 13 | }; 14 | 15 | const ProviderTwo = { 16 | name: "ProviderTwo", 17 | setup() { 18 | provide("foo", "fooTwo"); 19 | const foo = inject("foo"); 20 | 21 | return { 22 | foo, 23 | }; 24 | }, 25 | render() { 26 | return h("div", {}, [ 27 | h("p", {}, `ProviderTwo foo:${this.foo}`), 28 | h(Consumer), 29 | ]); 30 | }, 31 | }; 32 | 33 | const Consumer = { 34 | name: "Consumer", 35 | setup() { 36 | const foo = inject("foo"); 37 | const bar = inject("bar"); 38 | // const baz = inject("baz", "bazDefault"); 39 | const baz = inject("baz", () => "bazDefault"); 40 | return { 41 | foo, 42 | bar, 43 | baz, 44 | }; 45 | }, 46 | 47 | render() { 48 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`); 49 | }, 50 | }; 51 | 52 | 53 | 54 | export default { 55 | name: "App", 56 | setup() {}, 57 | render() { 58 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]); 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots} from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | const foo = { 5 | name: "foo", 6 | render() { 7 | window.slots = this.$slots 8 | const p = h('p', {}, 'P') 9 | // 传入单个渲染元素 10 | // return h('div', {}, [p, this.$slots]) 11 | 12 | // 传入数组类型子元素 13 | // 因为patch时 只会渲染虚拟节点 并不会处理数组类型的数据 可以使用h函数包裹创建一个新的节点 14 | // return h('div', {}, [p, h('div', {}, this.$slots)]) 15 | 16 | // 使用renderSlots辅助函数 17 | // return h('div', {}, [p, renderSlots(this.$slots, 'header')]) 18 | 19 | // 使用具名插槽 20 | // return h('div', {}, [renderSlots(this.$slots, 'navigator'), renderSlots(this.$slots, 'header'), p, renderSlots(this.$slots, 'footer')]) 21 | 22 | // 作用域插槽 23 | const age = 40 24 | return h('div', {}, [renderSlots(this.$slots, 'navigator', { age }), renderSlots(this.$slots, 'header'), p, renderSlots(this.$slots, 'footer')]) 25 | }, 26 | setup() { 27 | return { 28 | tag: '454' 29 | } 30 | } 31 | } 32 | 33 | export const App = { 34 | name: "App", 35 | render() { 36 | // 传入单个渲染元素 37 | // return h('div', {}, [ h('div', {class: 'red '}, 'App'), h(foo, {}, h('h1', {}, 'h1') ) ]) 38 | 39 | // 传入数组类型子元素 40 | // return h( 41 | // 'div', 42 | // {}, 43 | // [ 44 | // h( 45 | // 'div', 46 | // { class: 'red ' }, 47 | // 'App' 48 | // ), 49 | // h( 50 | // foo 51 | // , {} 52 | // , 53 | // h( 54 | // 'h1' 55 | // , 56 | // {}, 57 | // 'h1' 58 | // ), 59 | // ) 60 | // ] 61 | // ) 62 | 63 | // 具名插槽 64 | // const app = h("div", {}, "App"); 65 | // return h('div', {}, [app, h(foo, {}, { 66 | // navigator: h('nav', {}, 'nav'), 67 | // header: h('h1',{}, 'header'), 68 | // footer: h('h1',{}, 'footer'), 69 | // })]) 70 | 71 | // 作用域插槽 72 | const app = h("div", {}, "App"); 73 | return h('div', {}, [app, h(foo, {}, { 74 | navigator: ({ age }) => h('nav', {}, 'nav' + age), 75 | header: () => h('h1',{}, 'header'), 76 | footer: () => h('h1',{}, 'footer'), 77 | })]) 78 | 79 | }, 80 | setup() { 81 | return { 82 | }; 83 | }, 84 | }; 85 | 86 | // export const App = { 87 | // name: "App", 88 | // render() { 89 | // // return h("div", {}, [ 90 | // // h("div", {}, "App"), 91 | // // h(Foo, { 92 | // // onAdd(a, b) { 93 | // // console.log("onAdd", a, b); 94 | // // }, 95 | // // onAddFoo() { 96 | // // console.log("onAddFoo"); 97 | // // }, 98 | // // }), 99 | // // ]); 100 | // return h('div', {}, [h(foo, {}, [ h('h1', {}, 'h1'), h('h2', {}, 'h2') ])]) 101 | // }, 102 | // setup() { 103 | // return { 104 | // }; 105 | // }, 106 | // }; 107 | -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | name: 'Foo', 5 | setup(props, { emit }) { 6 | const emitAdd = () => { 7 | console.log("emit add"); 8 | emit("add",1,2); 9 | emit("add-foo", emit, props); 10 | }; 11 | 12 | return { 13 | emitAdd, 14 | }; 15 | }, 16 | render() { 17 | const btn = h( 18 | "button", 19 | { 20 | onClick: this.emitAdd, 21 | }, 22 | "emitAdd" 23 | ); 24 | 25 | const foo = h("p", {}, "foo"); 26 | return h("div", {}, [foo, btn, h('ul', {}, [h('li', {}, 'klop')])]); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextVNode} from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | // Fragment 以及 Text 5 | export const App = { 6 | name: "App", 7 | render() { 8 | const app = h("div", {}, "App"); 9 | 10 | const header = (props) => { 11 | return [ h('div', {}, 'header' + props.count), createTextVNode('hhaah')] 12 | } 13 | const footer = (props) => h('div', {}, 'footer') 14 | const hj = (prosp) => h('h1', { class: 'red' }, '[]') 15 | 16 | // 单个节点 17 | // const foo = h(Foo, {}, header ); 18 | // 支持传入数组 19 | // const foo = h(Foo, {}, [header, footer] ) 20 | 21 | // 实现具名插槽 22 | // 作用域插槽 23 | const foo = h(Foo, {}, { 24 | footer: footer, 25 | header: header, 26 | hj: hj 27 | }); 28 | 29 | return h("div", {}, [app, foo]); 30 | }, 31 | 32 | setup() { 33 | return {}; 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | setup() { 5 | return {}; 6 | }, 7 | render() { 8 | const count = 1 9 | const foo = h("p", {}, "foo"); 10 | return h("div", {}, [renderSlots(this.$slots,'hj') ,renderSlots(this.$slots, 'header', { count }), foo, renderSlots(this.$slots, 'footer')]); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/createApp/App.js: -------------------------------------------------------------------------------- 1 | 2 | import { h, renderSlots } from '../../lib/guide-mini-vue.esm.js' 3 | const foo = { 4 | name: "foo", 5 | setup (props, { emit }) { 6 | const add = () => { 7 | emit('add', 1, 2) 8 | } 9 | return { 10 | th: 'BU', 11 | add 12 | } 13 | }, 14 | render () { 15 | // 2 可以通过this.xxx 获取props 数据 16 | return h('button', { 17 | onClick: this.add, 18 | }, 'button') 19 | } 20 | } 21 | const foo1 = { 22 | name: "foo", 23 | setup (props, { emit }) { 24 | const add = () => { 25 | emit('add-foo', 4, 5 ) 26 | } 27 | return { 28 | th: 'BU', 29 | add 30 | } 31 | }, 32 | render () { 33 | // 2 可以通过this.xxx 获取props 数据 34 | return h('button', { 35 | onClick: this.add, 36 | }, 'foo1') 37 | } 38 | } 39 | 40 | 41 | /** c组件插槽 */ 42 | // 1 可以通过this$slots获取到传入的虚拟节点 ==》 ok 43 | // 2 使用一个 renderSolts 函数来创建节点 44 | const APP1 = { 45 | name: 'APP1', 46 | setup () { 47 | }, 48 | render () { 49 | const app1 = h('div', {}, 'APP1' ) 50 | return h('div', {}, [app1, renderSlots(this.$slots)]) 51 | } 52 | } 53 | window.self = null 54 | export default { 55 | name: "App", 56 | setup(props) { 57 | return { 58 | } 59 | }, 60 | props: { 61 | mag: '哈哈嘿嘿', 62 | }, 63 | render() { 64 | window.self = this 65 | // 测试单个元素 66 | // return h("div", 67 | // { id: '45', class: '' }, 68 | // [ 69 | // h('div', { id: 'op1' }, '我是div1'), 70 | // h('div', { id: 'op2' }, '我是div2'), 71 | // h(foo, { count: 1 }) 72 | // ] 73 | // ); 74 | 75 | // 测试组件代理对象 76 | // return h('div', { }, 'hi' + this.msg + this.jk ) 77 | 78 | 79 | // 测试 $el 80 | // return h('div', { }, 'hi' + this.msg) 81 | 82 | // return h('div', {}, [h(foo, { 83 | // onAdd: (a,b) => { 84 | // console.log('onAdd',a,b) 85 | // }, 86 | 87 | // }), h(foo1, { onAddFoo: (a,b) => { 88 | // console.log('onAddFoo', a,b) 89 | // } }, [ h('div', {}, 'div') ])] ) 90 | 91 | 92 | 93 | 94 | return h('div', {}, [h(APP1, {}, h('h1', {}, 'h1'))]) 95 | 96 | }, 97 | }; 98 | -------------------------------------------------------------------------------- /example/createApp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 14 | 15 | 16 | 17 |
18 | 19 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | render() { 7 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]); 8 | }, 9 | setup() { 10 | console.log(getCurrentInstance()) 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | name:"Foo", 5 | setup() { 6 | let instance = getCurrentInstance() 7 | return { 8 | text: instance.vnode.shapeflag, 9 | ...instance 10 | } 11 | }, 12 | render() { 13 | console.log(this) 14 | return h("div", {}, "foo" + this.text); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const App = { 4 | setup() { 5 | return { 6 | x: 100, 7 | y: 100, 8 | }; 9 | }, 10 | render() { 11 | return h("rect", { x: this.x, y: this.y }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /example/customRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/customRenderer/main.js: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | console.log(createRenderer) -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | import ArrayToText from "./ArrayToText.js"; 4 | import TextToText from "./TextToText.js"; 5 | import TextToArray from "./TextToArray.js"; 6 | import ArrayToArray from "./ArrayToArray.js"; 7 | 8 | export default { 9 | name: "App", 10 | setup() {}, 11 | 12 | render() { 13 | return h("div", { tId: 1 }, [ 14 | h("p", {}, "主页"), 15 | // 老的是 array 新的是 text 16 | // h(ArrayToText), 17 | // 老的是 text 新的是 text 18 | // h(TextToText), 19 | // 老的是 text 新的是 array 20 | // h(TextToArray) 21 | // 老的是 array 新的是 array 22 | h(ArrayToArray), 23 | ]); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 array 3 | 4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 5 | 6 | // 1. 左侧的对比 7 | // (a b) c 8 | // (a b) d e 9 | // const prevChildren = [ 10 | // h("p", { key: "A" }, "A"), 11 | // h("p", { key: "B" }, "B"), 12 | // h("p", { key: "C" }, "C"), 13 | // ]; 14 | // const nextChildren = [ 15 | // h("p", { key: "A" }, "A"), 16 | // h("p", { key: "B" }, "B"), 17 | // h("p", { key: "D" }, "D"), 18 | // h("p", { key: "E" }, "E"), 19 | // ]; 20 | 21 | // 2. 右侧的对比 22 | // a (b c) 23 | // d e (b c) 24 | // const prevChildren = [ 25 | // h("p", { key: "A" }, "A"), 26 | // h("p", { key: "B" }, "B"), 27 | // h("p", { key: "C" }, "C"), 28 | // ]; 29 | // const nextChildren = [ 30 | // h("p", { key: "D" }, "D"), 31 | // h("p", { key: "E" }, "E"), 32 | // h("p", { key: "B" }, "B"), 33 | // h("p", { key: "C" }, "C"), 34 | // ]; 35 | 36 | // 3. 新的比老的长 37 | // 创建新的 38 | // 左侧 39 | // (a b) 40 | // (a b) c 41 | // i = 2, e1 = 1, e2 = 2 42 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 43 | // const nextChildren = [ 44 | // h("p", { key: "A" }, "A"), 45 | // h("p", { key: "B" }, "B"), 46 | // h("p", { key: "C" }, "C"), 47 | // h("p", { key: "D" }, "D"), 48 | // ]; 49 | 50 | // 右侧 51 | // (a b) 52 | // c (a b) 53 | // i = 0, e1 = -1, e2 = 0 54 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 55 | // const nextChildren = [ 56 | // h("p", { key: "C" }, "C"), 57 | // h("p", { key: "A" }, "A"), 58 | // h("p", { key: "B" }, "B"), 59 | // ]; 60 | 61 | // 4. 老的比新的长 62 | // 删除老的 63 | // 左侧 64 | // (a b) c 65 | // (a b) 66 | // i = 2, e1 = 2, e2 = 1 67 | // const prevChildren = [ 68 | // h("p", { key: "A" }, "A"), 69 | // h("p", { key: "B" }, "B"), 70 | // h("p", { key: "C" }, "C"), 71 | // ]; 72 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 73 | 74 | // 右侧 75 | // a (b c) 76 | // (b c) 77 | // i = 0, e1 = 0, e2 = -1 78 | 79 | // const prevChildren = [ 80 | // h("p", { key: "A" }, "A"), 81 | // h("p", { key: "B" }, "B"), 82 | // h("p", { key: "C" }, "C"), 83 | // ]; 84 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 85 | 86 | // 5. 对比中间的部分 87 | // 删除老的 (在老的里面存在,新的里面不存在) 88 | // 5.1 89 | // a,b,(c,d),f,g 90 | // a,b,(e,c),f,g 91 | // D 节点在新的里面是没有的 - 需要删除掉 92 | // C 节点 props 也发生了变化 93 | 94 | // const prevChildren = [ 95 | // h("p", { key: "A" }, "A"), 96 | // h("p", { key: "B" }, "B"), 97 | // h("p", { key: "C", id: "c-prev" }, "C"), 98 | // h("p", { key: "D" }, "D"), 99 | // h("p", { key: "F" }, "F"), 100 | // h("p", { key: "G" }, "G"), 101 | // ]; 102 | 103 | // const nextChildren = [ 104 | // h("p", { key: "A" }, "A"), 105 | // h("p", { key: "B" }, "B"), 106 | // h("p", { key: "E" }, "E"), 107 | // h("p", { key: "C", id:"c-next" }, "C"), 108 | // h("p", { key: "F" }, "F"), 109 | // h("p", { key: "G" }, "G"), 110 | // ]; 111 | 112 | // 5.1.1 113 | // a,b,(c,e,d),f,g 114 | // a,b,(e,c),f,g 115 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑) 116 | // const prevChildren = [ 117 | // h("p", { key: "A" }, "A"), 118 | // h("p", { key: "B" }, "B"), 119 | // h("p", { key: "C", id: "c-prev" }, "C"), 120 | // h("p", { key: "E" }, "E"), 121 | // h("p", { key: "D" }, "D"), 122 | // h("p", { key: "F" }, "F"), 123 | // h("p", { key: "G" }, "G"), 124 | // ]; 125 | 126 | // const nextChildren = [ 127 | // h("p", { key: "A" }, "A"), 128 | // h("p", { key: "B" }, "B"), 129 | // h("p", { key: "E" }, "E"), 130 | // h("p", { key: "C", id:"c-next" }, "C"), 131 | // h("p", { key: "F" }, "F"), 132 | // h("p", { key: "G" }, "G"), 133 | // ]; 134 | 135 | // 2 移动 (节点存在于新的和老的里面,但是位置变了) 136 | 137 | // 2.1 138 | // a,b,(c,d,e),f,g 139 | // a,b,(e,c,d),f,g 140 | // 最长子序列: [1,2] 141 | 142 | const prevChildren = [ 143 | h("p", { key: "A" }, "A"), 144 | h("p", { key: "B" }, "B"), 145 | h("p", { key: "C" }, "C"), 146 | h("p", { key: "D" }, "D"), 147 | h("p", { key: "E" }, "E"), 148 | h("p", { key: "F" }, "F"), 149 | h("p", { key: "G" }, "G"), 150 | ]; 151 | 152 | const nextChildren = [ 153 | h("p", { key: "A" }, "A"), 154 | h("p", { key: "B" }, "B"), 155 | h("p", { key: "E" }, "E"), 156 | h("p", { key: "C" }, "C"), 157 | h("p", { key: "D" }, "D"), 158 | h("p", { key: "F" }, "F"), 159 | h("p", { key: "G" }, "G"), 160 | ]; 161 | 162 | // 3. 创建新的节点 163 | // a,b,(c,e),f,g 164 | // a,b,(e,c,d),f,g 165 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建 166 | // const prevChildren = [ 167 | // h("p", { key: "A" }, "A"), 168 | // h("p", { key: "B" }, "B"), 169 | // h("p", { key: "C" }, "C"), 170 | // h("p", { key: "E" }, "E"), 171 | // h("p", { key: "F" }, "F"), 172 | // h("p", { key: "G" }, "G"), 173 | // ]; 174 | 175 | // const nextChildren = [ 176 | // h("p", { key: "A" }, "A"), 177 | // h("p", { key: "B" }, "B"), 178 | // h("p", { key: "E" }, "E"), 179 | // h("p", { key: "C" }, "C"), 180 | // h("p", { key: "D" }, "D"), 181 | // h("p", { key: "F" }, "F"), 182 | // h("p", { key: "G" }, "G"), 183 | // ]; 184 | 185 | // 综合例子 186 | // a,b,(c,d,e,z),f,g 187 | // a,b,(d,c,y,e),f,g 188 | 189 | // const prevChildren = [ 190 | // h("p", { key: "A" }, "A"), 191 | // h("p", { key: "B" }, "B"), 192 | // h("p", { key: "C" }, "C"), 193 | // h("p", { key: "D" }, "D"), 194 | // h("p", { key: "E" }, "E"), 195 | // h("p", { key: "Z" }, "Z"), 196 | // h("p", { key: "F" }, "F"), 197 | // h("p", { key: "G" }, "G"), 198 | // ]; 199 | 200 | // const nextChildren = [ 201 | // h("p", { key: "A" }, "A"), 202 | // h("p", { key: "B" }, "B"), 203 | // h("p", { key: "D" }, "D"), 204 | // h("p", { key: "C" }, "C"), 205 | // h("p", { key: "Y" }, "Y"), 206 | // h("p", { key: "E" }, "E"), 207 | // h("p", { key: "F" }, "F"), 208 | // h("p", { key: "G" }, "G"), 209 | // ]; 210 | 211 | // fix c 节点应该是 move 而不是删除之后重新创建的 212 | // const prevChildren = [ 213 | // h("p", { key: "A" }, "A"), 214 | // h("p", {}, "C"), 215 | // h("p", { key: "B" }, "B"), 216 | // h("p", { key: "D" }, "D"), 217 | // ]; 218 | 219 | // const nextChildren = [ 220 | // h("p", { key: "A" }, "A"), 221 | // h("p", { key: "B" }, "B"), 222 | // h("p", {}, "C"), 223 | // h("p", { key: "D" }, "D"), 224 | // ]; 225 | 226 | export default { 227 | name: "ArrayToArray", 228 | setup() { 229 | const isChange = ref(false); 230 | window.isChange = isChange; 231 | 232 | return { 233 | isChange, 234 | }; 235 | }, 236 | render() { 237 | const self = this; 238 | 239 | return self.isChange === true 240 | ? h("div", {}, nextChildren) 241 | : h("div", {}, prevChildren); 242 | }, 243 | }; 244 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 text 3 | 4 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 5 | const nextChildren = "newChildren"; 6 | const prevChildren = [ 7 | h("div", {}, "A"), h("div", {}, "B"), 8 | h("div", {}, "A"), h("div", {}, "B"), 9 | h("div", {}, "A"), h("div", {}, "B"), 10 | h("div", {}, "A"), h("div", {}, "B"), 11 | h("div", {}, "A"), h("div", {}, "B"), 12 | // h("div", {}, "A"), h("div", {}, "B"), 13 | // h("div", {}, "A"), h("div", {}, "B"), 14 | // h("div", {}, "A"), h("div", {}, "B"), 15 | // h("div", {}, "A"), h("div", {}, "B"), 16 | // h("div", {}, "A"), h("div", {}, "B"), 17 | // h("div", {}, "A"), h("div", {}, "B"), 18 | // h("div", {}, "A"), h("div", {}, "B"), 19 | // h("div", {}, "A"), h("div", {}, "B"), 20 | // h("div", {}, "A"), h("div", {}, "B"), 21 | // h("div", {}, "A"), h("div", {}, "B"), 22 | // h("div", {}, "A"), h("div", {}, "B"), 23 | // h("div", {}, "A"), h("div", {}, "B"), 24 | ]; 25 | 26 | export default { 27 | name: "ArrayToText", 28 | setup() { 29 | const isChange = ref(false); 30 | window.isChange = isChange; 31 | 32 | return { 33 | isChange, 34 | }; 35 | }, 36 | render() { 37 | const self = this; 38 | 39 | return self.isChange === true 40 | ? h("div", {}, nextChildren) 41 | : h("div", {}, prevChildren); 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // 新的是 array 2 | // 老的是 text 3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 4 | 5 | const prevChildren = "oldChild"; 6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")]; 7 | 8 | export default { 9 | name: "TextToArray", 10 | setup() { 11 | const isChange = ref(false); 12 | window.isChange = isChange; 13 | 14 | return { 15 | isChange, 16 | }; 17 | }, 18 | render() { 19 | const self = this; 20 | 21 | return self.isChange === true 22 | ? h("div", {}, nextChildren) 23 | : h("div", {}, prevChildren); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 新的是 text 2 | // 老的是 text 3 | import { ref, h } from "../../lib/guide-mini-vue.esm.js"; 4 | 5 | const prevChildren = "oldChild"; 6 | const nextChildren = "newChild"; 7 | 8 | export default { 9 | name: "TextToText", 10 | setup() { 11 | const isChange = ref(false); 12 | window.isChange = isChange; 13 | 14 | return { 15 | isChange, 16 | }; 17 | }, 18 | render() { 19 | const self = this; 20 | 21 | return self.isChange === true 22 | ? h("div", {}, nextChildren) 23 | : h("div", {}, prevChildren); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | 6 | setup() { 7 | const count = ref(0); 8 | window.count = count 9 | const onClick = () => { 10 | console.log(count) 11 | count.value++; 12 | }; 13 | 14 | const props = ref({ 15 | foo: "foo", 16 | bar: "bar", 17 | }); 18 | const onChangePropsDemo1 = () => { 19 | props.value.foo = "new-foo"; 20 | }; 21 | 22 | const onChangePropsDemo2 = () => { 23 | props.value.foo = undefined; 24 | }; 25 | 26 | const onChangePropsDemo3 = () => { 27 | props.value = { 28 | foo: "foo", 29 | }; 30 | }; 31 | 32 | return { 33 | count, 34 | onClick, 35 | onChangePropsDemo1, 36 | onChangePropsDemo2, 37 | onChangePropsDemo3, 38 | props, 39 | }; 40 | }, 41 | render() { 42 | return h( 43 | "div", 44 | { 45 | id: "root", 46 | ...this.props, 47 | }, 48 | [ 49 | h("div", {}, "count:" + this.count), 50 | h( 51 | "button", 52 | { 53 | onClick: this.onClick, 54 | }, 55 | "click" 56 | ), 57 | h( 58 | "button", 59 | { 60 | onClick: this.onChangePropsDemo1, 61 | }, 62 | "changeProps - 值改变了 - 修改" 63 | ), 64 | 65 | h( 66 | "button", 67 | { 68 | onClick: this.onChangePropsDemo2, 69 | }, 70 | "changeProps - 值变成了 undefined - 删除" 71 | ), 72 | 73 | h( 74 | "button", 75 | { 76 | onClick: this.onChangePropsDemo3, 77 | }, 78 | "changeProps - key 在新的里面没有了 - 删除" 79 | ), 80 | ] 81 | ); 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-mini-vue", 3 | "version": "1.0.0", 4 | "main": "lib/guide-mini-vue.cjs.js", 5 | "module": "lib/guide-mini-vue.esm.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "jest", 9 | "build": "rollup -c rollup.config.js" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.17.5", 13 | "@babel/preset-env": "^7.16.11", 14 | "@babel/preset-typescript": "^7.16.7", 15 | "@types/jest": "^27.4.1", 16 | "babel-jest": "^27.5.1", 17 | "jest": "^27.5.1", 18 | "typescript": "^4.6.2", 19 | "rollup": "^2.57.0", 20 | "tslib": "^2.3.1", 21 | "@rollup/plugin-typescript": "^8.2.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from "./package.json"; 2 | import typescript from "@rollup/plugin-typescript"; 3 | export default { 4 | input: "./src/index.ts", 5 | output: [ 6 | // 1. cjs -> commonjs 7 | // 2. esm 8 | { 9 | format: "cjs", 10 | file: pkg.main, 11 | }, 12 | { 13 | format: "es", 14 | file: pkg.module, 15 | }, 16 | ], 17 | 18 | plugins: [typescript()], 19 | }; 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resctivite' 2 | export * from './runtime-dom' -------------------------------------------------------------------------------- /src/resctivite/baseHandler.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../shared/shared" 2 | import { track, trigger } from "./effect" 3 | import { isReactive, reactive, readonly } from "./reactive" 4 | import { isRef } from "./ref" 5 | 6 | export const enum ReactiveFlags { 7 | IS_REACTIVE = "__v_isReactive", 8 | IS_READONLY = "__v_isReadonly", 9 | } 10 | 11 | const get = createGetter() 12 | const set = createSetter() 13 | const readonlyGet = createGetter(true) 14 | const showllGet = createGetter(true, true) 15 | 16 | 17 | /** 18 | * 创建Proxy get处理函数 19 | * @isReadonly 只读数据 默认为false 只读数据不需要进行依赖收集 20 | */ 21 | export function createGetter (isReadonly = false, IsShowll = false) { 22 | 23 | return function get (target: object, key: any) { 24 | 25 | if (key == ReactiveFlags.IS_READONLY) { 26 | return isReadonly 27 | } else if (key == ReactiveFlags.IS_REACTIVE) { 28 | return !isReadonly 29 | } 30 | 31 | let res = Reflect.get(target, key) 32 | 33 | /** 34 | * 浅代理只代理第一层属性 35 | */ 36 | if (IsShowll) { 37 | return res 38 | } 39 | 40 | /** 41 | * 如果访问的数据是object类型 42 | * 就根据当前isReadonly的状态代理不同的数据类型 43 | */ 44 | if (isObject(res)) { 45 | return isReadonly ? readonly(res) : reactive(res); 46 | } 47 | 48 | /* 如果是只读数据 就不需要依赖收集 因为在数据变化后不需要执行副作用函数* */ 49 | if (!isReadonly) { 50 | track(target, key) 51 | } 52 | 53 | return res 54 | } 55 | 56 | } 57 | 58 | /** 59 | * 创建Proxy set处理函数 60 | */ 61 | export function createSetter () { 62 | return function set (target: object, key: any, value: any) { 63 | let res = Reflect.set(target, key, value) 64 | trigger(target, key) 65 | 66 | return res 67 | } 68 | } 69 | 70 | 71 | /** 72 | * reactive get set 73 | */ 74 | export const mutableHandlers = { 75 | get, 76 | set, 77 | } 78 | 79 | /** 80 | * readonly get set 81 | */ 82 | export const readonlyHandlers = { 83 | get: readonlyGet, 84 | set (target: object, key: any, value: any) { 85 | console.warn( 86 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, 87 | target 88 | ); 89 | return true 90 | } 91 | } 92 | 93 | 94 | /** 95 | * proxyRefsHandlers get set 96 | */ 97 | export const proxyRefsHandlers = { 98 | get (target: object, key: any) { 99 | let res = Reflect.get(target, key) 100 | return isRef(res) ? res.value : res 101 | }, 102 | set (target: object, key: any, value: any) { 103 | 104 | if (isRef(target[key] && !isRef(value))) { 105 | return (target[key].value = value) 106 | } else { 107 | let res = Reflect.set(target, key,value) 108 | return res 109 | } 110 | 111 | } 112 | } 113 | 114 | /** 115 | * shallowReadonlyHandlers get set 116 | */ 117 | export const shallowReadonlyHandlers = { 118 | get: showllGet, 119 | set (target: object, key: any, value: any) { 120 | console.warn( 121 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, 122 | target 123 | ); 124 | return true 125 | } 126 | } -------------------------------------------------------------------------------- /src/resctivite/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect" 2 | 3 | 4 | export class ComputedImpl { 5 | 6 | public _value 7 | 8 | private _dirty: boolean = true; 9 | 10 | public effect: ReactiveEffect 11 | 12 | constructor(getter) { 13 | 14 | this.effect = new ReactiveEffect(getter, () => { 15 | /** 16 | * 通过scheduler参数 在数据变化后执行这个传入的函数修改this._dirty 的状态标记为true 17 | */ 18 | if (!this._dirty) { 19 | this._dirty = true 20 | } 21 | 22 | }) 23 | 24 | } 25 | 26 | get value () { 27 | /** 28 | * @ 通过this._dirty 标记是否初始化过this._value 的值 29 | * @ 读取过后将状态标记为false 后续依赖的数据没有变化就不再给this._value 重新赋值 直接将之前读取的返沪即可 30 | */ 31 | if (this._dirty) { 32 | this._dirty = false 33 | this._value = this.effect.run() 34 | } 35 | return this._value 36 | } 37 | 38 | } 39 | 40 | export function computed (fn: any) { 41 | const computed = new ComputedImpl(fn) 42 | return computed 43 | } -------------------------------------------------------------------------------- /src/resctivite/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "../shared/shared" 2 | 3 | type anyFn = () => any 4 | 5 | /** effect 函数的返回值 */ 6 | type effectFn = { 7 | (): anyFn, 8 | /** effect实例 */ 9 | effect: ReactiveEffect 10 | } 11 | 12 | /** 13 | * 存储响应式数据的副作用容器 14 | * @ targetMap Map数据结构 全局响应式对象的容器 15 | * @-- object Map数据结构 响应式对象 16 | * @-- key Set数据结构 响应式对象的key 通过key值找到对应的依赖 17 | */ 18 | const targetMap: Map> = new Map() 19 | 20 | let activeEffect: ReactiveEffect | undefined = undefined 21 | 22 | /** 是否需要收集依赖 */ 23 | let shouldTrack: boolean = false 24 | 25 | /** 26 | * 27 | * 一个effect 对应一个ReactiveEffect实例 28 | */ 29 | export class ReactiveEffect { 30 | /** 传入的fn*/ 31 | public fn: anyFn 32 | 33 | /** 配置参数 传入首次执行fn, 当数据变更后不会再执行fn而是调用scheduler */ 34 | public scheduler 35 | 36 | 37 | /** 存储副作用 */ 38 | public dep = [] 39 | 40 | /** 41 | * 是否没有执行过stop函数 42 | */ 43 | public active: boolean = true 44 | 45 | /** 46 | * 执行过stop函数后的回调 47 | */ 48 | public onStop 49 | 50 | constructor(fn: anyFn, scheduler?, onStop?) { 51 | this.scheduler = scheduler 52 | this.onStop = onStop 53 | this.fn = fn 54 | } 55 | 56 | /** 57 | * 58 | * 数据变化函数执行 59 | */ 60 | run (): anyFn { 61 | 62 | // 如果是关闭的状态就说明不在需要收集依赖 63 | if (!this.active) { 64 | return this.fn() 65 | } 66 | // 执行前将shouldTrack 开关 打开 67 | shouldTrack = true 68 | activeEffect = this 69 | /** 这里依赖收集 */ 70 | const result = this.fn() 71 | /* 收集完成过后将 shouldTrack 开关 关闭 **/ 72 | shouldTrack = false 73 | activeEffect = undefined; 74 | return result 75 | 76 | } 77 | 78 | 79 | /** 清除收集的依赖 后续数据变更后副作用不在执行 */ 80 | stop (): void { 81 | 82 | // 如果没有执行就执行 并且将stop 的状态关闭 83 | if (this.active) { 84 | cleanupEffect(this) 85 | // 将开关关闭 86 | this.active = false 87 | /**传入了onStop 函数就在执行过stop函数后执行onStop逻辑 */ 88 | if (this.onStop) { 89 | this.onStop() 90 | } 91 | } 92 | } 93 | 94 | } 95 | 96 | /** 97 | * @effect 收集的副作用函数 98 | * @ 清除收集的依赖函数 99 | */ 100 | export function cleanupEffect (effect): void { 101 | effect.dep.forEach((dep: Set) => { 102 | dep.delete(effect) 103 | }) 104 | } 105 | 106 | 107 | export function stop (runner: effectFn): void { 108 | runner.effect.stop() 109 | } 110 | 111 | 112 | /** 收集依赖 */ 113 | export function track (target: object, key: any) { 114 | 115 | if (!isTracking()) return 116 | 117 | let depsMap = targetMap.get(target) 118 | if (!depsMap) { 119 | depsMap = new Map() 120 | targetMap.set(target, depsMap) 121 | } 122 | let dep = depsMap.get(key) 123 | if (!dep) { 124 | dep = new Set() 125 | depsMap.set(key, dep) 126 | } 127 | dep.add(activeEffect) 128 | activeEffect.dep.push(dep) 129 | } 130 | /** 131 | * @dep 存储副作用 132 | * 收集依赖 133 | */ 134 | export function trackEffects (dep: Set): void { 135 | dep.add(activeEffect) 136 | activeEffect.dep.push(dep) 137 | } 138 | 139 | export function isTracking (): boolean { 140 | return activeEffect !== undefined && shouldTrack 141 | } 142 | 143 | 144 | /** 触发依赖 */ 145 | export function trigger (target: object, key: any) { 146 | 147 | let depsMap = targetMap.get(target) 148 | if (!depsMap) return 149 | let dep = depsMap.get(key) 150 | 151 | triggerEffects(dep) 152 | 153 | } 154 | 155 | /** 触发依赖 */ 156 | export function triggerEffects (dep): void { 157 | for (const effect of dep) { 158 | let _effect: ReactiveEffect = effect 159 | if (_effect.scheduler) { 160 | _effect.scheduler() 161 | } else { 162 | _effect.run() 163 | } 164 | } 165 | } 166 | 167 | 168 | /** 169 | * 170 | * 一个effect 对应一个ReactiveEffect实例 171 | * @fn 响应式数据变更后触发的函数 172 | */ 173 | export function effect (fn: anyFn, options: any = {} ): effectFn { 174 | 175 | // let _effect: ReactiveEffect = new ReactiveEffect(fn, options.scheduler, options.onStop) 176 | 177 | let _effect: ReactiveEffect = new ReactiveEffect(fn) 178 | extend(_effect, options) 179 | 180 | _effect.run() 181 | 182 | const runner = _effect.run.bind(_effect) 183 | 184 | runner.effect = _effect 185 | 186 | return runner 187 | 188 | } -------------------------------------------------------------------------------- /src/resctivite/index.ts: -------------------------------------------------------------------------------- 1 | export { ref, proxyRefs } from "./ref"; -------------------------------------------------------------------------------- /src/resctivite/reactive.ts: -------------------------------------------------------------------------------- 1 | import { mutableHandlers, ReactiveFlags, readonlyHandlers, shallowReadonlyHandlers } from "./baseHandler" 2 | 3 | 4 | /** 5 | * @raw 原始数据 object类型 6 | * 响应式对象 7 | */ 8 | export function reactive (raw: any): any { 9 | return creayeReactive(raw, mutableHandlers) 10 | } 11 | /** 检测是否是reactive 数据类型 12 | * @value 需要检查的数据 13 | */ 14 | export function isReactive (value): boolean { 15 | return !!value[ReactiveFlags.IS_REACTIVE] 16 | } 17 | 18 | /** 19 | * @raw 原始数据 object类型 20 | * 只读对象 21 | */ 22 | export function readonly (raw: any): any { 23 | return creayeReactive(raw, readonlyHandlers) 24 | } 25 | 26 | /** 27 | * 数据浅代理 代理第一层属性 28 | * @raw 需要被检测的数据 29 | */ 30 | export function shallowReadonly (raw: any): any { 31 | return creayeReactive(raw, shallowReadonlyHandlers) 32 | } 33 | 34 | /** 检测是否是 readonly 数据类型 35 | * @value 需要检查的数据 36 | */ 37 | export function isReadonly (value): boolean { 38 | return !!value[ReactiveFlags.IS_READONLY] 39 | } 40 | 41 | /** 42 | * @value 需要被检测的数据 43 | * 检测是否是 Proxy代理的数据 44 | */ 45 | export function isProxy (value): boolean { 46 | return isReactive(value) || isReadonly(value) 47 | } 48 | 49 | 50 | /** 51 | * 初始化Proxy 代理对象 52 | * @raw 需要代理的数据 53 | * @mutableHandlers Proxy代理对象的 get set 处理函数 54 | */ 55 | function creayeReactive (raw: object, mutableHandlers) { 56 | return new Proxy(raw, mutableHandlers) 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/resctivite/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from "../shared/shared" 2 | import { proxyRefsHandlers } from "./baseHandler" 3 | import { isTracking, trackEffects, triggerEffects } from "./effect" 4 | import { reactive } from "./reactive" 5 | 6 | /** 7 | * RefImpl 8 | * @value 需要代理的数据 9 | * ref class类 内部存储 副作用 10 | */ 11 | class RefImpl { 12 | 13 | public __v_isRef = true 14 | 15 | /** 包装过后的值 */ 16 | public _value 17 | 18 | /** 原始值 */ 19 | public _rawValue 20 | 21 | public dep = new Set() 22 | 23 | constructor(value) { 24 | 25 | this._rawValue = value 26 | this._value = convert(value) 27 | 28 | } 29 | 30 | get value () { 31 | /** 依赖收集 */ 32 | trackRefValue(this) 33 | return this._value 34 | } 35 | 36 | set value (newValue) { 37 | // 触发依赖 38 | if (hasChanged(this._rawValue, newValue)) { 39 | this._rawValue = newValue 40 | this._value = convert(newValue) 41 | triggerEffects(this.dep) 42 | } 43 | } 44 | 45 | } 46 | 47 | /** 48 | * 触发ref依赖收集 49 | */ 50 | function trackRefValue (ref): void { 51 | if (isTracking()) { 52 | trackEffects(ref.dep) 53 | } 54 | } 55 | 56 | /** 57 | * 如果传入了object 就使用reactive包装 反值返回值本身 58 | */ 59 | export function convert (value): any { 60 | return isObject(value) ? reactive(value) : value 61 | } 62 | 63 | 64 | /** 65 | * ref 66 | */ 67 | export function ref (value: any) { 68 | const ref = new RefImpl(value) 69 | return ref 70 | } 71 | 72 | /** 73 | * @ref 需要检测的数据 74 | * @ 数据是否是ref 类型 75 | */ 76 | export function isRef (ref: any): boolean { 77 | return !!ref.__v_isRef 78 | } 79 | 80 | /** 81 | * @value 需要检测的数据 82 | * @ 如果数据是 ref 返回ref.value 反值返回值本身 83 | */ 84 | export function unRef (value: any): boolean { 85 | return isRef(value) ? value.value : value 86 | } 87 | 88 | /** 89 | * 访问的数据是 ref 类型 就返回ref.value值 90 | */ 91 | export function proxyRefs (objectWithRefs: any): any { 92 | return new Proxy(objectWithRefs, proxyRefsHandlers) 93 | } -------------------------------------------------------------------------------- /src/resctivite/tests/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "../computed"; 2 | import { reactive } from "../reactive"; 3 | 4 | 5 | describe("computed", () => { 6 | it("happy path", () => { 7 | const user = reactive({ 8 | age: 1, 9 | }); 10 | const age = computed(() => { 11 | return user.age; 12 | }); 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; // get 34 | expect(getter).toHaveBeenCalledTimes(1); 35 | 36 | // should not compute until needed 37 | value.foo = 2; 38 | expect(getter).toHaveBeenCalledTimes(1); 39 | 40 | // now it should compute 41 | expect(cValue.value).toBe(2); 42 | expect(getter).toHaveBeenCalledTimes(2); 43 | 44 | // should not compute again 45 | cValue.value; 46 | expect(getter).toHaveBeenCalledTimes(2); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/resctivite/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { reactive } from "../reactive"; 3 | import { effect, stop } from '../effect' 4 | 5 | describe('effect' , () => { 6 | 7 | it('happy path ', () => { 8 | const user = reactive({ 9 | age: 10, 10 | }); 11 | 12 | let nextAge; 13 | effect(() => { 14 | nextAge = user.age + 1; 15 | }); 16 | 17 | expect(nextAge).toBe(11); 18 | 19 | // update 20 | user.age++; 21 | expect(nextAge).toBe(12); 22 | }) 23 | 24 | it("should return runner when call effect", () => { 25 | // 当调用 runner 的时候可以重新执行 effect.run 26 | // runner 的返回值就是用户给的 fn 的返回值 27 | let foo = 0; 28 | const runner = effect(() => { 29 | foo++; 30 | return foo; 31 | }); 32 | expect(foo).toBe(1); 33 | runner(); 34 | expect(foo).toBe(2); 35 | expect(runner()).toBe(3); 36 | 37 | }); 38 | 39 | 40 | it("scheduler", () => { 41 | let dummy; 42 | let run: any; 43 | const scheduler = jest.fn(() => { 44 | run = runner; 45 | }); 46 | const obj = reactive({ foo: 1 }); 47 | const runner = effect( 48 | () => { 49 | dummy = obj.foo; 50 | }, 51 | { scheduler } 52 | ); 53 | expect(scheduler).not.toHaveBeenCalled(); 54 | expect(dummy).toBe(1); 55 | // should be called on first trigger 56 | obj.foo++; 57 | expect(scheduler).toHaveBeenCalledTimes(1); 58 | // // should not run yet 59 | expect(dummy).toBe(1); 60 | // // manually run 61 | run(); 62 | // // should have run 63 | expect(dummy).toBe(2); 64 | }); 65 | 66 | 67 | it("stop", () => { 68 | let dummy; 69 | const obj = reactive({ prop: 1 }); 70 | const runner = effect(() => { 71 | dummy = obj.prop; 72 | }); 73 | obj.prop = 2; 74 | expect(dummy).toBe(2); 75 | stop(runner); 76 | obj.prop++; 77 | expect(dummy).toBe(2); 78 | // stopped effect should still be manually callable 79 | runner(); 80 | expect(dummy).toBe(3); 81 | }); 82 | 83 | 84 | it("onStop", () => { 85 | const obj = reactive({ 86 | foo: 1, 87 | }); 88 | const onStop = jest.fn(); 89 | let dummy; 90 | const runner = effect( 91 | () => { 92 | dummy = obj.foo; 93 | }, 94 | { 95 | onStop, 96 | } 97 | ); 98 | 99 | stop(runner); 100 | expect(onStop).toBeCalledTimes(1); 101 | }); 102 | 103 | }) -------------------------------------------------------------------------------- /src/resctivite/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { isReactive, reactive, isProxy } from "../reactive"; 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 | expect(isReactive(observed)).toBe(true); 10 | expect(isReactive(original)).toBe(false); 11 | expect(isProxy(observed)).toBe(true); 12 | }); 13 | 14 | test("nested reactives", () => { 15 | const original = { 16 | nested: { 17 | foo: 1, 18 | }, 19 | array: [{ bar: 2 }], 20 | }; 21 | const observed = reactive(original); 22 | expect(isReactive(observed.nested)).toBe(true); 23 | expect(isReactive(observed.array)).toBe(true); 24 | expect(isReactive(observed.array[0])).toBe(true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/resctivite/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReadonly, readonly } from "../reactive"; 2 | 3 | describe("readonly", () => { 4 | 5 | it("should make nested values readonly", () => { 6 | const original = { foo: 1, bar: { baz: 2 } }; 7 | const wrapped = readonly(original); 8 | expect(wrapped).not.toBe(original); 9 | expect(isReadonly(wrapped)).toBe(true); 10 | expect(isReadonly(original)).toBe(false); 11 | expect(isReadonly(wrapped.bar)).toBe(true); 12 | expect(isReadonly(original.bar)).toBe(false); 13 | expect(isProxy(wrapped)).toBe(true); 14 | expect(wrapped.foo).toBe(1); 15 | }); 16 | 17 | it("should call console.warn when set", () => { 18 | console.warn = jest.fn(); 19 | const user = readonly({ 20 | age: 10, 21 | }); 22 | 23 | user.age = 11; 24 | expect(console.warn).toHaveBeenCalled(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/resctivite/tests/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | import { isRef, ref ,unRef, proxyRefs } from "../ref"; 4 | describe("ref", () => { 5 | it("happy path", () => { 6 | const a = ref(1); 7 | expect(a.value).toBe(1); 8 | }); 9 | 10 | it("should be reactive", () => { 11 | const a = ref(1); 12 | let dummy; 13 | let calls = 0; 14 | effect(() => { 15 | calls++; 16 | dummy = a.value; 17 | }); 18 | expect(calls).toBe(1); 19 | expect(dummy).toBe(1); 20 | a.value = 2; 21 | expect(calls).toBe(2); 22 | expect(dummy).toBe(2); 23 | // // same value should not trigger 24 | a.value = 2; 25 | expect(calls).toBe(2); 26 | expect(dummy).toBe(2); 27 | a.value = 3; 28 | expect(calls).toBe(3); 29 | expect(dummy).toBe(3); 30 | }); 31 | 32 | it("should make nested properties reactive", () => { 33 | const a = ref({ 34 | count: 1, 35 | }); 36 | let dummy; 37 | effect(() => { 38 | dummy = a.value.count; 39 | }); 40 | expect(dummy).toBe(1); 41 | a.value.count = 2; 42 | expect(dummy).toBe(2); 43 | a.value.count = 3; 44 | expect(dummy).toBe(3); 45 | }); 46 | 47 | it("isRef", () => { 48 | const a = ref(1); 49 | const user = reactive({ 50 | age: 1, 51 | }); 52 | expect(isRef(a)).toBe(true); 53 | expect(isRef(1)).toBe(false); 54 | expect(isRef(user)).toBe(false); 55 | }); 56 | 57 | it("unRef", () => { 58 | const a = ref(1); 59 | expect(unRef(a)).toBe(1); 60 | expect(unRef(1)).toBe(1); 61 | }); 62 | 63 | it.skip("proxyRefs", () => { 64 | const user = { 65 | age: ref(10), 66 | name: "xiaohong", 67 | }; 68 | 69 | const proxyUser = proxyRefs(user); 70 | expect(user.age.value).toBe(10); 71 | expect(proxyUser.age).toBe(10); 72 | expect(proxyUser.name).toBe("xiaohong"); 73 | 74 | proxyUser.age = 20; 75 | 76 | expect(proxyUser.age).toBe(20); 77 | expect(user.age.value).toBe(20); 78 | 79 | proxyUser.age = ref(10); 80 | expect(proxyUser.age).toBe(10); 81 | expect(user.age.value).toBe(10); 82 | 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /src/resctivite/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, isReadonly, readonly, shallowReadonly } from "../reactive"; 2 | 3 | describe("shallowReadonly", () => { 4 | test("should not make non-reactive properties reactive", () => { 5 | const props = shallowReadonly({ n: { foo: 1 } }); 6 | expect(isReactive(props.n)).toBe(false); 7 | }); 8 | test("should differentiate from normal readonly calls", async () => { 9 | const original = { foo: {} }; 10 | const shallowProxy = shallowReadonly(original); 11 | const reactiveProxy = readonly(original); 12 | expect(shallowProxy).not.toBe(reactiveProxy); 13 | expect(isReadonly(shallowProxy.foo)).toBe(false); 14 | expect(isReadonly(reactiveProxy.foo)).toBe(true); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | export function provide (key: string, value: any) { 4 | const currentInstance = getCurrentInstance() 5 | // 当前组件的provides ==> 初始化时指向了父组件的provides 6 | if (currentInstance) { 7 | let { provides } = currentInstance 8 | let parentProvides = currentInstance.parent.provides 9 | // 初始化的时候 当前组件的provides 指向了父组件的provides 10 | // 每次执行Object.create的时候相当于重置了当前的组件的provides 的propotye所以只需要初始化一次即可 11 | if (provides == parentProvides) { 12 | // 将当前组件的 provides的原型指向父组件的 provides 13 | provides = currentInstance.provides = Object.create(parentProvides) 14 | } 15 | provides[key] = value 16 | } 17 | } 18 | 19 | /** 20 | * * @defaultValue 默认参数 也可以是函数 21 | */ 22 | export function inject (key: string, defaultValue: any) { 23 | const currentInstance = getCurrentInstance() 24 | if (currentInstance) { 25 | const { provides } = currentInstance 26 | if (key in provides) { 27 | return provides[key] 28 | } else { 29 | if (defaultValue) { 30 | if (typeof defaultValue === 'function') { 31 | return defaultValue() 32 | } 33 | return defaultValue 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { proxyRefs } from "../resctivite" 2 | import { shallowReadonly } from "../resctivite/reactive" 3 | import { isObject } from "../shared/shared" 4 | import { emit } from "./componentEmit" 5 | import { initProps } from "./componentProps" 6 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance" 7 | import { initSlots } from "./componentSlots" 8 | 9 | /** 临时存储组件的实例 */ 10 | let currentInstance = null 11 | 12 | 13 | /** 创建组件实例 将vnoe 上的数据 经过处理添加到 组件上 */ 14 | export function createComponentInstance(vnode: any, parent) { 15 | const component = { 16 | /** 组件是否已经被挂载过 */ 17 | isMounted: false, 18 | /** 虚拟节点 */ 19 | vnode, 20 | /** */ 21 | type: vnode.type, 22 | /** 组件的属性 */ 23 | props: {}, 24 | /** 组件setup 返回的数据 */ 25 | setupState: {}, 26 | /** 组件代理对象 方便用户 直接通过 this.xxx 访问组件的数据 */ 27 | proxy: null, 28 | /** 组件emit 事件 */ 29 | emit: () => {}, 30 | /** 组件的插槽 */ 31 | slots: [], 32 | /** 当前组件的依赖注入 */ 33 | provides: parent ? parent.provides : {}, 34 | /** 当前组件得父级组件 */ 35 | parent, 36 | } 37 | /** 重写emit方法 将参数1传入emit内 */ 38 | component.emit = emit.bind(null, component) 39 | return component 40 | } 41 | 42 | /** 初始化组件 初始化对应的 props slots 初始化渲染函数 */ 43 | export function setupComponent(instance: any) { 44 | // 初始化属性 45 | initProps(instance, instance.vnode.props) 46 | 47 | // 初始化插槽 48 | initSlots(instance, instance.vnode.children) 49 | 50 | // 设置组件渲染函数 51 | setupStatefulComponent(instance) 52 | } 53 | 54 | /** 初始化有状态的组件的 调用组件的 */ 55 | function setupStatefulComponent(instance: any) { 56 | /** 组件 */ 57 | const Component = instance.type 58 | 59 | const { setup } = Component 60 | 61 | // 创建组件的代理对象 62 | instance.proxy = new Proxy({_: instance}, PublicInstanceProxyHandlers) 63 | 64 | if (setup) { 65 | setCurrentInstance(instance) 66 | let setupResult = setup(shallowReadonly(instance.props), { 67 | emit: instance.emit 68 | }) 69 | setCurrentInstance(null) 70 | handleSetupResult(instance, setupResult) 71 | } 72 | } 73 | 74 | <<<<<<< HEAD 75 | /** 76 | * 处理组件返回值 77 | */ 78 | function handleSetupResult(instance, setupResult: any) { 79 | console.log('处理setup函数的返回值', setupResult) 80 | 81 | // TODO function 82 | 83 | // object 84 | if (typeof setupResult == 'object') { 85 | instance.setupState = setupResult 86 | console.log('=====返回值是object 就给组件实例上添加setupState 属性==== [git] ==') 87 | ======= 88 | function handleSetupResult(instance: any, setupResult: any) { 89 | if (isObject(setupResult)) { 90 | instance.setupState = proxyRefs(setupResult) 91 | } else { 92 | >>>>>>> lb-dev 93 | } 94 | finishComponentSetup(instance) 95 | } 96 | 97 | function finishComponentSetup(instance: any) { 98 | const Component = instance.type 99 | instance.render = Component.render 100 | } 101 | 102 | 103 | /** 104 | * 获取当前组件的实例 105 | * 函数会在创建组件实例的时候执行 106 | */ 107 | export function getCurrentInstance () { 108 | return currentInstance 109 | } 110 | 111 | /** 112 | * 通过这个函数修改 组件的实例 113 | */ 114 | function setCurrentInstance (value) { 115 | currentInstance = value 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | function capitalize (str: string): string { 2 | return str ? str.charAt(0).toUpperCase() + str.slice(1) : '' 3 | } 4 | function camelize (str: string) { 5 | return str.replace(/-(\w)/g, (_, s: string)=> { 6 | return s ? s.toUpperCase() : '' 7 | }) 8 | } 9 | function toHandelKey(str: string) { 10 | return str ? 'on' + camelize(str) : '' 11 | } 12 | 13 | export function emit (instance, event: string, ...args) { 14 | 15 | const { props } = instance 16 | 17 | let handelKey = toHandelKey(capitalize(event)) 18 | 19 | let handle = props[handelKey] 20 | 21 | handle && handle(...args) 22 | } -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance: any, rawProps: any) { 2 | // 将组件的虚拟节点的props添加到 组件实力上的props 如果组件的虚拟节点的props不存在就添加一个默认值 3 | instance.props = rawProps || {} 4 | } -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from "../shared/shared" 2 | 3 | const publicPropertiesMap = { 4 | $el: (instance) => instance.vnode.el, 5 | $slots: (instance)=> instance.slots 6 | } 7 | 8 | export const PublicInstanceProxyHandlers = { 9 | 10 | get ({ _: instance }: any, key: any) { 11 | 12 | const { setupState ,props } = instance 13 | if (hasOwn(setupState, key)) { 14 | return setupState[key] 15 | } else if (hasOwn(props, key)) { 16 | return props[key] 17 | } 18 | 19 | const publicGetter = publicPropertiesMap[key] 20 | if (publicGetter) { 21 | return publicGetter(instance) 22 | } 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlages } from "../shared/ShapeFlages"; 2 | import { isArray } from "../shared/shared"; 3 | export function initSlots (instance: any, children: any) { 4 | let { vnode } = instance 5 | if (vnode.shapeflag & ShapeFlages.SLOTS_CHILDREN) { 6 | normalizeObjectSlots(children, instance.slots) 7 | } 8 | } 9 | 10 | function normalizeObjectSlots(children, slots) { 11 | for (const key in children) { 12 | let value = children[key]; 13 | slots[key] = (props) => normalizeSlotValue(value(props)) 14 | } 15 | } 16 | 17 | function normalizeSlotValue (value) { 18 | return isArray(value) ? value : [ value ] 19 | } -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode" 2 | 3 | export function createAppAPI (render) { 4 | return function createApp (rootComponent: any) { 5 | return { 6 | mount (rootContainer: Element) { 7 | 8 | const vnode = createVNode(rootComponent) 9 | 10 | render(vnode, rootContainer) 11 | } 12 | } 13 | } 14 | } 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | export function h (type: any, props?: object, children?: any) { 3 | return createVNode(type, props, children) 4 | } -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from "../vnode"; 2 | export function renderSlots (slots: any, name: string, props) { 3 | let slot = slots[name] 4 | if (slot) { 5 | if (typeof slot === 'function') { 6 | return createVNode(Fragment, {}, slot(props)) 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | export { h } from './h' 2 | export { renderSlots } from './helpers/renderSlots' 3 | export { createTextVNode } from './vnode' 4 | export { getCurrentInstance } from './component' 5 | export { provide, inject } from './apiInject' 6 | export { createRenderer } from './render' -------------------------------------------------------------------------------- /src/runtime-core/render.ts: -------------------------------------------------------------------------------- 1 | import { createAppAPI } from './createApp' 2 | import { ShapeFlages } from "../shared/ShapeFlages" 3 | import { createComponentInstance, setupComponent } from "./component" 4 | import { Fragment, Text } from "./vnode" 5 | import { effect } from '../resctivite/effect' 6 | function isSomeVNodeType(n1, n2) { 7 | return n1.type === n2.type && n1.key === n2.key; 8 | } 9 | export function createRenderer(options) { 10 | 11 | const { 12 | createElement: hostCreateElement, 13 | patchProp: hostPatchProp, 14 | insert: hostInsert, 15 | setElementText: hostSetElementText, 16 | removeElement: hostRemoveElement, 17 | } = options 18 | 19 | function render(vnode: any, container: Element, parentInstance, anchor) { 20 | patch(null, vnode, container, parentInstance, anchor) 21 | } 22 | 23 | function patch(n1, n2: any, container: Element, parentInstance, anchor) { 24 | 25 | const { shapeflag, type} = n2 26 | switch (type) { 27 | case Fragment: 28 | processFrament(n1, n2, container, parentInstance, anchor) 29 | break; 30 | case Text: 31 | processText(n1, n2, container) 32 | break; 33 | 34 | default: 35 | if (shapeflag & ShapeFlages.ELEMENT) { 36 | // 处理元素 37 | processElement(n1, n2, container, parentInstance, anchor) 38 | } else if (shapeflag & ShapeFlages.STATEFUL_COMPONENT) { 39 | // 处理组件 40 | processComponent(n1, n2, container, parentInstance, anchor) 41 | } 42 | break; 43 | } 44 | 45 | } 46 | 47 | /** 处理组件 */ 48 | function processComponent(n1, n2: any, container: Element, parentInstance, anchor) { 49 | mountComponent(n2, container, parentInstance, anchor) 50 | } 51 | 52 | /** 挂载元素 */ 53 | function mountComponent(vnode: any, container: Element, parentInstance, anchor) { 54 | const instance = createComponentInstance(vnode, parentInstance) 55 | 56 | // 设置组件的属性、插槽、render函数 57 | setupComponent(instance) 58 | 59 | // 设置组件更新 60 | setupRenderEffect(instance, vnode, container, anchor) 61 | } 62 | 63 | /** 组件更新 */ 64 | function setupRenderEffect(instance: any, vnode: any, container: Element, anchor) { 65 | effect(() => { 66 | if (!instance.isMounted) { 67 | const { proxy } = instance 68 | // 修改render函数的this 指向组件的代理对象 就可以直接通过 this.xxx访问组件的数据了 69 | const subTree = (instance.subTree = instance.render.call(proxy)) 70 | patch(null, subTree, container, instance, anchor) 71 | vnode.el = subTree.el 72 | instance.isMounted = true 73 | } else { 74 | const { proxy } = instance 75 | // 修改render函数的this 指向组件的代理对象 就可以直接通过 this.xxx访问组件的数据了 76 | /** 新的虚拟节点 */ 77 | const subTree = instance.render.call(proxy) 78 | /** 老的虚拟节点 */ 79 | const prevSubTree = instance.subTree 80 | instance.subTree = subTree; 81 | // 更新 subTree 82 | patch(prevSubTree, subTree, container, instance, anchor) 83 | } 84 | }) 85 | } 86 | 87 | 88 | /** 处理元素 */ 89 | function processElement(n1, n2: any, container: Element, parentInstance, anchor) { 90 | if (n1) { 91 | patchElement(n1, n2, container, parentInstance, anchor) 92 | } else { 93 | mountElement(n2, container, parentInstance, anchor) 94 | } 95 | } 96 | 97 | const TEMP_OBJECT = {} 98 | 99 | function patchElement (n1: any, n2: any, container: Element, parentInstance, anchor) { 100 | // 节点得属性可能不存在 props 需要初始化一个默认值 101 | // 默认值 指向同一个对象 102 | // 方便处理属性时做优化 103 | // 如果新旧属性都是同一个对象则不需要循环处理 104 | const prevProps = n1.props || TEMP_OBJECT 105 | const nextProps = n2.props || TEMP_OBJECT 106 | const el = (n2.el = n1.el); 107 | patchChildren(n1, n2, el, parentInstance, anchor) 108 | patchProps(el, prevProps, nextProps) 109 | } 110 | 111 | /** 比对新旧节点的props 更新对应的属性*/ 112 | function patchProps (el, oldProps, newProps) { 113 | 114 | if (newProps !== oldProps) { 115 | /** 新的属性更旧的不同更改 */ 116 | for (const key in newProps) { 117 | const newValue = newProps[key] 118 | const oldValue = oldProps[key] 119 | if (newValue !== oldValue) { 120 | hostPatchProp(el, key, oldValue, newValue) 121 | } 122 | } 123 | } 124 | // 如果老的节点是TEMP_OBJECT 说明节点是初始化得属性不需要循环 125 | if (oldProps !== TEMP_OBJECT) { 126 | // 老的属性再新得中不存在 循环老的属性即可 127 | for (const key in oldProps) { 128 | if (!(key in newProps)) { 129 | hostPatchProp(el, key, oldProps[key], null) 130 | } 131 | } 132 | } 133 | 134 | } 135 | /** 比对 children */ 136 | function patchChildren (n1, n2, container: Element, parentInstance, anchor) { 137 | // 新节点 138 | const newShapeflag = n2.shapeflag 139 | const newChildren = n2.children 140 | 141 | // 老节点 142 | const oldShapeflag = n1.shapeflag 143 | const oldChildren = n1.children 144 | 145 | if (newShapeflag & ShapeFlages.TEXT_CHILDREN) { 146 | if (oldShapeflag & ShapeFlages.ARRAY_CHILDREN) { 147 | unmountChildren(oldChildren) 148 | } 149 | if (newChildren !== oldChildren) { 150 | hostSetElementText(container, newChildren) 151 | } 152 | } else { 153 | if (oldShapeflag & ShapeFlages.TEXT_CHILDREN) { 154 | hostSetElementText(container, '') 155 | mountChildren(n2.children, container, parentInstance, anchor) 156 | } else { 157 | patchKeyedChildren(oldChildren, newChildren, container, parentInstance, anchor) 158 | } 159 | } 160 | 161 | } 162 | 163 | function patchKeyedChildren(c1: any, c2: any, container: Element, parentInstance: any, anchor) { 164 | let l2 = c2.length 165 | let i = 0; 166 | let e1 = c1.length - 1 167 | let e2 = c2.length - 1 168 | 169 | // 1. 左侧的对比 170 | while ( i <= e1 && i <= e2) { 171 | const n1 = c1[i] 172 | const n2 = c2[i] 173 | if (isSomeVNodeType(n1, n2)) { 174 | // type key 相同比对属性你 175 | patch(n1, n2, container, parentInstance, anchor) 176 | } else { 177 | break 178 | } 179 | i++ 180 | } 181 | 182 | // 右侧的对比 183 | while ( i <= e1 && i <= e2) { 184 | const n1 = c1[e1] 185 | const n2 = c2[e2] 186 | if (isSomeVNodeType(n1, n2)) { 187 | patch(n1, n2, container, parentInstance, anchor); 188 | } else { 189 | break 190 | } 191 | e1-- 192 | e2-- 193 | } 194 | 195 | // 3. 新的比老的长 196 | // 创建新的 ==> mountElement 197 | if (i > e1) { 198 | if (i <= e2) { 199 | const nextPos = e2 + 1; 200 | const anchor = nextPos < l2 ? c2[nextPos].el : null; 201 | while (i <= e2) { 202 | let n2 = c2[i] 203 | patch(null, n2, container, parentInstance, anchor) 204 | i++ 205 | } 206 | } 207 | } else if (i > e2){ 208 | // l老的比新的长 删除 209 | while(i <= e1) { 210 | hostRemoveElement(c1[i].el) 211 | i++ 212 | } 213 | } else { 214 | 215 | 216 | let s1 = i 217 | let s2 = i 218 | const keyToNewIndexMap: Map< string, number > = new Map() 219 | 220 | // 获取新的节点中部分总数 221 | // 如果新的比对完成 多余的直接删除即可 222 | const toBePatched = e2 - s2 + 1 223 | let patched = 0 224 | // 初始化 225 | const newIndexToOldIndexMap = new Array(toBePatched) 226 | 227 | for (let index = 0; index < toBePatched; index++) { 228 | newIndexToOldIndexMap[index] = 0; 229 | } 230 | 231 | 232 | console.log(newIndexToOldIndexMap) 233 | 234 | for (let index = s2; index <= e2; index++) { 235 | keyToNewIndexMap.set(c2[index].key, index) 236 | } 237 | 238 | // 循环老节点 是否存在新的节点中 239 | for (let index = s1; index <= e1; index++) { 240 | let prevChild = c1[index] 241 | 242 | // 新的节点对比完成 多余的老节点直接删除 243 | if (patched >= toBePatched) { 244 | hostRemoveElement(prevChild.el) 245 | continue 246 | } 247 | 248 | let newIndex 249 | if (prevChild.key != null) { 250 | newIndex = keyToNewIndexMap.get(prevChild.key) 251 | } else { 252 | for (let j = s2; j < e2; j++) { 253 | if (isSomeVNodeType(prevChild, c2[j])) { 254 | newIndex = j 255 | break 256 | } 257 | } 258 | } 259 | 260 | // 老的不在新的里面删除 261 | if (newIndex === undefined) { 262 | hostRemoveElement(prevChild.el) 263 | } else { 264 | // 265 | newIndexToOldIndexMap[ newIndex - s2 ] = index + 1 266 | // 老的再新的的里面 比对props children 267 | patch(prevChild, c2[newIndex], container, parentInstance, null) 268 | console.log(newIndexToOldIndexMap) 269 | patched++ 270 | } 271 | 272 | 273 | } 274 | 275 | const increasingNewIndexSequence = getSequence(newIndexToOldIndexMap) 276 | let j: number = increasingNewIndexSequence.length - 1 277 | for (let index = toBePatched; index >= 0 ; index--) { 278 | const nextIndex = i + s2; 279 | const nextChild = c2[nextIndex]; 280 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null; 281 | if (index !== increasingNewIndexSequence[j]) { 282 | console.log('ismove', nextChild.el) 283 | console.log('ismove', nextChild.el) 284 | debugger 285 | hostInsert(nextChild.el, container, anchor); 286 | } else { 287 | j-- 288 | } 289 | } 290 | 291 | } 292 | 293 | 294 | } 295 | 296 | function mountElement( vnode: any, container: Element, parentInstance, anchor) { 297 | const { type, children, props, shapeflag } = vnode 298 | // 创建元素 299 | const el: Element = ( vnode.el = hostCreateElement(type)) 300 | 301 | // 文本节点 302 | if (shapeflag & ShapeFlages.TEXT_CHILDREN) { 303 | hostSetElementText(el, children) 304 | } else if (shapeflag & ShapeFlages.ARRAY_CHILDREN) { // 数组节点 305 | mountChildren(vnode.children, el, parentInstance, anchor) 306 | } 307 | 308 | for (const key in props) { 309 | const value = props[key] 310 | hostPatchProp(el, key, null, value) 311 | } 312 | 313 | hostInsert(el, container, anchor) 314 | } 315 | 316 | function unmountChildren (children: any) { 317 | children.forEach(vnode => { 318 | let el: Element = vnode.el 319 | hostRemoveElement(el) 320 | }) 321 | } 322 | 323 | 324 | function mountChildren(n2: any[], container: Element, parentInstance, anchor) { 325 | n2.forEach((vnode) => { 326 | patch(null, vnode, container, parentInstance, anchor) 327 | }) 328 | } 329 | 330 | 331 | /** 处理Frament */ 332 | function processFrament(n1, n2: any, container: Element, parentInstance, anchor) { 333 | /** 只需要渲染 children 部分 */ 334 | mountChildren(n2.children, container, parentInstance, anchor) 335 | } 336 | 337 | /** 处理Text */ 338 | function processText(n1, n2: any, container: Element) { 339 | const el = (n2.el = document.createTextNode(n2.children)) 340 | container.append(el) 341 | } 342 | 343 | return { 344 | createApp: createAppAPI(render) 345 | } 346 | } 347 | 348 | 349 | 350 | 351 | 352 | /** 353 | *最长递增子序列 354 | */ 355 | function getSequence(arr) { 356 | const p = arr.slice(); 357 | const result = [0]; 358 | let i, j, u, v, c; 359 | const len = arr.length; 360 | for (i = 0; i < len; i++) { 361 | const arrI = arr[i]; 362 | if (arrI !== 0) { 363 | j = result[result.length - 1]; 364 | if (arr[j] < arrI) { 365 | p[i] = j; 366 | result.push(i); 367 | continue; 368 | } 369 | u = 0; 370 | v = result.length - 1; 371 | while (u < v) { 372 | c = (u + v) >> 1; 373 | if (arr[result[c]] < arrI) { 374 | u = c + 1; 375 | } else { 376 | v = c; 377 | } 378 | } 379 | if (arrI < arr[result[u]]) { 380 | if (u > 0) { 381 | p[i] = result[u - 1]; 382 | } 383 | result[u] = i; 384 | } 385 | } 386 | } 387 | u = result.length; 388 | v = result[u - 1]; 389 | while (u-- > 0) { 390 | result[u] = v; 391 | v = p[v]; 392 | } 393 | return result; 394 | } 395 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlages } from "../shared/ShapeFlages" 2 | import { isArray } from "../shared/shared" 3 | import { h } from "./h" 4 | export const Fragment = Symbol('Fragment') 5 | export const Text = Symbol('Text') 6 | 7 | export function createVNode(type: any, props?, children?){ 8 | const vnode = { 9 | /**根据这个值判断 这个虚拟节点是组件还是 elemnt 类型 */ 10 | type: type, 11 | /** 属性 */ 12 | props, 13 | /** 子元素 */ 14 | children, 15 | /** 虚拟节点挂载的元素 */ 16 | el: null, 17 | shapeflag: getShapeFlag(type), 18 | key: props && props.key 19 | } 20 | 21 | // 继续判断children 是数组类型 还是 string 类型 设置shapeflag 22 | updateVnodeShapeFlag(vnode, children) 23 | return vnode 24 | } 25 | 26 | function getShapeFlag(type: any) { 27 | return typeof type === 'string' ? ShapeFlages.ELEMENT : ShapeFlages.STATEFUL_COMPONENT 28 | } 29 | 30 | function updateVnodeShapeFlag (vnode, children) { 31 | if (typeof children === 'string') { 32 | vnode.shapeflag |= ShapeFlages.TEXT_CHILDREN 33 | } else if (isArray(children)) { 34 | vnode.shapeflag |= ShapeFlages.ARRAY_CHILDREN 35 | } 36 | // 1 节点需要是组件类型 37 | // 2 CHILDREN 是一个对象类型 38 | if (vnode.shapeflag & ShapeFlages.STATEFUL_COMPONENT) { 39 | if (typeof children === 'object') { 40 | vnode.shapeflag |= ShapeFlages.SLOTS_CHILDREN 41 | } 42 | } 43 | } 44 | 45 | /** 创建文本虚拟节点 */ 46 | export function createTextVNode (children: string) { 47 | return h(Text, {}, children) 48 | } 49 | -------------------------------------------------------------------------------- /src/runtime-core/vue.dt.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 虚拟节点 */ 3 | export type VNode = { 4 | /** type 是sting 标识是dom元素 */ 5 | type: string | object, 6 | /** 组件属性 或者 dom元素属性 */ 7 | props?: {}, 8 | /** 子元素 或者 组件类型 */ 9 | children?: string | Array 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../runtime-core/render" 2 | /** 是否是以 on开头 */ 3 | const isOn = (key: string) => /^on[A-Z]/.test(key) 4 | // 获取事件名称 5 | const getEventName = (key: string) => key.slice(2).toLocaleLowerCase() 6 | 7 | 8 | function createElement(type) { 9 | return document.createElement(type) 10 | } 11 | 12 | function patchProp(el: Element, key, prevValue, newValue) { 13 | if (isOn(key)) { 14 | let event = getEventName(key) 15 | el.addEventListener(event, newValue) 16 | } else { 17 | el.setAttribute(key, newValue) 18 | } 19 | // 新得是 null || undefined 删除属性 20 | if (newValue == null || newValue == undefined) { 21 | el.removeAttribute(key) 22 | } 23 | 24 | } 25 | 26 | function insert (el: Element, parent: Element, anchor) { 27 | // parent.append(el) 28 | parent.insertBefore(el, anchor || null) 29 | } 30 | 31 | 32 | function removeElement (el) { 33 | el.parentNode.removeChild(el) 34 | } 35 | 36 | function setElementText (el, text) { 37 | el.textContent = text; 38 | } 39 | 40 | const renderer: any = createRenderer({ 41 | createElement, 42 | patchProp, 43 | insert, 44 | setElementText, 45 | removeElement 46 | }) 47 | 48 | export function createApp(...args) { 49 | return renderer.createApp(...args); 50 | } 51 | export * from '../runtime-core/index' -------------------------------------------------------------------------------- /src/shared/ShapeFlages.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlages { 2 | /** 元素类型 */ 3 | ELEMENT = 1, 4 | /** 组件类型 */ 5 | STATEFUL_COMPONENT = 1 << 1 , 6 | /** children 是string类型 */ 7 | TEXT_CHILDREN = 1 << 2, 8 | /** children 数组类型 */ 9 | ARRAY_CHILDREN = 1 << 3, 10 | /** 插槽 */ 11 | SLOTS_CHILDREN = 1 << 4 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/shared/shared.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 数据是否是对象 4 | * @data 数据源 5 | */ 6 | export function isObject (data) { 7 | return typeof data == 'object' && data !== null 8 | } 9 | /** 10 | * 数据是否发生变化 11 | * @value 旧值 12 | * @newValue 新值 13 | */ 14 | export function hasChanged (value: any, newValue: any): boolean { 15 | return !Object.is(value, newValue) 16 | } 17 | 18 | /*** 19 | * 检测属性是否存在在当前对象上 20 | * @value object 21 | * @key 属性 22 | */ 23 | export const hasOwn = (value, key: string) => Object.prototype.hasOwnProperty.call(value, key) 24 | 25 | /** 26 | * 属性是否是以on开头 27 | * @key 属性key' 28 | */ 29 | export function isON (key: string) { 30 | return /^on[A-Z]/.test(key) 31 | } 32 | 33 | 34 | /** 35 | * 首字母转大写 36 | * @key 英文字母 37 | */ 38 | export const capitalize = (key: string): string => { 39 | return key.charAt(0).toLocaleUpperCase() + key.slice(1) 40 | } 41 | 42 | /** 43 | * add-foo ==>> addFoo 转驼峰写法 44 | * @str 45 | */ 46 | export const camelize = (str: string) => { 47 | return str.replace(/-(\w)/g, (_, c: string) => { 48 | return c ? c.toUpperCase() : ""; 49 | }) 50 | } 51 | 52 | export const toHandlerKey = (str: string) => { 53 | return str ? "on" + capitalize(str) : ""; 54 | }; 55 | 56 | 57 | /** 58 | * 对象合并 59 | */ 60 | export const extend = Object.assign 61 | 62 | /** 63 | * 是否是数组 64 | */ 65 | export const isArray = Array.isArray 66 | -------------------------------------------------------------------------------- /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": false, /* Enable all strict type-checking options. */ 78 | // "noImplicitAny": true, /* 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 | -------------------------------------------------------------------------------- /vueSetupFlow/INIT_VUE_FLOW.MD: -------------------------------------------------------------------------------- 1 | 2 | ## mini-ve 执行顺序 3 | 4 | ```js 5 | createrApp(rootComponent) 6 | .mount(container) // mount中开始执行 createVNode 和 render 7 | 8 | /** * 返回虚拟节点*/ 9 | ==> createVNode(rootComponent) ==> vnode 10 | // 将createVNode 返回的虚拟节点 交给render 函数处理 11 | 12 | /** * 通过 vnode 进行进一步的处理 */ 13 | ==> render(vnode, rootComponent) 14 | 15 | // 执行patch 这里开始区分vnode 是组件类型 还是elment 类型 16 | // 根据不同的类型做不同的处理 17 | ==> patch(vnode, container) 18 | // vnode, container 19 | 组件类型 ==> processComponent( vnode, container) 20 | 21 | // 挂载组件 vnode, container 22 | ==> mountComponent( vnode, container ) 23 | 24 | // 创建组件实例 25 | ==> createComponentInstance(vnode) // 返回组件实例 26 | 27 | // 设置组件的属性、插槽、render函数 28 | ==> setupComponent(instance) 29 | // 属性 30 | ==> initProps(....) 31 | ==> 32 | // 插槽 33 | ==> initSlots(....) 34 | ==> normalizeObjectSlots(children, instance.slots) 35 | ==> normalizeSlotValue() 36 | 37 | ==> setupStatefulComponent(instance) // 初始化有状态的组件 38 | 39 | // 处理 setup 函数返回的数据 40 | ==> handleSetupResult(instance, setupResult) 41 | 42 | // 设置组件的render 函数 确保组件拥有渲染函数 43 | ==> finishComponentSetup(instance) 44 | // 设置组件的更新 执行组件的render函数的得到 虚拟节点 subTree 45 | ==> setupRenderEffect(instance, container) 46 | // 将组件内的 render函数的返回值 交给patch 继续递归处理 47 | ==> patch(subTree, container) 48 | 49 | // vnode, container 50 | 元素类型 ==> processElement(vnode, container) 51 | // 初始化挂载元素 52 | // 函数内部根据 虚拟节点的类型创建 元素 53 | // 1.1 如果节点的 children是string 类型直接设置元素的textContent即可 54 | // 1.2 如果元素children是array 就循环children 调用patch函数区分虚拟节点是组件还是普通元素, 递归处理 55 | // 1.3 根据传入props 设置元素的属性 56 | ==> mountElement(vnode, container) 57 | ``` 58 | 59 | 60 | ## 使用位运算标识组件类型 61 | 62 | ```js 63 | // 64 | // 元素类型 65 | ELEMENT = 1, // 0001 66 | // 组件类型 67 | STATEFUL_COMPONENT = 1 << 1 , // 0010 68 | // 文本类型 69 | TEXT_CHILDREN = 1 << 2, // 0100 70 | // 数组类型 71 | ARRAY_CHILDREN = 1 << 3 // 1000 72 | 0010 73 | 1000 74 | 0000 75 | /** 76 | * | 两位都为0 才为0 77 | * & 两位都为1 才为1 78 | * 79 | * 80 | * 通过 |运算符 修改 81 | * 0000 82 | * | 0001 83 | * ==>0001 修改为 elemnt类型 84 | * 85 | * 0000 86 | * | 0010 87 | * ==> 0010 修改为 组件类型 88 | * 89 | * // 既是元素 children又是 string 90 | * 0001 91 | * | 0100 92 | * ==>0101 93 | * ......... 94 | * 95 | * 96 | * 通过 &运算符 查询 97 | * 0001 98 | * & 0001 99 | * ==>0000 100 | * 101 | * // 判断既是元素 children又是 string的类型 102 | * 0101 103 | * & 0001 104 | * ==>0001 ==> element 类型 105 | * 106 | */ 107 | // 1 createVnode 在初始化虚拟节点的时候区分节点的类型 108 | // 2 109 | createVNode(type, props, children) 110 | // type 为对象说明节点是 组件类型初步设置为 STATEFUL_COMPONENT ==>> 0010 111 | // type 为string 说明是 元素类型初步设置为 ELEMENT ==>> 0001 112 | ==> getShapeFlag(type) // 设置节点类型 113 | // 在根据子元素的类型设置 flage的标记 114 | // 如果 flage为element children 为string 则设置为 0101 115 | // 如果 flage为element children 为array 则设置为 1001 116 | ==> updateVnodeShapeFlag(vnode, children) 117 | ``` 118 | 119 | 120 | ## 获取组件实例 121 | 122 | ```js 123 | // 这个函数只能在setup中调用 124 | // 125 | 126 | /** 临时存储组件的实例 */ 127 | let currentInstance = null 128 | /** 初始化有状态的组件的 调用组件的 */ 129 | function setupStatefulComponent(instance: any) { 130 | /** 组件 */ 131 | const Component = instance.type 132 | 133 | const { setup } = Component 134 | 135 | // 创建组件的代理对象 136 | instance.proxy = new Proxy({_: instance}, PublicInstanceProxyHandlers) 137 | 138 | if (setup) { 139 | setCurrentInstance(instance) 140 | let setupResult = setup(shallowReadonly(instance.props), { 141 | emit: instance.emit 142 | }) 143 | setCurrentInstance(null) 144 | handleSetupResult(instance, setupResult) 145 | } 146 | } 147 | 148 | /** 149 | * 获取当前组件的实例 150 | * 函数会在创建组件实例的时候执行 151 | */ 152 | export function getCurrentInstance () { 153 | return currentInstance 154 | } 155 | 156 | /** 157 | * 通过这个函数修改 组件的实例 158 | */ 159 | function setCurrentInstance (value) { 160 | currentInstance = value 161 | } 162 | ``` 163 | 164 | 165 | ## 依赖注入 provide inject 166 | - 父组件通过 provide 注入数据 后代组件可以通过provide 获取 167 | - 子组件访问父组件的 provides 数据 168 | #### provide 169 | ```js 170 | export function provide (key: string, value: any) { 171 | const currentInstance = getCurrentInstance() 172 | // 当前组件的provides ==> 初始化时指向了父组件的provides 173 | if (currentInstance) { 174 | let { provides } = currentInstance 175 | let parentProvides = currentInstance.parent.provides 176 | // 初始化的时候 当前组件的provides 指向了 组组件的provides 177 | // 每次执行Object.create 的时候相当于重置了 当前的组件的provides.propotye 所以只需要初始化一次即可 178 | if (provides == parentProvides) { 179 | // 将当前组件的 provides的原型指向父组件的 provides 180 | provides = currentInstance.provides = Object.create(parentProvides) 181 | } 182 | provides[key] = value 183 | } 184 | } 185 | ``` 186 | 187 | 188 | #### inject 189 | ```js 190 | /** 191 | * * @defaultValue 默认参数 也可以是函数 192 | */ 193 | export function inject (key: string, defaultValue: any) { 194 | const currentInstance = getCurrentInstance() 195 | if (currentInstance) { 196 | const { provides } = currentInstance 197 | if (key in provides) { 198 | return provides[key] 199 | } else { 200 | if (defaultValue) { 201 | if (typeof defaultValue === 'function') { 202 | return defaultValue() 203 | } 204 | return defaultValue 205 | } 206 | } 207 | } 208 | } 209 | ``` -------------------------------------------------------------------------------- /vueSetupFlow/实现Fragment、Text.MD: -------------------------------------------------------------------------------- 1 | 2 | ### patch 函数的内部处理代码 3 | ```js 4 | export const Fragment = Symbol('Fragment') 5 | export const Text = Symbol('Text') 6 | 7 | function patch(vnode: any, container: Element) { 8 | const { shapeflag, type} = vnode 9 | switch (type) { 10 | case Fragment: 11 | // 处理 Frament 12 | processFrament(vnode, container) 13 | break; 14 | case Text: 15 | // 处理 文本节点 16 | processText(vnode, container) 17 | break; 18 | 19 | default: 20 | if (shapeflag & ShapeFlages.ELEMENT) { 21 | // 处理元素 22 | processElemnt(vnode, container) 23 | } else if (shapeflag & ShapeFlages.STATEFUL_COMPONENT) { 24 | // 处理组件 25 | processComponent(vnode, container) 26 | } 27 | break; 28 | } 29 | } 30 | ``` 31 | 32 | 33 | ## 实现 Fragment 34 | ```js 35 | /** 36 | * 1 使用插槽创建节点的时候 给createVNode的 第一个参数赋值为Fragment 37 | * 2 在patch 的时候 针对于Fragment 类型对应的处理 38 | * 3 对于Fragment 类型 只需要 循环children节点 继续调用patch 即可 39 | */ 40 | import { createVNode, Fragment } from "../vnode"; 41 | export function renderSlots (slots: any, name: string, props) { 42 | let slot = slots[name] 43 | if (slot) { 44 | if (typeof slot === 'function') { 45 | return createVNode(Fragment, {}, slot(props)) 46 | } 47 | } 48 | } 49 | /** 处理Frament */ 50 | function processFrament(vnode: any, container: Element) { 51 | /** 只需要渲染 children 部分 */ 52 | mountChildren(vnode.children, container) 53 | } 54 | ``` 55 | 56 | ## 实现 Text vnode 57 | ```js 58 | /** 创建文本虚拟节点 */ 59 | /** 60 | * 1 在创建文本的虚拟节点时 给参数1赋值为 Text 类型 61 | * 2 在执行patch 函数的时候做对应的处理 62 | * 3 63 | */ 64 | export function createTextVNode (children: string) { 65 | return h(Text, {}, children) 66 | } 67 | /** 处理文本节点 */ 68 | function processText(vnode: any, container: Element) { 69 | const el = (vnode.el = document.createTextNode(vnode.children)) 70 | container.append(el) 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /vueSetupFlow/实现组件的emit.MD: -------------------------------------------------------------------------------- 1 | ## 实现组件的 emit事件 2 | 3 | ```js 4 | /** 5 | * 子组件 在setup函数的的第二个参数结构出 emit 方法 6 | * 子组件通过调用 emit('event') 触发props上的对应事件 7 | */ 8 | function setupStatefulComponent(instance: any) { 9 | /** 组件 */ 10 | const Component = instance.type 11 | 12 | const { setup } = Component 13 | 14 | // 创建组件的代理对象 15 | instance.proxy = new Proxy({_: instance}, PublicInstanceProxyHandlers) 16 | 17 | if (setup) { 18 | 19 | let setupResult = setup(shallowReadonly(instance.props), { 20 | emit: instance.emit 21 | }) 22 | 23 | handleSetupResult(instance, setupResult) 24 | } 25 | } 26 | // 1 组件上添加一个 emit属性 27 | // 2 重写emit 这个方法 28 | // 2.1 通过bind返回一个填充过组件实例的函数 29 | // 外部调用时不在需要传入组件实例 30 | export function createComponentInstance(vnode: any) { 31 | const component = { 32 | /** 虚拟节点 */ 33 | vnode, 34 | type: vnode.type, 35 | /** 组件的属性 */ 36 | props: {}, 37 | /** 组件setup 返回的数据 */ 38 | setupState: {}, 39 | /** 组件代理对象 方便用户 直接通过 this.xxx 访问组件的数据 */ 40 | proxy: null, 41 | /** 组件emit 事件 */ 42 | emit: () => {} 43 | } 44 | /** 重写emit方法 将参数component传入emit内 */ 45 | component.emit = emit.bind(null, component) 46 | return component 47 | } 48 | 49 | 50 | // emit 处理部分 51 | function capitalize (str: string): string { 52 | return str ? str.charAt(0).toUpperCase() + str.slice(1) : '' 53 | } 54 | 55 | function camelize (str: string) { 56 | return str.replace(/-(\w)/g, (_, s: string)=> { 57 | return s ? s.toUpperCase() : '' 58 | }) 59 | } 60 | 61 | function toHandelKey(str: string) { 62 | return str ? 'on' + camelize(str) : '' 63 | } 64 | 65 | // 通过 ...args 将出剩余参数传入 66 | export function emit (instance, event: string, ...args) { 67 | 68 | const { props } = instance 69 | 70 | let handelKey = toHandelKey(capitalize(event)) 71 | 72 | let handle = props[handelKey] 73 | 74 | handle && handle(...args) 75 | } 76 | ``` -------------------------------------------------------------------------------- /vueSetupFlow/实现自定义渲染器.MD: -------------------------------------------------------------------------------- 1 | ## 自定义渲染器 2 | 3 | -------------------------------------------------------------------------------- /vueSetupFlow/更新组件的渲染流程.MD: -------------------------------------------------------------------------------- 1 | ## 更新element流程搭建 2 | - patch时传入patch( n1, n2, container, parentInstance ) 3 | - 老节点 新节点 节点需要挂载得容器 父组件 4 | 5 | - processFrament( n1, n2, container, parentInstance) 6 | - 老节点 新节点 节点需要挂载得容器 父组件 7 | 8 | - processText( n1, n2, container) 9 | - 老节点 新节点 节点需要挂载得容 10 | 11 | - processElement( n1, n2, container, parentInstance) 12 | - 老节点 新节点 节点需要挂载得容器 父组件 13 | 14 | - processComponent(n1, n2, container, parentInstance) 15 | - 老节点 新节点 节点需要挂载得容器 父组件 16 | 17 | ```js 18 | /** 19 | * 1 当元素使用得 响应式数据发生变化后 20 | * 2 setupRenderEffect() 内部使用effect 函数包裹得函数会被重新执行 21 | * 3 可以通过比对两个 不同得 vnode 计算出差异部分 定点更新 22 | * 4 组件上添加一个是否已经被挂载过得 属性 ==> isMontend 23 | * 5 默认是false 挂载过后将属性设置为true 24 | * 6 再次执行patch ==> 执行 ==> 25 | * 7 函数内部区分组件是否已经被挂载 如果是则组件不会再次挂载 反值挂载 26 | * ==> processComponent 27 | * ==> mountComponent 28 | * ==> createComponentInstance 29 | * ==> setupComponent 30 | * ==> setupRenderEffect 31 | * ==> updateRenderEffect 32 | * 函数内部判断组件是否被挂载 33 | * 否 ==> 挂载元素 34 | * ==> patch(n1, n2, container, parentInstance) ==> 组件被处理好之后是个元素节点会执行 processElement 流程 35 | * ==> processElement() 36 | * ==> mountElement() 37 | * ==> 子元素是 string 直接设置textContent 即可 38 | * ==> 子元素是 children 则循环 children 调用patch 继续区分 39 | * 是 ==> 执行更新逻辑 40 | * ==> patch(n1, n2, container, parentInstance) 41 | * ==> processElement(n1, n2, container, parentInstance) 42 | * ==> n1 存在说明节点是被挂载过的 需要 比对新旧vnode 进行定向更新 43 | * ==> patchElement() 44 | * ==> patchProps() // 处理属性 45 | * // 1 新旧属性不同 修改 46 | * // 2 新的属性是 undefined 直接删除 这个属性 47 | * // 3 老的属性不存在新得中删除 48 | * ==> patchElement() 49 | * // 1 老的是array新的是text 50 | * // 2 老的是 text 新的是 text 51 | * // 3 老的是 text 新的是 array 52 | * 53 | * ==> n1 不存在 说明是初始化 执行元素得 54 | * ==> mountElement() 55 | * 56 | */ 57 | ``` 58 | 59 | ```js 60 | import { createAppAPI } from './createApp' 61 | import { ShapeFlages } from "../shared/ShapeFlages" 62 | import { createComponentInstance, setupComponent } from "./component" 63 | import { Fragment, Text } from "./vnode" 64 | import { effect } from '../resctivite/effect' 65 | 66 | export function createRenderer(options) { 67 | 68 | const { 69 | createElement: hostCreateElement, 70 | patchProp: hostPatchProp, 71 | insert: hostInsert, 72 | setElementText: hostSetElementText 73 | } = options 74 | 75 | function render(vnode: any, container: Element, parentInstance) { 76 | patch(null, vnode, container, parentInstance) 77 | } 78 | 79 | function patch(n1, n2: any, container: Element, parentInstance) { 80 | 81 | const { shapeflag, type} = n2 82 | switch (type) { 83 | case Fragment: 84 | processFrament(n1, n2, container, parentInstance) 85 | break; 86 | case Text: 87 | processText(n1, n2, container) 88 | break; 89 | 90 | default: 91 | if (shapeflag & ShapeFlages.ELEMENT) { 92 | // 处理元素 93 | processElement(n1, n2, container, parentInstance) 94 | } else if (shapeflag & ShapeFlages.STATEFUL_COMPONENT) { 95 | // 处理组件 96 | processComponent(n1, n2, container, parentInstance) 97 | } 98 | break; 99 | } 100 | 101 | } 102 | 103 | /** 处理组件 */ 104 | function processComponent(n1, n2: any, container: Element, parentInstance) { 105 | mountComponent(n2, container, parentInstance) 106 | } 107 | 108 | /** 挂载元素 */ 109 | function mountComponent(vnode: any, container: Element, parentInstance) { 110 | const instance = createComponentInstance(vnode, parentInstance) 111 | 112 | // 设置组件的属性、插槽、render函数 113 | setupComponent(instance) 114 | 115 | // 设置组件更新 116 | setupRenderEffect(instance, vnode, container) 117 | } 118 | 119 | /** 组件更新 */ 120 | function setupRenderEffect(instance: any, vnode: any, container: Element) { 121 | effect(() => { 122 | updateRenderEffect(instance, vnode, container) 123 | }) 124 | } 125 | 126 | 127 | function updateRenderEffect (instance: any, vnode: any ,container: Element) { 128 | if (!instance.isMounted) { 129 | const { proxy } = instance 130 | // 修改render函数的this 指向组件的代理对象 就可以直接通过 this.xxx访问组件的数据了 131 | const subTree = (instance.subTree = instance.render.call(proxy)) 132 | patch(null, subTree, container, instance) 133 | vnode.el = subTree.el 134 | instance.isMounted = true 135 | } else { 136 | const { proxy } = instance 137 | // 修改render函数的this 指向组件的代理对象 就可以直接通过 this.xxx访问组件的数据了 138 | /** 新的虚拟节点 */ 139 | const subTree = instance.render.call(proxy) 140 | /** 老的虚拟节点 */ 141 | const prevSubTree = instance.subTree 142 | // 更新 subTree 143 | // 这里不会再挂载组件 所以需要组件挂载的 dom实例赋值给 最新的虚拟节点 144 | subTree.el = prevSubTree.el 145 | patch(prevSubTree, subTree, container, instance) 146 | } 147 | } 148 | 149 | 150 | 151 | /** 处理元素 */ 152 | function processElement(n1, n2: any, container: Element, parentInstance) { 153 | if (n1) { 154 | patchElement(n1, n2, container, parentInstance) 155 | } else { 156 | mountElement(n1, n2, container, parentInstance) 157 | } 158 | } 159 | 160 | const TEMP_OBJECT = {} 161 | 162 | function patchElement (n1, n2: any, container: Element, parentInstance) { 163 | // 节点得属性可能不存在 props 需要初始化一个默认值 164 | // 默认值 指向同一个对象 165 | // 方便处理属性时做优化 166 | // 如果新旧属性都是同一个对象则不需要循环处理 167 | const prevProps = n1.props || TEMP_OBJECT 168 | const nextProps = n2.props || TEMP_OBJECT 169 | const el = n2.el 170 | patchProps(el, prevProps, nextProps) 171 | } 172 | 173 | /** 比对新旧节点的props 更新对应的属性*/ 174 | function patchProps (el, oldProps, newProps) { 175 | 176 | if (newProps !== oldProps) { 177 | /** 新的属性更旧的不同更改 */ 178 | for (const key in newProps) { 179 | const newValue = newProps[key] 180 | const oldValue = oldProps[key] 181 | if (newValue !== oldValue) { 182 | hostPatchProp(el, key, oldValue, newValue) 183 | } 184 | } 185 | } 186 | // 如果老的节点是TEMP_OBJECT 说明节点是初始化得属性不需要循环 187 | if (oldProps !== TEMP_OBJECT) { 188 | // 老的属性再新得中不存在 循环老的属性即可 189 | for (const key in oldProps) { 190 | if (!(key in newProps)) { 191 | hostPatchProp(el, key, oldProps[key], null) 192 | } 193 | } 194 | } 195 | 196 | } 197 | 198 | 199 | function mountElement(n1, n2: any, container: Element, parentInstance) { 200 | const { type, children, props, shapeflag } = n2 201 | 202 | // 创建元素 203 | const el: Element = ( n2.el = hostCreateElement(type)) 204 | 205 | // 文本节点 206 | if (shapeflag & ShapeFlages.TEXT_CHILDREN) { 207 | hostSetElementText(el, children) 208 | } else if (shapeflag & ShapeFlages.ARRAY_CHILDREN) { // 数组节点 209 | mountChildren(n1,children, el, parentInstance) 210 | } 211 | 212 | for (const key in props) { 213 | const value = props[key] 214 | hostPatchProp(el, key, null, value) 215 | } 216 | 217 | hostInsert(el, container) 218 | } 219 | 220 | 221 | function mountChildren(n1, n2: any[], container: Element, parentInstance) { 222 | n2.forEach((vnode) => { 223 | patch(null, vnode, container, parentInstance) 224 | }) 225 | } 226 | 227 | 228 | /** 处理Frament */ 229 | function processFrament(n1, n2: any, container: Element, parentInstance) { 230 | /** 只需要渲染 children 部分 */ 231 | mountChildren(n1, n2.children, container, parentInstance) 232 | } 233 | 234 | /** 处理Text */ 235 | function processText(n1, n2: any, container: Element) { 236 | const el = (n2.el = document.createTextNode(n2.children)) 237 | container.append(el) 238 | } 239 | 240 | return { 241 | createApp: createAppAPI(render) 242 | } 243 | } 244 | 245 | 246 | ``` -------------------------------------------------------------------------------- /vueSetupFlow/注册事件.MD: -------------------------------------------------------------------------------- 1 | ## 注册点击事件 2 | 3 | ```js 4 | 5 | for (const key in props) { 6 | const value = props[key] 7 | /** 是否是以 on开头 */ 8 | const isOn = (key: string) => /^on[A-Z]/.test(key) 9 | // 获取事件名称 10 | const getEventName = (key: string) => key.slice(2).toLocaleLowerCase() 11 | if (isOn(key)) { 12 | let event = getEventName(key) 13 | el.addEventListener(event, value) 14 | } else { 15 | el.setAttribute(key, value) 16 | } 17 | } 18 | 19 | /** 20 | * 21 | * 22 | * 注册事件的命名规范是 on + event 事件名称首字母大写 23 | * // 1 事件写在 虚拟节点的props中并且以on+Event 形式出现 24 | * // 2 在处理元素类型的props时判断 属性是否是 on+Event出现 /^on[A-Z]/.test(key) 25 | * // 3 通过截取on后面的字符 获取到事件名称 并且转为小写 26 | * // 4 给元素通过 el.addEventListener(获取到事件, 属性值) 27 | * 28 | * 29 | */ 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /vueSetupFlow/组件代理对象.MD: -------------------------------------------------------------------------------- 1 | ## 实现组件的代理对象 2 | 3 | 4 | #### 组件代理对象代码 5 | ```js 6 | import { hasOwn } from "../shared/shared" 7 | 8 | const publicPropertiesMap = { 9 | $el: (instance) => instance.vnode.el, 10 | $slots: (instance)=> instance.slots 11 | } 12 | 13 | export const PublicInstanceProxyHandlers = { 14 | 15 | get ({ _: instance }: any, key: any) { 16 | 17 | const { setupState ,props } = instance 18 | console.log(instance) 19 | if (hasOwn(setupState, key)) { 20 | return setupState[key] 21 | } else if (hasOwn(props, key)) { 22 | return props[key] 23 | } 24 | 25 | const publicGetter = publicPropertiesMap[key] 26 | if (publicGetter) { 27 | return publicGetter(instance) 28 | } 29 | 30 | } 31 | 32 | } 33 | ``` 34 | 35 | ```js 36 | 37 | function setupStatefulComponent(instance: any) { 38 | /** 组件 */ 39 | const Component = instance.type 40 | 41 | const { setup } = Component 42 | 43 | // 创建组件的代理对象 44 | instance.proxy = new Proxy({_: instance}, PublicInstanceProxyHandlers) 45 | 46 | if (setup) { 47 | let setupResult = setup() 48 | handleSetupResult(instance, setupResult) 49 | } 50 | } 51 | 52 | function setupRenderEffect(instance: any, vnode: any ,container: Element) { 53 | 54 | const { proxy } = instance 55 | 56 | // 修改render函数的this 指向组件的代理对象 就可以直接通过 this.xxx访问组件的数据了 57 | const subTree = instance.render.call(proxy) 58 | 59 | patch(subTree, container) 60 | 61 | vnode.el = subTree.el 62 | } 63 | 64 | // 目标在render函数中通过this.xxx访问setup函数返回的数据 ==> setupState 65 | // 1给组件创建代理对象proxy ==> new Proxy({}, { get (target, key) {} }) 66 | // 1.1 通过call方法修改组件render函数内部的this将其指向组件的代理对象proxy 67 | // 1.2 当redner函数内部访问this.xxx时,在组件prox 代理对象的get处理函数中判断访问的属性在不在组件的数据中 68 | // 如果存在就返回这个属性 69 | 70 | ``` 71 | 72 | ## 实现组件的$el 73 | 74 | ```js 75 | // 挂载元素时获得dom实例 76 | function mountElement(vnode: any, container: Element) { 77 | const { type, children, props, shapeflag } = vnode 78 | // 创建元素 79 | const el: Element = ( vnode.el = document.createElement(type)) 80 | } 81 | 82 | // 在虚拟节点patch 结束后可以得到 组件挂载的根容器 83 | function setupRenderEffect(instance: any, vnode: any ,container: Element) { 84 | 85 | const { proxy } = instance 86 | 87 | // 修改render函数的this 指向组件的代理对象 就可以直接通过 this.xxx访问组件的数据了 88 | const subTree = instance.render.call(proxy) 89 | // patch过后说明元素已经渲染完成 90 | patch(subTree, container) 91 | 92 | vnode.el = subTree.el 93 | } 94 | // 实现组件的 $el 95 | // 功能说明: 返回虚拟节点的挂载的dom实例 96 | // 1.1 在初始化虚拟节点的时候添加一个el属性 97 | // 1.2 在mountElement 的时候可以获取到虚拟节点生成的dom实例 98 | // // 1.1 在mountElement的时候,给element类型的vnode的el属性设置为dom实例 99 | // 1.2 在setupRenderEffect函数内 100 | // 1.1 获取render函数执行后取得得subtree 101 | // 1.2 在patch 函数执行过后,再给组件类型得虚拟节点的el赋值 102 | // 因为在patch 函数之前 dom实例还并没有被创建出来 此时的subtree的el 还是一个null 103 | ``` 104 | 105 | ## 实现组件的props 106 | 107 | ```js 108 | /** 109 | * 1 可以在setup中获取到prosp 110 | * 2 可以在render中获取到prosp 111 | * 3 传入的props 数据不可更改 112 | */ 113 | 114 | // 1.1 初始化属性 115 | export function setupComponent(instance: any) { 116 | // 初始化属性 117 | initProps(instance,instance.vnode.props) 118 | } 119 | // 将虚拟节点的属性挂载到 组件实例上的props 120 | function initProps(instance: any, rawProps: any) { 121 | // 将组件的虚拟节点的props添加到 组件实力上的props 如果组件的虚拟节点的props不存在就添加一个默认值 122 | instance.props = rawProps || {} 123 | } 124 | // 1.3 在执行组件的setup函数时将props传入 125 | function setupStatefulComponent(instance: any) { 126 | /** 组件 */ 127 | const Component = instance.type 128 | 129 | const { setup } = Component 130 | 131 | // 创建组件的代理对象 132 | instance.proxy = new Proxy({_: instance}, PublicInstanceProxyHandlers) 133 | 134 | if (setup) { 135 | // 1.3 136 | let setupResult = setup(instance.props) 137 | handleSetupResult(instance, setupResult) 138 | } 139 | } 140 | 141 | 142 | // 2.1在render函数中访问数据的属性 会被组件的代理对象拦截到 143 | // 2.2 当访问到组的props 属性时去获取组件的prosp 并将访问的数据返回 144 | 145 | 146 | 147 | // 3.1 给props 数据使用 shallowReadonly 代理 (实现的时浅代理) 148 | import { shallowReadonly } from "../resctivite" 149 | function setupStatefulComponent(instance: any) { 150 | /** 组件 */ 151 | const Component = instance.type 152 | 153 | const { setup } = Component 154 | 155 | // 创建组件的代理对象 156 | instance.proxy = new Proxy({_: instance}, PublicInstanceProxyHandlers) 157 | 158 | if (setup) { 159 | 160 | let setupResult = setup(shallowReadonly(instance.props)) 161 | handleSetupResult(instance, setupResult) 162 | } 163 | } 164 | ``` 165 | 166 | ## 实现组件的$slots功能 获取传入的 children 节点 167 | - 通过组件的proxy 代理访问到 组件的slots -------------------------------------------------------------------------------- /vueSetupFlow/组件插槽.MD: -------------------------------------------------------------------------------- 1 | 2 | ## 实现组件插槽 3 | 4 | ```js 5 | 6 | // 目标1 实现在子组件内渲染 组件传入的 children 节点 7 | // 1.1 将传入的 children 数据添加到 组件slot属性上 8 | 9 | // 1.2 实现具名插槽 10 | // 在给子组件的children传参数时传入一个对象 将需要渲染的节点指定名称 11 | // children结构: 12 | { footer: h('div', {}, 'hahaah' ) } || { footer: h('div', {}, [h('div', {}, 'xxx')] ) } 13 | // 子组件通过使用 renderSlots(this.$slots, 'name') 获取需要渲染的节点 14 | // children结构: 15 | h("div", {}, [ renderSlots(this.$slots, 'header', { count }), foo]) 16 | 17 | 18 | // 1.3 实现作用域插槽插槽的节点在渲染时可以获取到组件的内部的数据 19 | // 需要将传入的数据结构体改变为 20 | // children结构: 21 | { footer: (props) => h('div', {}, 'hahaah' + props.xxx ) }, { footer: (props) => h('div', {}, [h('div', {}, 'xxx' + props.xxx) ] ) } 22 | 23 | 24 | 25 | // Foo 组件 26 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js"; 27 | 28 | export const Foo = { 29 | setup() { 30 | return {}; 31 | }, 32 | render() { 33 | const count = 1 34 | const foo = h("p", {}, "foo"); 35 | return h("div", {}, [ renderSlots(this.$slots, 'header', { count }), foo, renderSlots(this.$slots, 'footer')]); 36 | }, 37 | }; 38 | 39 | // app组件 40 | export const App = { 41 | name: "App", 42 | render() { 43 | const app = h("div", {}, "App"); 44 | 45 | const header = (props) => { 46 | return h('div', {}, 'header' + props.count) 47 | } 48 | const footer = (props) => h('div', {}, 'footer') 49 | 50 | // 单个节点 51 | // const foo = h(Foo, {}, header ); 52 | // 支持传入数组 53 | // const foo = h(Foo, {}, [header, footer] ) 54 | 55 | // 实现具名插槽 56 | // 作用域插槽 57 | const foo = h(Foo, {}, { 58 | footer: footer, 59 | header: header 60 | }); 61 | 62 | return h("div", {}, [app, foo]); 63 | }, 64 | 65 | setup() { 66 | return {}; 67 | }, 68 | }; 69 | 70 | 71 | export function createComponentInstance(vnode: any) { 72 | const component = { 73 | /** 虚拟节点 */ 74 | vnode, 75 | 76 | type: vnode.type, 77 | /** 组件的属性 */ 78 | props: {}, 79 | /** 组件setup 返回的数据 */ 80 | setupState: {}, 81 | /** 组件代理对象 方便用户 直接通过 this.xxx 访问组件的数据 */ 82 | proxy: null, 83 | /** 组件emit 事件 */ 84 | emit: () => {}, 85 | /** 组件的插槽 */ 86 | slots: {} 87 | } 88 | /** 重写emit方法 将参数1传入emit内 */ 89 | component.emit = emit.bind(null, component) 90 | return component 91 | } 92 | 93 | // 初始化插槽代码 94 | import { isArray } from "../shared/shared"; 95 | export function initSlots (instance: any, children: any) { 96 | normalizeObjectSlots(children, instance.slots); 97 | } 98 | 99 | function normalizeObjectSlots(children, slots) { 100 | for (const key in children) { 101 | let value = children[key]; 102 | // 获取到的是个 函数需要执行它 103 | slots[key] = (props) => normalizeSlotValue(value(props)); 104 | } 105 | } 106 | 107 | function normalizeSlotValue (value) { 108 | return isArray(value) ? value : [ value ] 109 | } 110 | 111 | // 渲染插槽的代码 112 | import { createVNode } from "../vnode"; 113 | export function renderSlots (slots: any, name: string, props) { 114 | // props 作用域插槽传入的数据 115 | let slot = slots[name] 116 | if (slot) { 117 | if (typeof slot === 'function') { 118 | return createVNode('div', {}, slot(props)) 119 | } 120 | } 121 | } 122 | 123 | ``` --------------------------------------------------------------------------------