├── .gitignore ├── .vscode └── settings.json ├── App.js ├── README.md ├── babel.config.js ├── core ├── index.js └── reactivity │ └── index.js ├── example ├── componemtSlot │ ├── APP.js │ ├── foo.js │ ├── index.html │ └── main.js ├── componentEmit │ ├── APP.js │ ├── foo.js │ ├── index.html │ └── main.js ├── getcurrentinstance │ ├── APP.js │ ├── foo.js │ ├── index.html │ └── main.js ├── patchChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js ├── projectInject │ ├── APP.js │ ├── foo.js │ ├── index.html │ └── main.js ├── reactivity │ ├── APP.js │ ├── foo.js │ ├── index.html │ └── main.js └── update │ ├── APP.js │ ├── foo.js │ ├── index.html │ └── main.js ├── index.html ├── lib ├── guide.cjs.js └── guide.esm.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── index.js ├── index.ts ├── reactivity │ ├── bseHandlers.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ ├── shared │ │ └── index.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── componemtPublicInstance.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentSlots.ts │ ├── createApp.ts │ ├── h.ts │ ├── helper │ │ └── renderSlot.ts │ ├── index.ts │ ├── render.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ └── sharedFlags.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compile-hero.disable-compile-files-on-did-save-code": true 3 | } -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | render(context) { }, 4 | setup() { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### 初始化项目 4 | 5 | 6 | ## 简易版mini-vue的实现 7 | 8 | 解决jest报错 yarn add jest @types/jest --dev 9 | 10 | 响应式文件夹 11 | runtime-core 12 | runtime-dom 13 | shared 主要方法函数 14 | 15 | jest 报错需要安装babel并且创建babel.js 16 | yarn add --dev babel-jest @babel/core @babel/preset-env 17 | yarn add --dev @babel/preset-typescript //支持typescript 18 | 19 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { 4 | targets: { 5 | node: 'current' 6 | } 7 | }], 8 | '@babel/preset-typescript', 9 | ], 10 | }; -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | effectWatcher 3 | } from './reactivity/index' 4 | export function createApp(rootComponent) { 5 | return { 6 | mount(rootContainer) { 7 | const context = rootComponent.setup() 8 | effectWatcher(() => { 9 | const element = rootComponent.render(context) 10 | rootContainer.appendChild(element) 11 | }) 12 | 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /core/reactivity/index.js: -------------------------------------------------------------------------------- 1 | let currentEffect = null; 2 | class Dep { 3 | constructor(val) { 4 | this.effects = new Set(); 5 | this._val = val; 6 | } 7 | get value() { 8 | this.depend() 9 | return this._val; 10 | } 11 | set value(newValue) { 12 | this._val = newValue; 13 | this.notices() 14 | } 15 | // 1收集依赖 16 | depend() { 17 | console.log('收集依赖', currentEffect) 18 | if (currentEffect) { 19 | 20 | this.effects.add(currentEffect) 21 | } 22 | } 23 | // 触发依赖 24 | notices() { 25 | console.log('触发了===notices', this.effects.size) 26 | // c触发一下我们之前收集到的依赖 27 | this.effects.forEach(effect => { 28 | effect(); 29 | }) 30 | } 31 | 32 | } 33 | const dep = new Dep(123) 34 | 35 | function effectWatch(effect) { 36 | // 收集依赖 37 | 38 | currentEffect = effect 39 | effect() 40 | // dep.depend() 41 | currentEffect = null 42 | } 43 | let b; 44 | // effectWatch(() => { 45 | 46 | // b = dep.value + 10 47 | // console.log('hello',b) 48 | // }) 49 | // dep.value = 11 50 | // dep.notices() 51 | 52 | 53 | const targetMap = new Map() 54 | 55 | // 收集依赖 56 | function getDep(target, key) { 57 | let depsMap = targetMap.get(target); 58 | if (!depsMap) { 59 | depsMap = new Map(); 60 | targetMap.set(target, depsMap) 61 | } 62 | let deps = depsMap.get(key); 63 | if (!deps) { 64 | deps = new Dep(); 65 | depsMap.set(key, deps) 66 | } 67 | return deps; 68 | } 69 | 70 | function reactive(raw) { 71 | return new Proxy(raw, { 72 | get(target, key) { 73 | const deps = getDep(target, key); 74 | console.log('key===', key) 75 | // 依赖收集 76 | deps.depend() 77 | return Reflect.get(target, key) 78 | }, 79 | set(target, key, value) { 80 | // 触发依赖 81 | const deps = getDep(target, key); 82 | console.log('set===', deps) 83 | const result = Reflect.set(target, key, value) 84 | deps.notices(); 85 | return result; 86 | } 87 | }) 88 | } 89 | const user = reactive({ 90 | age: 12 91 | }) 92 | let double; 93 | effectWatch(() => { 94 | console.log('effectWatch=====') 95 | double = user.age * 2 96 | console.log('double', double) 97 | }) 98 | user.age = 14 -------------------------------------------------------------------------------- /example/componemtSlot/APP.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | createTextVnode 4 | } from '../../lib/guide.esm.js' 5 | import { 6 | Foo 7 | } from './foo.js' 8 | export const APP = { 9 | render() { 10 | window.self = this; 11 | const app = h('div', { 12 | id: 'app' 13 | }, 'app') 14 | const foo = h(Foo, {}, { 15 | footer: () => [h('p', {}, 'slots111'), createTextVnode('你好呀text节点')], 16 | header: ({ 17 | age 18 | }) => h('p', {}, 'slots222' + age) 19 | }) 20 | // const foo = h(Foo, {}, h('p', {}, 'slots111')) 21 | 22 | return h('div', {}, [app, foo]) 23 | }, 24 | setup() { 25 | return { 26 | name: 'world12345666' 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /example/componemtSlot/foo.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | renderSlot 4 | } from '../../lib/guide.esm.js' 5 | export const Foo = { 6 | setup() {}, 7 | render() { 8 | console.log('slots', this.$slots) 9 | const foo = h('p', {}, 'p元素foo') 10 | // renderSlot(this.$slots, "footer") 11 | return h('div', {}, [renderSlot(this.$slots, "header",{age:18}), foo,]) 12 | 13 | } 14 | } -------------------------------------------------------------------------------- /example/componemtSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /example/componemtSlot/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | APP 6 | } from './APP.js' 7 | const rootContainer=document.getElementById('app') 8 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/componentEmit/APP.js: -------------------------------------------------------------------------------- 1 | import { 2 | h 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | Foo 6 | } from './foo.js' 7 | export const APP = { 8 | render() { 9 | window.self = this; 10 | return h('div', { 11 | id: 'app111', 12 | class: 'name', 13 | }, 14 | [h('div', { 15 | id: 'red' 16 | }, '测试emit功能'), h(Foo, { 17 | onAdd(a,v) { 18 | console.log('我是APP组件的onAdd事件',a,v); 19 | }, 20 | onAddFoo(a, v) { 21 | console.log('我是APP组件的onAddFoo事件', a, v); 22 | } 23 | })] 24 | ); 25 | }, 26 | setup() { 27 | return { 28 | name: 'world12345666' 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /example/componentEmit/foo.js: -------------------------------------------------------------------------------- 1 | import { 2 | h 3 | } from '../../lib/guide.esm.js' 4 | export const Foo = { 5 | setup(props, { 6 | emit 7 | }) { 8 | console.log('props', props); 9 | const emitAdd = () => { 10 | console.log('我是emitAdd事件'); 11 | emit('Add', 1, 2) 12 | emit('add-foo', 3, 4) 13 | } 14 | return { 15 | emitAdd 16 | } 17 | }, 18 | render() { 19 | const btn = h('button', { 20 | onClick: this.emitAdd 21 | }, 'emitAdd') 22 | const foo = h('p', {}, '我是foo组件') 23 | return h('div', { 24 | id: 'foo' 25 | }, [btn, foo]); 26 | } 27 | } -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | APP 6 | } from './APP.js' 7 | const rootContainer=document.getElementById('app') 8 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/getcurrentinstance/APP.js: -------------------------------------------------------------------------------- 1 | import { 2 | h,getCurrentInstance 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | Foo 6 | } from './foo.js' 7 | export const APP = { 8 | render() { 9 | window.self = this; 10 | return h('div', { 11 | id: 'app111', 12 | class: 'name', 13 | }, 14 | [h('div', { 15 | id: 'red' 16 | }, '测试emit功能'), h(Foo, { 17 | onAdd(a,v) { 18 | console.log('我是APP组件的onAdd事件',a,v); 19 | }, 20 | onAddFoo(a, v) { 21 | console.log('我是APP组件的onAddFoo事件', a, v); 22 | } 23 | })] 24 | ); 25 | }, 26 | setup() { 27 | const instance = getCurrentInstance() 28 | console.log('instanceAPP', instance) 29 | return { 30 | name: 'world12345666' 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /example/getcurrentinstance/foo.js: -------------------------------------------------------------------------------- 1 | import { 2 | h,getCurrentInstance 3 | } from '../../lib/guide.esm.js' 4 | export const Foo = { 5 | setup(props, { 6 | emit 7 | }) { 8 | const instance = getCurrentInstance() 9 | console.log('instanceFoo------', instance) 10 | const emitAdd = () => { 11 | console.log('我是emitAdd事件'); 12 | emit('Add', 1, 2) 13 | emit('add-foo', 3, 4) 14 | } 15 | return { 16 | emitAdd 17 | } 18 | }, 19 | render() { 20 | const btn = h('button', { 21 | onClick: this.emitAdd 22 | }, 'emitAdd') 23 | const foo = h('p', {}, '我是foo组件') 24 | return h('div', { 25 | id: 'foo' 26 | }, [btn, foo]); 27 | } 28 | } -------------------------------------------------------------------------------- /example/getcurrentinstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /example/getcurrentinstance/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | APP 6 | } from './APP.js' 7 | const rootContainer=document.getElementById('app') 8 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | h 3 | } from "../../lib/guide.esm.js"; 4 | 5 | import ArrayToText from "./ArrayToText.js"; 6 | import TextToText from "./TextToText.js"; 7 | import TextToArray from "./TextToArray.js"; 8 | import ArrayToArray from "./ArrayToArray.js"; 9 | 10 | export default { 11 | name: "App", 12 | setup() {}, 13 | 14 | render() { 15 | return h("div", { 16 | tId: 1 17 | }, [ 18 | h("p", {}, "主页"), 19 | // 老的是 array 新的是 text 20 | // h(ArrayToText), 21 | // 老的是 text 新的是 text 22 | // h(TextToText), 23 | // 老的是 text 新的是 array 24 | // h(TextToArray) 25 | // 老的是 array 新的是 array 26 | h(ArrayToArray), 27 | ]); 28 | }, 29 | }; -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 array 3 | 4 | import { 5 | ref, 6 | h 7 | } from "../../lib/guide.esm.js"; 8 | 9 | // 1. 左侧的对比 10 | // (a b) c 11 | // (a b) d e 12 | // const prevChildren = [ 13 | // h("p", { key: "A" }, "A"), 14 | // h("p", { key: "B" }, "B"), 15 | // h("p", { key: "C" }, "C"), 16 | // ]; 17 | // const nextChildren = [ 18 | // h("p", { key: "A" }, "A"), 19 | // h("p", { key: "B" }, "B"), 20 | // h("p", { key: "D" }, "D"), 21 | // h("p", { key: "E" }, "E"), 22 | // ]; 23 | 24 | // 2. 右侧的对比 25 | // a (b c) 26 | // d e (b c) 27 | // const prevChildren = [ 28 | // h("p", { key: "A" }, "A"), 29 | // h("p", { key: "B" }, "B"), 30 | // h("p", { key: "C" }, "C"), 31 | // ]; 32 | // const nextChildren = [ 33 | // h("p", { key: "D" }, "D"), 34 | // h("p", { key: "E" }, "E"), 35 | // h("p", { key: "B" }, "B"), 36 | // h("p", { key: "C" }, "C"), 37 | // ]; 38 | 39 | // 3. 新的比老的长 40 | // 创建新的 41 | // 左侧 42 | // (a b) 43 | // (a b) c 44 | // i = 2, e1 = 1, e2 = 2 45 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 46 | // const nextChildren = [ 47 | // h("p", { key: "A" }, "A"), 48 | // h("p", { key: "B" }, "B"), 49 | // h("p", { key: "C" }, "C"), 50 | // h("p", { key: "D" }, "D"), 51 | // ]; 52 | 53 | // 右侧 54 | // (a b) 55 | // c (a b) 56 | // i = 0, e1 = -1, e2 = 0 57 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 58 | // const nextChildren = [ 59 | // h("p", { key: "C" }, "C"), 60 | // h("p", { key: "A" }, "A"), 61 | // h("p", { key: "B" }, "B"), 62 | // ]; 63 | 64 | // 4. 老的比新的长 65 | // 删除老的 66 | // 左侧 67 | // (a b) c 68 | // (a b) 69 | // i = 2, e1 = 2, e2 = 1 70 | // const prevChildren = [ 71 | // h("p", { key: "A" }, "A"), 72 | // h("p", { key: "B" }, "B"), 73 | // h("p", { key: "C" }, "C"), 74 | // ]; 75 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 76 | 77 | // 右侧 78 | // a (b c) 79 | // (b c) 80 | // i = 0, e1 = 0, e2 = -1 81 | 82 | // const prevChildren = [ 83 | // h("p", { key: "A" }, "A"), 84 | // h("p", { key: "B" }, "B"), 85 | // h("p", { key: "C" }, "C"), 86 | // ]; 87 | // const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 88 | 89 | // 5. 对比中间的部分 90 | // 删除老的 (在老的里面存在,新的里面不存在) 91 | // 5.1 92 | // a,b,(c,d),f,g 93 | // a,b,(e,c),f,g 94 | // D 节点在新的里面是没有的 - 需要删除掉 95 | // C 节点 props 也发生了变化 96 | 97 | // const prevChildren = [ 98 | // h("p", { key: "A" }, "A"), 99 | // h("p", { key: "B" }, "B"), 100 | // h("p", { key: "C", id: "c-prev" }, "C"), 101 | // h("p", { key: "D" }, "D"), 102 | // h("p", { key: "F" }, "F"), 103 | // h("p", { key: "G" }, "G"), 104 | // ]; 105 | 106 | // const nextChildren = [ 107 | // h("p", { key: "A" }, "A"), 108 | // h("p", { key: "B" }, "B"), 109 | // h("p", { key: "E" }, "E"), 110 | // h("p", { key: "C", id:"c-next" }, "C"), 111 | // h("p", { key: "F" }, "F"), 112 | // h("p", { key: "G" }, "G"), 113 | // ]; 114 | 115 | // 5.1.1 116 | // a,b,(c,e,d),f,g 117 | // a,b,(e,c),f,g 118 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑) 119 | // const prevChildren = [ 120 | // h("p", { key: "A" }, "A"), 121 | // h("p", { key: "B" }, "B"), 122 | // h("p", { key: "C", id: "c-prev" }, "C"), 123 | // h("p", { key: "E" }, "E"), 124 | // h("p", { key: "D" }, "D"), 125 | // h("p", { key: "F" }, "F"), 126 | // h("p", { key: "G" }, "G"), 127 | // ]; 128 | 129 | // const nextChildren = [ 130 | // h("p", { key: "A" }, "A"), 131 | // h("p", { key: "B" }, "B"), 132 | // h("p", { key: "E" }, "E"), 133 | // h("p", { key: "C", id:"c-next" }, "C"), 134 | // h("p", { key: "F" }, "F"), 135 | // h("p", { key: "G" }, "G"), 136 | // ]; 137 | 138 | // 2 移动 (节点存在于新的和老的里面,但是位置变了) 139 | 140 | // 2.1 141 | // a,b,(c,d,e),f,g 142 | // a,b,(e,c,d),f,g 143 | // 最长子序列: [1,2] 144 | 145 | // const prevChildren = [ 146 | // h("p", { key: "A" }, "A"), 147 | // h("p", { key: "B" }, "B"), 148 | // h("p", { key: "C" }, "C"), 149 | // h("p", { key: "D" }, "D"), 150 | // h("p", { key: "E" }, "E"), 151 | // h("p", { key: "F" }, "F"), 152 | // h("p", { key: "G" }, "G"), 153 | // ]; 154 | 155 | // const nextChildren = [ 156 | // h("p", { key: "A" }, "A"), 157 | // h("p", { key: "B" }, "B"), 158 | // h("p", { key: "E" }, "E"), 159 | // h("p", { key: "C" }, "C"), 160 | // h("p", { key: "D" }, "D"), 161 | // h("p", { key: "F" }, "F"), 162 | // h("p", { key: "G" }, "G"), 163 | // ]; 164 | 165 | // 3. 创建新的节点 166 | // a,b,(c,e),f,g 167 | // a,b,(e,c,d),f,g 168 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建 169 | // const prevChildren = [ 170 | // h("p", { key: "A" }, "A"), 171 | // h("p", { key: "B" }, "B"), 172 | // h("p", { key: "C" }, "C"), 173 | // h("p", { key: "E" }, "E"), 174 | // h("p", { key: "F" }, "F"), 175 | // h("p", { key: "G" }, "G"), 176 | // ]; 177 | 178 | // const nextChildren = [ 179 | // h("p", { key: "A" }, "A"), 180 | // h("p", { key: "B" }, "B"), 181 | // h("p", { key: "E" }, "E"), 182 | // h("p", { key: "C" }, "C"), 183 | // h("p", { key: "D" }, "D"), 184 | // h("p", { key: "F" }, "F"), 185 | // h("p", { key: "G" }, "G"), 186 | // ]; 187 | 188 | // 综合例子 189 | // a,b,(c,d,e,z),f,g 190 | // a,b,(d,c,y,e),f,g 191 | 192 | // const prevChildren = [ 193 | // h("p", { key: "A" }, "A"), 194 | // h("p", { key: "B" }, "B"), 195 | // h("p", { key: "C" }, "C"), 196 | // h("p", { key: "D" }, "D"), 197 | // h("p", { key: "E" }, "E"), 198 | // h("p", { key: "Z" }, "Z"), 199 | // h("p", { key: "F" }, "F"), 200 | // h("p", { key: "G" }, "G"), 201 | // ]; 202 | 203 | // const nextChildren = [ 204 | // h("p", { key: "A" }, "A"), 205 | // h("p", { key: "B" }, "B"), 206 | // h("p", { key: "D" }, "D"), 207 | // h("p", { key: "C" }, "C"), 208 | // h("p", { key: "Y" }, "Y"), 209 | // h("p", { key: "E" }, "E"), 210 | // h("p", { key: "F" }, "F"), 211 | // h("p", { key: "G" }, "G"), 212 | // ]; 213 | 214 | // fix c 节点应该是 move 而不是删除之后重新创建的 215 | const prevChildren = [ 216 | h("p", { 217 | key: "A" 218 | }, "A"), 219 | h("p", {}, "C"), 220 | h("p", { 221 | key: "B" 222 | }, "B"), 223 | h("p", { 224 | key: "D" 225 | }, "D"), 226 | ]; 227 | 228 | const nextChildren = [ 229 | h("p", { 230 | key: "A" 231 | }, "A"), 232 | h("p", { 233 | key: "B" 234 | }, "B"), 235 | h("p", {}, "C"), 236 | h("p", { 237 | key: "D" 238 | }, "D"), 239 | ]; 240 | 241 | export default { 242 | name: "ArrayToArray", 243 | setup() { 244 | const isChange = ref(false); 245 | window.isChange = isChange; 246 | 247 | return { 248 | isChange, 249 | }; 250 | }, 251 | render() { 252 | const self = this; 253 | 254 | return self.isChange === true ? 255 | h("div", {}, nextChildren) : 256 | h("div", {}, prevChildren); 257 | }, 258 | }; -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | // 老的是 array 2 | // 新的是 text 3 | 4 | import { 5 | ref, 6 | h 7 | } from "../../lib/guide.esm.js"; 8 | const nextChildren = "newChildren"; 9 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")]; 10 | 11 | export default { 12 | name: "ArrayToText", 13 | setup() { 14 | const isChange = ref(false); 15 | window.isChange = isChange; 16 | 17 | return { 18 | isChange, 19 | }; 20 | }, 21 | render() { 22 | const self = this; 23 | 24 | return self.isChange === true ? 25 | h("div", {}, nextChildren) : 26 | h("div", {}, prevChildren); 27 | }, 28 | }; -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // 新的是 array 2 | // 老的是 text 3 | import { 4 | ref, 5 | h 6 | } from "../../lib/guide.esm.js"; 7 | 8 | const prevChildren = "oldChild"; 9 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")]; 10 | 11 | export default { 12 | name: "TextToArray", 13 | setup() { 14 | const isChange = ref(false); 15 | window.isChange = isChange; 16 | 17 | return { 18 | isChange, 19 | }; 20 | }, 21 | render() { 22 | const self = this; 23 | 24 | return self.isChange === true ? 25 | h("div", {}, nextChildren) : 26 | h("div", {}, prevChildren); 27 | }, 28 | }; -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 新的是 text 2 | // 老的是 text 3 | import { 4 | ref, 5 | h 6 | } from "../../lib/guide.esm.js"; 7 | 8 | const prevChildren = "oldChild"; 9 | const nextChildren = "newChild"; 10 | 11 | export default { 12 | name: "TextToText", 13 | setup() { 14 | const isChange = ref(false); 15 | window.isChange = isChange; 16 | 17 | return { 18 | isChange, 19 | }; 20 | }, 21 | render() { 22 | const self = this; 23 | 24 | return self.isChange === true ? 25 | h("div", {}, nextChildren) : 26 | h("div", {}, prevChildren); 27 | }, 28 | }; -------------------------------------------------------------------------------- /example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from "../../lib/guide.esm.js"; 4 | import App from "./App.js"; 5 | 6 | const rootContainer = document.querySelector("#root"); 7 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/projectInject/APP.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | provide 4 | } from '../../lib/guide.esm.js' 5 | import { 6 | Foo 7 | } from './foo.js' 8 | export const APP = { 9 | render() { 10 | window.self = this; 11 | 12 | return h('div', { 13 | id: 'app111', 14 | class: 'name', 15 | }, 16 | [h('div', { 17 | id: 'red' 18 | }, '测试emit功能'), h(Foo, { 19 | onAdd(a, v) { 20 | console.log('我是APP组件的onAdd事件', a, v); 21 | }, 22 | onAddFoo(a, v) { 23 | console.log('我是APP组件的onAddFoo事件', a, v); 24 | } 25 | })] 26 | ); 27 | }, 28 | setup() { 29 | provide('foo', 'foo111') 30 | provide('bar', 'bar2222') 31 | return { 32 | name: 'world12345666' 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /example/projectInject/foo.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | inject 4 | 5 | } from '../../lib/guide.esm.js' 6 | export const Foo = { 7 | setup(props) { 8 | const foo = inject('foo') 9 | const bar = inject('bar') 10 | console.log('inject==========',foo, bar) 11 | return { 12 | name:foo, 13 | bar:bar, 14 | name1:123 15 | } 16 | }, 17 | render() { 18 | console.log('fooooooo',bar) 19 | return h('div', { 20 | id: 'foo' 21 | }, `inject:foo${name}`); 22 | } 23 | } -------------------------------------------------------------------------------- /example/projectInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /example/projectInject/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | APP 6 | } from './APP.js' 7 | const rootContainer=document.getElementById('app') 8 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/reactivity/APP.js: -------------------------------------------------------------------------------- 1 | import { 2 | h 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | Foo 6 | } from './foo.js' 7 | export const APP = { 8 | render() { 9 | window.self = this; 10 | return h('div', { 11 | id: 'app111', 12 | class: 'name', 13 | onClick: () => { 14 | console.log('onclick'); 15 | } 16 | }, 17 | [h('div', { 18 | id: 'red' 19 | }, 'red'), h(Foo,{count:1})] 20 | // 'hello world' + this.name 21 | // [h('p', { 22 | // id: "red", 23 | // class:['mo','text'] 24 | // }, '子组件1'), h('p', { 25 | // id: "blue" 26 | // }, '子组件2')] 27 | ); 28 | }, 29 | setup() { 30 | return { 31 | name: 'world12345666' 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /example/reactivity/foo.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | h 4 | } from '../../lib/guide.esm.js' 5 | export const Foo = { 6 | setup(props) { 7 | console.log('props', props); 8 | }, 9 | render() { 10 | return h('div', { 11 | id: 'foo' 12 | }, "foo" + this.count); 13 | } 14 | } -------------------------------------------------------------------------------- /example/reactivity/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /example/reactivity/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | APP 6 | } from './APP.js' 7 | const rootContainer=document.getElementById('app') 8 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /example/update/APP.js: -------------------------------------------------------------------------------- 1 | import { 2 | h,getCurrentInstance 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | Foo 6 | } from './foo.js' 7 | export const APP = { 8 | render() { 9 | window.self = this; 10 | return h('div', { 11 | id: 'app111', 12 | class: 'name', 13 | }, 14 | [h('div', { 15 | id: 'red' 16 | }, '测试emit功能'), h(Foo, { 17 | onAdd(a,v) { 18 | console.log('我是APP组件的onAdd事件',a,v); 19 | }, 20 | onAddFoo(a, v) { 21 | console.log('我是APP组件的onAddFoo事件', a, v); 22 | } 23 | })] 24 | ); 25 | }, 26 | setup() { 27 | const instance = getCurrentInstance() 28 | console.log('instanceAPP', instance) 29 | return { 30 | name: 'world12345666' 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /example/update/foo.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | getCurrentInstance, 4 | ref 5 | } from '../../lib/guide.esm.js' 6 | export const Foo = { 7 | setup(props, { 8 | emit 9 | }) { 10 | const instance = getCurrentInstance() 11 | console.log('instanceFoo------', instance) 12 | const emitAdd = () => { 13 | console.log('我是emitAdd事件'); 14 | // emit('Add', 1, 2) 15 | // emit('add-foo', 3, 4) 16 | count.value++ 17 | // id.value='new-foo' 18 | id.value=undefined; 19 | } 20 | const count=ref(11) 21 | const id=ref('foo123') 22 | return { 23 | count, 24 | emitAdd, 25 | id 26 | } 27 | }, 28 | render() { 29 | const btn = h('button', { 30 | onClick: this.emitAdd 31 | }, 'emitAdd') 32 | const foo = h('p', {}, '我是foo组件'+this.count.value) 33 | return h('div', { 34 | id: this.id.value 35 | }, [btn, foo]); 36 | } 37 | } -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from '../../lib/guide.esm.js' 4 | import { 5 | APP 6 | } from './APP.js' 7 | const rootContainer=document.getElementById('app') 8 | createApp(APP).mount(rootContainer) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /lib/guide.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | const extend = Object.assign; 6 | function isObject(obj) { 7 | return obj !== null && typeof obj === 'object'; 8 | } 9 | function hasChange(val, newValue) { 10 | return !Object.is(val, newValue); 11 | } 12 | 13 | let activeEffect; 14 | let shouldTrick; 15 | class ReactiveEffect { 16 | constructor(fn, scheduler) { 17 | this.scheduler = scheduler; 18 | this.deps = []; 19 | this.active = true; 20 | this._fn = fn; 21 | } 22 | run() { 23 | if (!this.active) { 24 | return this._fn(); 25 | } 26 | shouldTrick = true; 27 | // 收集依赖 28 | activeEffect = this; 29 | const result = this._fn(); 30 | shouldTrick = false; 31 | return result; 32 | } 33 | stop() { 34 | if (this.active) { 35 | clearUpEffect(this); 36 | this.onStop && this.onStop(); 37 | this.active = false; 38 | } 39 | } 40 | } 41 | function clearUpEffect(effect) { 42 | effect.deps.forEach((dep) => { 43 | dep.delete(effect); 44 | }); 45 | } 46 | // 依赖收集 47 | const targetMap = new Map(); 48 | function track(target, key) { 49 | if (!isTracking()) 50 | return; 51 | let depsMap = targetMap.get(target); 52 | if (!depsMap) { 53 | depsMap = new Map(); 54 | targetMap.set(target, depsMap); 55 | } 56 | let dep = depsMap.get(key); 57 | if (!dep) { 58 | dep = new Set(); 59 | depsMap.set(key, dep); 60 | } 61 | trackEffect(dep); 62 | } 63 | function trackEffect(dep) { 64 | if (dep.has(activeEffect)) 65 | return; 66 | dep.add(activeEffect); 67 | activeEffect.deps.push(dep); 68 | } 69 | function isTracking() { 70 | return activeEffect !== undefined && shouldTrick; 71 | } 72 | function effect(fn, options = {}) { 73 | // 上来就要调用fn 74 | const scheduler = options.scheduler; //获取 75 | const _effect = new ReactiveEffect(fn, scheduler); 76 | extend(_effect, options); //挂载所有options的值 77 | _effect.run(); 78 | const runner = _effect.run.bind(_effect); 79 | runner.effect = _effect; 80 | return runner; 81 | } 82 | function trigger(target, key) { 83 | let depsMap = targetMap.get(target); 84 | let deps = depsMap.get(key); 85 | triggerEffect(deps); 86 | } 87 | function triggerEffect(deps) { 88 | for (const effect of deps) { 89 | if (effect.scheduler) { 90 | effect.scheduler(); 91 | } 92 | else { 93 | effect.run(); 94 | } 95 | } 96 | } 97 | 98 | function hostPatchProp(el, key, prevValue, nextValue) { 99 | const isOn = (key) => /^on[A-Z]/.test(key); 100 | if (isOn(key)) { 101 | const event = key.slice(2).toLowerCase(); 102 | el.addEventListener(event, nextValue); 103 | } 104 | else { 105 | if (nextValue === undefined || nextValue === null) { 106 | el.removeAttribute(key, nextValue); 107 | } 108 | else { 109 | el.setAttribute(key, nextValue); 110 | } 111 | } 112 | } 113 | 114 | const set = createSetter(); 115 | const get = createGetter(); 116 | const readonlyGet = createGetter(true); 117 | const shallowReadonlyGet = createGetter(true, true); 118 | function createGetter(isReadOnly = false, shallow = false) { 119 | return function get(target, key) { 120 | if (key === "__v_isReactive__" /* IS_REACTIVE */) { 121 | return !isReadOnly; 122 | } 123 | else if (key === "__v_isReadonly__" /* IS_READ_ONLY */) { 124 | return isReadOnly; 125 | } 126 | const res = Reflect.get(target, key); 127 | if (shallow) { 128 | // 如果是shallow就不需要深层次的观察 129 | return res; 130 | } 131 | // 看看是不是object 132 | if (isObject(res)) { 133 | return isReadOnly ? readonly(res) : reactive(res); 134 | } 135 | if (!isReadOnly) { 136 | track(target, key); 137 | } 138 | return res; 139 | }; 140 | } 141 | function createSetter() { 142 | return function get(target, key, value) { 143 | const res = Reflect.set(target, key, value); 144 | trigger(target, key); 145 | return res; 146 | }; 147 | } 148 | const mutableHandlers = { 149 | get, 150 | set, 151 | }; 152 | const readonlyHandlers = { 153 | get: readonlyGet, 154 | set(target, key, value) { 155 | // readonly的时候不允许set 156 | console.warn(`Attempting to mutate readonly value ${`${target}[${key}]`}`); 157 | return true; 158 | }, 159 | }; 160 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 161 | get: shallowReadonlyGet, 162 | }); 163 | 164 | function reactive(raw) { 165 | return new Proxy(raw, mutableHandlers); 166 | } 167 | function readonly(raw) { 168 | return new Proxy(raw, readonlyHandlers); 169 | } 170 | function shallowReadonly(raw) { 171 | if (!isObject(raw)) { 172 | console.warn(`target ${raw} 必须是一个对象`); 173 | return raw; 174 | } 175 | return new Proxy(raw, shallowReadonlyHandlers); 176 | } 177 | 178 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 179 | const capitalize = (str) => { 180 | return str.charAt(0).toUpperCase() + str.slice(1); 181 | }; 182 | const toHandlerKey = (str) => { 183 | return str ? `on${capitalize(str)}` : ""; 184 | }; 185 | const camelize = (str) => { 186 | return str.replace(/-(\w)/g, (_, c) => { 187 | return c ? c.toUpperCase() : ""; 188 | }); 189 | }; 190 | 191 | const publicProxyMaps = { 192 | $el: (instance) => instance.vnode.el, 193 | $slots: (instance) => instance.slots, 194 | }; 195 | const publicInstanceProxyHandler = { 196 | get({ _: instance }, key) { 197 | if (key in instance.setupState) { 198 | return instance.setupState[key]; 199 | } 200 | const { props } = instance; 201 | if (hasOwn(instance.setupState, key)) { 202 | return instance.setupState[key]; 203 | } 204 | else if (hasOwn(props, key)) { 205 | return props[key]; 206 | } 207 | const publicGetter = publicProxyMaps[key]; 208 | if (publicGetter) { 209 | return publicGetter(instance); 210 | } 211 | }, 212 | }; 213 | 214 | function emit(instance, event, ...args) { 215 | console.log("event====", event); 216 | const { props } = instance; 217 | // TPP 218 | // 先去写一个特定的行为再去写一个通用的行为 219 | const handlersName = toHandlerKey(camelize(event)); 220 | const handlers = props[handlersName]; 221 | handlers && handlers(...args); 222 | } 223 | 224 | function initProps(instance, rawProps) { 225 | instance.props = rawProps || {}; 226 | } 227 | 228 | function initSlots(instance, children) { 229 | // 是否是数组,如果不是就给包裹一层 230 | // instance.slots = Array.isArray(children) ? children : [children]; 231 | const { vnode } = instance; 232 | if (vnode.shapeFlags & 16 /* SLOT_CHILDREN */) { 233 | const slot = {}; 234 | for (let key in children) { 235 | const value = children[key]; 236 | slot[key] = (props) => normalizeSlotsValue(value(props)); 237 | } 238 | instance.slots = slot; 239 | } 240 | } 241 | function normalizeSlotsValue(value) { 242 | return Array.isArray(value) ? value : [value]; 243 | } 244 | 245 | function createComponentInstance(vnode, parent) { 246 | console.log("调试====", parent); 247 | const component = { 248 | vnode: vnode, 249 | type: vnode.type, 250 | setupState: {}, 251 | props: {}, 252 | provides: parent ? parent.provides : {}, 253 | parent, 254 | slot: {}, 255 | isMounted: false, 256 | subTree: {}, 257 | emit: () => { }, 258 | }; 259 | component.emit = emit.bind(null, component); //拿到instance 260 | return component; 261 | } 262 | function setComponentInstance(instance) { 263 | // initProps 264 | // initSlots 265 | initSlots(instance, instance.vnode.children); 266 | initProps(instance, instance.vnode.props); 267 | setupStatefulComponent(instance); 268 | } 269 | function setupStatefulComponent(instance) { 270 | setCurrentInstance(instance); 271 | const Component = instance.type; 272 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandler); 273 | const { setup } = Component; 274 | if (setup) { 275 | const setupResult = setup(shallowReadonly(instance.props), { 276 | emit: instance.emit, 277 | }); //props在子组件不去改变 278 | setCurrentInstance(null); 279 | handelSetupResult(setupResult, instance); 280 | } 281 | } 282 | function handelSetupResult(setupResult, instance) { 283 | if (typeof setupResult === "object") { 284 | instance.setupState = setupResult; 285 | } 286 | finishComponentSetup(instance); 287 | } 288 | function finishComponentSetup(instance) { 289 | const Component = instance.type; 290 | if (Component.render) { 291 | instance.render = Component.render; 292 | } 293 | } 294 | let currentInstance = null; 295 | function getCurrentInstance() { 296 | return currentInstance; 297 | } 298 | function setCurrentInstance(instance) { 299 | currentInstance = instance; 300 | } 301 | 302 | function render(vnode, container) { 303 | patch(null, vnode, container, null); 304 | console.log("render==", vnode, container); 305 | } 306 | function patch(n1, n2, container, parentComponent) { 307 | // 处理组件 308 | // 先判断是不是element 309 | // fragment只渲染children 310 | const { type, shapeFlags } = n2; 311 | switch (type) { 312 | case "Fragment": 313 | processFragment(n1, n2, container, parentComponent); 314 | break; 315 | case "Text": 316 | processText(n1, n2, container); 317 | break; 318 | default: 319 | if (shapeFlags & 1 /* ELEMENT */) { 320 | processElement(n1, n2, container, parentComponent); 321 | } 322 | else if (shapeFlags & 2 /* STATEFUL_COMPONENT */) { 323 | processComponent(n1, n2, container, parentComponent); 324 | } 325 | break; 326 | } 327 | } 328 | function processComponent(n1, vnode, container, parentComponent) { 329 | mountComponent(vnode, container, parentComponent); 330 | } 331 | function mountComponent(initialVNode, container, parentComponent) { 332 | const instance = createComponentInstance(initialVNode, parentComponent); 333 | setComponentInstance(instance); 334 | setupRenderEffect(instance, container, initialVNode); 335 | } 336 | function setupRenderEffect(instance, container, initialVNode) { 337 | effect(() => { 338 | if (!instance.isMounted) { 339 | const { proxy } = instance; 340 | console.log("初始化===", proxy); 341 | const subTree = (instance.subTree = instance.render.call(proxy)); 342 | patch(null, subTree, container, instance); 343 | initialVNode.el = subTree.el; 344 | instance.isMounted = true; 345 | } 346 | else { 347 | console.log("update"); 348 | const { proxy } = instance; 349 | const subTree = instance.render.call(proxy); 350 | const prevSubTree = instance.subTree; 351 | instance.subTree = subTree; 352 | patch(prevSubTree, subTree, container, instance); 353 | // 更新 354 | } 355 | }); 356 | } 357 | function processElement(n1, n2, container, parentComponent) { 358 | if (!n1) { 359 | // 初始化 360 | mountElement(n2, container, parentComponent); 361 | } 362 | else { 363 | // 更新 364 | patchElement(n1, n2); 365 | } 366 | } 367 | function patchElement(n1, n2, container, parentComponent) { 368 | console.log("patchElement"); 369 | const oldProps = n1.props || {}; 370 | const newProps = n2.props || {}; 371 | const el = (n2.el = n1.el); 372 | patchProp(el, oldProps, newProps); 373 | } 374 | function patchProp(el, oldProps, newProps) { 375 | // 遍历新的props 376 | for (const key in newProps) { 377 | const prevProp = oldProps[key]; 378 | const nextProp = newProps[key]; 379 | console.log("111", prevProp, nextProp); 380 | // 如果props不想等就准备更新 381 | if (prevProp !== nextProp) { 382 | hostPatchProp(el, key, prevProp, nextProp); 383 | } 384 | } 385 | } 386 | function mountElement(vnode, container, parentComponent) { 387 | const el = (vnode.el = document.createElement(vnode.type)); 388 | const { children, props, shapeFlags } = vnode; 389 | if (shapeFlags & 4 /* TEXT_CHILDREN */) { 390 | el.textContent = children; 391 | } 392 | else if (shapeFlags & 8 /* ARROW_CHILDREN */) { 393 | mountChild(children, el, parentComponent); 394 | } 395 | for (const key in props) { 396 | const value = props[key]; 397 | const isOn = (key) => /^on[A-Z]/.test(key); 398 | if (isOn(key)) { 399 | const event = key.slice(2).toLowerCase(); 400 | el.addEventListener(event, value); 401 | } 402 | else { 403 | el.setAttribute(key, value); 404 | } 405 | } 406 | container.appendChild(el); 407 | } 408 | function mountChild(vnode, container, parentComponent) { 409 | console.log("mountChild", vnode); 410 | if (Array.isArray(vnode)) { 411 | vnode.forEach((el) => { 412 | patch(null, el, container, parentComponent); 413 | }); 414 | } 415 | } 416 | function processFragment(n1, vnode, container, parentComponent) { 417 | mountChild(vnode, container, parentComponent); 418 | } 419 | function processText(n1, vnode, container) { 420 | // 只渲染文字 421 | const { children } = vnode; 422 | const textNode = (vnode.el = document.createTextNode(children)); 423 | container.append(textNode); 424 | } 425 | function createRender(options) { } 426 | 427 | function createVnode(type, props, children) { 428 | const vnode = { 429 | type, 430 | props, 431 | children, 432 | shapeFlags: getShapeFlags(type), 433 | el: null, 434 | }; 435 | // children 位运算符 436 | if (typeof children === "string") { 437 | vnode.shapeFlags |= 4 /* TEXT_CHILDREN */; 438 | } 439 | else if (Array.isArray(children)) { 440 | vnode.shapeFlags |= 8 /* ARROW_CHILDREN */; 441 | } 442 | // 组件 slot 443 | if (vnode.shapeFlags & 2 /* STATEFUL_COMPONENT */) { 444 | if (typeof children === "object") { 445 | vnode.shapeFlags |= 16 /* SLOT_CHILDREN */; 446 | } 447 | } 448 | return vnode; 449 | } 450 | function createTextVnode(text) { 451 | return createVnode('Text', {}, text); 452 | } 453 | function getShapeFlags(type) { 454 | return typeof type === "string" 455 | ? 1 /* ELEMENT */ 456 | : 2 /* STATEFUL_COMPONENT */; 457 | } 458 | 459 | function createApp(rootComponent) { 460 | return { 461 | mount(rootContainer) { 462 | // 先转换成虚拟节点 463 | // 所有节点都基于虚拟节点 464 | const vnode = createVnode(rootComponent); 465 | render(vnode, rootContainer); 466 | }, 467 | }; 468 | } 469 | 470 | function h(type, props, children) { 471 | return createVnode(type, props, children); 472 | } 473 | 474 | function renderSlot(slots, name, props) { 475 | const slot = slots[name]; 476 | if (slot) { 477 | if (typeof slot === "function") { 478 | return createVnode("Fragment", {}, slot(props)); 479 | } 480 | } 481 | } 482 | 483 | // 存 484 | function provide(key, value) { 485 | const currentInstance = getCurrentInstance(); 486 | if (currentInstance) { 487 | let { provides } = currentInstance; 488 | console.log("存provides====", currentInstance); 489 | const parentProviders = currentInstance.parent && currentInstance.parent.provides; 490 | if (provides === parentProviders) { 491 | provides = currentInstance.provides = Object.create(parentProviders); 492 | } 493 | provides[key] = value; 494 | } 495 | } 496 | // 取 497 | function inject(key, defaultValue) { 498 | const currentInstance = getCurrentInstance(); 499 | console.log("inject=====parentProviders", currentInstance); 500 | if (currentInstance) { 501 | const { parent } = currentInstance; 502 | const parentProviders = parent.provides; 503 | if (key in parentProviders) { 504 | return parentProviders[key]; 505 | } 506 | else if (defaultValue) { 507 | if (typeof defaultValue === "function") { 508 | return defaultValue(); 509 | } 510 | return defaultValue; 511 | } 512 | } 513 | } 514 | 515 | class RefImpl { 516 | constructor(value) { 517 | this._v_isRef = true; 518 | this._rawValue = value; 519 | this._value = convert(value); 520 | // 判断对象是不是对象 521 | this.dep = new Set(); 522 | } 523 | set value(newValue) { 524 | // 对比的时候 525 | if (hasChange(newValue, this._rawValue)) { 526 | this._value = convert(newValue); 527 | this._rawValue = newValue; 528 | triggerEffect(this.dep); 529 | } 530 | } 531 | get value() { 532 | if (isTracking()) { 533 | trackEffect(this.dep); 534 | } 535 | return this._value; 536 | } 537 | } 538 | function convert(value) { 539 | return isObject(value) ? reactive(value) : value; 540 | } 541 | function ref(value) { 542 | return new RefImpl(value); 543 | } 544 | 545 | exports.createApp = createApp; 546 | exports.createRender = createRender; 547 | exports.createTextVnode = createTextVnode; 548 | exports.getCurrentInstance = getCurrentInstance; 549 | exports.h = h; 550 | exports.inject = inject; 551 | exports.provide = provide; 552 | exports.ref = ref; 553 | exports.renderSlot = renderSlot; 554 | -------------------------------------------------------------------------------- /lib/guide.esm.js: -------------------------------------------------------------------------------- 1 | const extend = Object.assign; 2 | function isObject(obj) { 3 | return obj !== null && typeof obj === 'object'; 4 | } 5 | function hasChange(val, newValue) { 6 | return !Object.is(val, newValue); 7 | } 8 | 9 | let activeEffect; 10 | let shouldTrick; 11 | class ReactiveEffect { 12 | constructor(fn, scheduler) { 13 | this.scheduler = scheduler; 14 | this.deps = []; 15 | this.active = true; 16 | this._fn = fn; 17 | } 18 | run() { 19 | if (!this.active) { 20 | return this._fn(); 21 | } 22 | shouldTrick = true; 23 | // 收集依赖 24 | activeEffect = this; 25 | const result = this._fn(); 26 | shouldTrick = false; 27 | return result; 28 | } 29 | stop() { 30 | if (this.active) { 31 | clearUpEffect(this); 32 | this.onStop && this.onStop(); 33 | this.active = false; 34 | } 35 | } 36 | } 37 | function clearUpEffect(effect) { 38 | effect.deps.forEach((dep) => { 39 | dep.delete(effect); 40 | }); 41 | } 42 | // 依赖收集 43 | const targetMap = new Map(); 44 | function track(target, key) { 45 | if (!isTracking()) 46 | return; 47 | let depsMap = targetMap.get(target); 48 | if (!depsMap) { 49 | depsMap = new Map(); 50 | targetMap.set(target, depsMap); 51 | } 52 | let dep = depsMap.get(key); 53 | if (!dep) { 54 | dep = new Set(); 55 | depsMap.set(key, dep); 56 | } 57 | trackEffect(dep); 58 | } 59 | function trackEffect(dep) { 60 | if (dep.has(activeEffect)) 61 | return; 62 | dep.add(activeEffect); 63 | activeEffect.deps.push(dep); 64 | } 65 | function isTracking() { 66 | return activeEffect !== undefined && shouldTrick; 67 | } 68 | function effect(fn, options = {}) { 69 | // 上来就要调用fn 70 | const scheduler = options.scheduler; //获取 71 | const _effect = new ReactiveEffect(fn, scheduler); 72 | extend(_effect, options); //挂载所有options的值 73 | _effect.run(); 74 | const runner = _effect.run.bind(_effect); 75 | runner.effect = _effect; 76 | return runner; 77 | } 78 | function trigger(target, key) { 79 | let depsMap = targetMap.get(target); 80 | let deps = depsMap.get(key); 81 | triggerEffect(deps); 82 | } 83 | function triggerEffect(deps) { 84 | for (const effect of deps) { 85 | if (effect.scheduler) { 86 | effect.scheduler(); 87 | } 88 | else { 89 | effect.run(); 90 | } 91 | } 92 | } 93 | 94 | function hostPatchProp(el, key, prevValue, nextValue) { 95 | const isOn = (key) => /^on[A-Z]/.test(key); 96 | if (isOn(key)) { 97 | const event = key.slice(2).toLowerCase(); 98 | el.addEventListener(event, nextValue); 99 | } 100 | else { 101 | if (nextValue === undefined || nextValue === null) { 102 | el.removeAttribute(key, nextValue); 103 | } 104 | else { 105 | el.setAttribute(key, nextValue); 106 | } 107 | } 108 | } 109 | 110 | const set = createSetter(); 111 | const get = createGetter(); 112 | const readonlyGet = createGetter(true); 113 | const shallowReadonlyGet = createGetter(true, true); 114 | function createGetter(isReadOnly = false, shallow = false) { 115 | return function get(target, key) { 116 | if (key === "__v_isReactive__" /* IS_REACTIVE */) { 117 | return !isReadOnly; 118 | } 119 | else if (key === "__v_isReadonly__" /* IS_READ_ONLY */) { 120 | return isReadOnly; 121 | } 122 | const res = Reflect.get(target, key); 123 | if (shallow) { 124 | // 如果是shallow就不需要深层次的观察 125 | return res; 126 | } 127 | // 看看是不是object 128 | if (isObject(res)) { 129 | return isReadOnly ? readonly(res) : reactive(res); 130 | } 131 | if (!isReadOnly) { 132 | track(target, key); 133 | } 134 | return res; 135 | }; 136 | } 137 | function createSetter() { 138 | return function get(target, key, value) { 139 | const res = Reflect.set(target, key, value); 140 | trigger(target, key); 141 | return res; 142 | }; 143 | } 144 | const mutableHandlers = { 145 | get, 146 | set, 147 | }; 148 | const readonlyHandlers = { 149 | get: readonlyGet, 150 | set(target, key, value) { 151 | // readonly的时候不允许set 152 | console.warn(`Attempting to mutate readonly value ${`${target}[${key}]`}`); 153 | return true; 154 | }, 155 | }; 156 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 157 | get: shallowReadonlyGet, 158 | }); 159 | 160 | function reactive(raw) { 161 | return new Proxy(raw, mutableHandlers); 162 | } 163 | function readonly(raw) { 164 | return new Proxy(raw, readonlyHandlers); 165 | } 166 | function shallowReadonly(raw) { 167 | if (!isObject(raw)) { 168 | console.warn(`target ${raw} 必须是一个对象`); 169 | return raw; 170 | } 171 | return new Proxy(raw, shallowReadonlyHandlers); 172 | } 173 | 174 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 175 | const capitalize = (str) => { 176 | return str.charAt(0).toUpperCase() + str.slice(1); 177 | }; 178 | const toHandlerKey = (str) => { 179 | return str ? `on${capitalize(str)}` : ""; 180 | }; 181 | const camelize = (str) => { 182 | return str.replace(/-(\w)/g, (_, c) => { 183 | return c ? c.toUpperCase() : ""; 184 | }); 185 | }; 186 | 187 | const publicProxyMaps = { 188 | $el: (instance) => instance.vnode.el, 189 | $slots: (instance) => instance.slots, 190 | }; 191 | const publicInstanceProxyHandler = { 192 | get({ _: instance }, key) { 193 | if (key in instance.setupState) { 194 | return instance.setupState[key]; 195 | } 196 | const { props } = instance; 197 | if (hasOwn(instance.setupState, key)) { 198 | return instance.setupState[key]; 199 | } 200 | else if (hasOwn(props, key)) { 201 | return props[key]; 202 | } 203 | const publicGetter = publicProxyMaps[key]; 204 | if (publicGetter) { 205 | return publicGetter(instance); 206 | } 207 | }, 208 | }; 209 | 210 | function emit(instance, event, ...args) { 211 | console.log("event====", event); 212 | const { props } = instance; 213 | // TPP 214 | // 先去写一个特定的行为再去写一个通用的行为 215 | const handlersName = toHandlerKey(camelize(event)); 216 | const handlers = props[handlersName]; 217 | handlers && handlers(...args); 218 | } 219 | 220 | function initProps(instance, rawProps) { 221 | instance.props = rawProps || {}; 222 | } 223 | 224 | function initSlots(instance, children) { 225 | // 是否是数组,如果不是就给包裹一层 226 | // instance.slots = Array.isArray(children) ? children : [children]; 227 | const { vnode } = instance; 228 | if (vnode.shapeFlags & 16 /* SLOT_CHILDREN */) { 229 | const slot = {}; 230 | for (let key in children) { 231 | const value = children[key]; 232 | slot[key] = (props) => normalizeSlotsValue(value(props)); 233 | } 234 | instance.slots = slot; 235 | } 236 | } 237 | function normalizeSlotsValue(value) { 238 | return Array.isArray(value) ? value : [value]; 239 | } 240 | 241 | function createComponentInstance(vnode, parent) { 242 | console.log("调试====", parent); 243 | const component = { 244 | vnode: vnode, 245 | type: vnode.type, 246 | setupState: {}, 247 | props: {}, 248 | provides: parent ? parent.provides : {}, 249 | parent, 250 | slot: {}, 251 | isMounted: false, 252 | subTree: {}, 253 | emit: () => { }, 254 | }; 255 | component.emit = emit.bind(null, component); //拿到instance 256 | return component; 257 | } 258 | function setComponentInstance(instance) { 259 | // initProps 260 | // initSlots 261 | initSlots(instance, instance.vnode.children); 262 | initProps(instance, instance.vnode.props); 263 | setupStatefulComponent(instance); 264 | } 265 | function setupStatefulComponent(instance) { 266 | setCurrentInstance(instance); 267 | const Component = instance.type; 268 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandler); 269 | const { setup } = Component; 270 | if (setup) { 271 | const setupResult = setup(shallowReadonly(instance.props), { 272 | emit: instance.emit, 273 | }); //props在子组件不去改变 274 | setCurrentInstance(null); 275 | handelSetupResult(setupResult, instance); 276 | } 277 | } 278 | function handelSetupResult(setupResult, instance) { 279 | if (typeof setupResult === "object") { 280 | instance.setupState = setupResult; 281 | } 282 | finishComponentSetup(instance); 283 | } 284 | function finishComponentSetup(instance) { 285 | const Component = instance.type; 286 | if (Component.render) { 287 | instance.render = Component.render; 288 | } 289 | } 290 | let currentInstance = null; 291 | function getCurrentInstance() { 292 | return currentInstance; 293 | } 294 | function setCurrentInstance(instance) { 295 | currentInstance = instance; 296 | } 297 | 298 | function render(vnode, container) { 299 | patch(null, vnode, container, null); 300 | console.log("render==", vnode, container); 301 | } 302 | function patch(n1, n2, container, parentComponent) { 303 | // 处理组件 304 | // 先判断是不是element 305 | // fragment只渲染children 306 | const { type, shapeFlags } = n2; 307 | switch (type) { 308 | case "Fragment": 309 | processFragment(n1, n2, container, parentComponent); 310 | break; 311 | case "Text": 312 | processText(n1, n2, container); 313 | break; 314 | default: 315 | if (shapeFlags & 1 /* ELEMENT */) { 316 | processElement(n1, n2, container, parentComponent); 317 | } 318 | else if (shapeFlags & 2 /* STATEFUL_COMPONENT */) { 319 | processComponent(n1, n2, container, parentComponent); 320 | } 321 | break; 322 | } 323 | } 324 | function processComponent(n1, vnode, container, parentComponent) { 325 | mountComponent(vnode, container, parentComponent); 326 | } 327 | function mountComponent(initialVNode, container, parentComponent) { 328 | const instance = createComponentInstance(initialVNode, parentComponent); 329 | setComponentInstance(instance); 330 | setupRenderEffect(instance, container, initialVNode); 331 | } 332 | function setupRenderEffect(instance, container, initialVNode) { 333 | effect(() => { 334 | if (!instance.isMounted) { 335 | const { proxy } = instance; 336 | console.log("初始化===", proxy); 337 | const subTree = (instance.subTree = instance.render.call(proxy)); 338 | patch(null, subTree, container, instance); 339 | initialVNode.el = subTree.el; 340 | instance.isMounted = true; 341 | } 342 | else { 343 | console.log("update"); 344 | const { proxy } = instance; 345 | const subTree = instance.render.call(proxy); 346 | const prevSubTree = instance.subTree; 347 | instance.subTree = subTree; 348 | patch(prevSubTree, subTree, container, instance); 349 | // 更新 350 | } 351 | }); 352 | } 353 | function processElement(n1, n2, container, parentComponent) { 354 | if (!n1) { 355 | // 初始化 356 | mountElement(n2, container, parentComponent); 357 | } 358 | else { 359 | // 更新 360 | patchElement(n1, n2); 361 | } 362 | } 363 | function patchElement(n1, n2, container, parentComponent) { 364 | console.log("patchElement"); 365 | const oldProps = n1.props || {}; 366 | const newProps = n2.props || {}; 367 | const el = (n2.el = n1.el); 368 | patchProp(el, oldProps, newProps); 369 | } 370 | function patchProp(el, oldProps, newProps) { 371 | // 遍历新的props 372 | for (const key in newProps) { 373 | const prevProp = oldProps[key]; 374 | const nextProp = newProps[key]; 375 | console.log("111", prevProp, nextProp); 376 | // 如果props不想等就准备更新 377 | if (prevProp !== nextProp) { 378 | hostPatchProp(el, key, prevProp, nextProp); 379 | } 380 | } 381 | } 382 | function mountElement(vnode, container, parentComponent) { 383 | const el = (vnode.el = document.createElement(vnode.type)); 384 | const { children, props, shapeFlags } = vnode; 385 | if (shapeFlags & 4 /* TEXT_CHILDREN */) { 386 | el.textContent = children; 387 | } 388 | else if (shapeFlags & 8 /* ARROW_CHILDREN */) { 389 | mountChild(children, el, parentComponent); 390 | } 391 | for (const key in props) { 392 | const value = props[key]; 393 | const isOn = (key) => /^on[A-Z]/.test(key); 394 | if (isOn(key)) { 395 | const event = key.slice(2).toLowerCase(); 396 | el.addEventListener(event, value); 397 | } 398 | else { 399 | el.setAttribute(key, value); 400 | } 401 | } 402 | container.appendChild(el); 403 | } 404 | function mountChild(vnode, container, parentComponent) { 405 | console.log("mountChild", vnode); 406 | if (Array.isArray(vnode)) { 407 | vnode.forEach((el) => { 408 | patch(null, el, container, parentComponent); 409 | }); 410 | } 411 | } 412 | function processFragment(n1, vnode, container, parentComponent) { 413 | mountChild(vnode, container, parentComponent); 414 | } 415 | function processText(n1, vnode, container) { 416 | // 只渲染文字 417 | const { children } = vnode; 418 | const textNode = (vnode.el = document.createTextNode(children)); 419 | container.append(textNode); 420 | } 421 | function createRender(options) { } 422 | 423 | function createVnode(type, props, children) { 424 | const vnode = { 425 | type, 426 | props, 427 | children, 428 | shapeFlags: getShapeFlags(type), 429 | el: null, 430 | }; 431 | // children 位运算符 432 | if (typeof children === "string") { 433 | vnode.shapeFlags |= 4 /* TEXT_CHILDREN */; 434 | } 435 | else if (Array.isArray(children)) { 436 | vnode.shapeFlags |= 8 /* ARROW_CHILDREN */; 437 | } 438 | // 组件 slot 439 | if (vnode.shapeFlags & 2 /* STATEFUL_COMPONENT */) { 440 | if (typeof children === "object") { 441 | vnode.shapeFlags |= 16 /* SLOT_CHILDREN */; 442 | } 443 | } 444 | return vnode; 445 | } 446 | function createTextVnode(text) { 447 | return createVnode('Text', {}, text); 448 | } 449 | function getShapeFlags(type) { 450 | return typeof type === "string" 451 | ? 1 /* ELEMENT */ 452 | : 2 /* STATEFUL_COMPONENT */; 453 | } 454 | 455 | function createApp(rootComponent) { 456 | return { 457 | mount(rootContainer) { 458 | // 先转换成虚拟节点 459 | // 所有节点都基于虚拟节点 460 | const vnode = createVnode(rootComponent); 461 | render(vnode, rootContainer); 462 | }, 463 | }; 464 | } 465 | 466 | function h(type, props, children) { 467 | return createVnode(type, props, children); 468 | } 469 | 470 | function renderSlot(slots, name, props) { 471 | const slot = slots[name]; 472 | if (slot) { 473 | if (typeof slot === "function") { 474 | return createVnode("Fragment", {}, slot(props)); 475 | } 476 | } 477 | } 478 | 479 | // 存 480 | function provide(key, value) { 481 | const currentInstance = getCurrentInstance(); 482 | if (currentInstance) { 483 | let { provides } = currentInstance; 484 | console.log("存provides====", currentInstance); 485 | const parentProviders = currentInstance.parent && currentInstance.parent.provides; 486 | if (provides === parentProviders) { 487 | provides = currentInstance.provides = Object.create(parentProviders); 488 | } 489 | provides[key] = value; 490 | } 491 | } 492 | // 取 493 | function inject(key, defaultValue) { 494 | const currentInstance = getCurrentInstance(); 495 | console.log("inject=====parentProviders", currentInstance); 496 | if (currentInstance) { 497 | const { parent } = currentInstance; 498 | const parentProviders = parent.provides; 499 | if (key in parentProviders) { 500 | return parentProviders[key]; 501 | } 502 | else if (defaultValue) { 503 | if (typeof defaultValue === "function") { 504 | return defaultValue(); 505 | } 506 | return defaultValue; 507 | } 508 | } 509 | } 510 | 511 | class RefImpl { 512 | constructor(value) { 513 | this._v_isRef = true; 514 | this._rawValue = value; 515 | this._value = convert(value); 516 | // 判断对象是不是对象 517 | this.dep = new Set(); 518 | } 519 | set value(newValue) { 520 | // 对比的时候 521 | if (hasChange(newValue, this._rawValue)) { 522 | this._value = convert(newValue); 523 | this._rawValue = newValue; 524 | triggerEffect(this.dep); 525 | } 526 | } 527 | get value() { 528 | if (isTracking()) { 529 | trackEffect(this.dep); 530 | } 531 | return this._value; 532 | } 533 | } 534 | function convert(value) { 535 | return isObject(value) ? reactive(value) : value; 536 | } 537 | function ref(value) { 538 | return new RefImpl(value); 539 | } 540 | 541 | export { createApp, createRender, createTextVnode, getCurrentInstance, h, inject, provide, ref, renderSlot }; 542 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "before-minivue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/guide-cjs.js", 6 | "module":"lib/guide-esm.js", 7 | "scripts": { 8 | "dev": "node src/index.js --watch", 9 | "test": "jest", 10 | "build": "rollup -c" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@rollup/plugin-typescript": "^8.3.1", 17 | "@vue/reactivity": "^3.2.31" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.17.8", 21 | "@babel/preset-env": "^7.16.11", 22 | "@babel/preset-typescript": "^7.16.7", 23 | "@types/jest": "^27.4.1", 24 | "babel-jest": "^27.5.1", 25 | "jest": "^27.5.1", 26 | "rollup": "^2.70.1", 27 | "tslib": "^2.3.1", 28 | "typescript": "^4.6.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript' 2 | export default { 3 | input: './src/index.ts', 4 | output: [{ 5 | format: 'cjs', 6 | file: "lib/guide.cjs.js", 7 | }, 8 | { 9 | format: 'es', 10 | file: "lib/guide.esm.js", 11 | } 12 | ], 13 | plugins: [typescript()] 14 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | reactive, 3 | effect 4 | } = require('@vue/reactivity'); 5 | 6 | // 声明一个响应式对象 7 | let a = reactive({ 8 | value:10 9 | }); 10 | let b 11 | effect(() => { 12 | // 上来就会执行1次 13 | b = a.value + 1000; 14 | console.log(b); 15 | }) 16 | a.value = 20 //effect还会再次执行 -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | // mini 出口 3 | export * from './runtime-core/index' -------------------------------------------------------------------------------- /src/reactivity/bseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { track, trigger } from "./effect"; 2 | import { reactive, ReactiveFlags, readonly } from "./reactive"; 3 | import { extend, isObject } from "./shared"; 4 | const set = createSetter(); 5 | const get = createGetter(); 6 | const readonlyGet = createGetter(true); 7 | const shallowReadonlyGet = createGetter(true, true); 8 | function createGetter(isReadOnly: boolean = false, shallow: boolean = false) { 9 | return function get(target, key) { 10 | if (key === ReactiveFlags.IS_REACTIVE) { 11 | return !isReadOnly; 12 | } else if (key === ReactiveFlags.IS_READ_ONLY) { 13 | return isReadOnly; 14 | } 15 | 16 | const res = Reflect.get(target, key); 17 | if (shallow) { 18 | // 如果是shallow就不需要深层次的观察 19 | return res; 20 | } 21 | // 看看是不是object 22 | if (isObject(res)) { 23 | return isReadOnly ? readonly(res) : reactive(res); 24 | } 25 | if (!isReadOnly) { 26 | track(target, key); 27 | } 28 | return res; 29 | }; 30 | } 31 | function createSetter() { 32 | return function get(target, key, value) { 33 | const res = Reflect.set(target, key, value); 34 | trigger(target, key); 35 | return res; 36 | }; 37 | } 38 | export const mutableHandlers = { 39 | get, 40 | set, 41 | }; 42 | export const readonlyHandlers = { 43 | get: readonlyGet, 44 | set(target, key, value) { 45 | // readonly的时候不允许set 46 | console.warn(`Attempting to mutate readonly value ${`${target}[${key}]`}`); 47 | return true; 48 | }, 49 | }; 50 | 51 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 52 | get: shallowReadonlyGet, 53 | }); 54 | -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect"; 2 | class computedRefImpl { 3 | private _getter: any; 4 | private _dirty: boolean = true; 5 | private _value: any; 6 | private _effect: ReactiveEffect; 7 | constructor(getter) { 8 | this._getter = getter; 9 | this._effect = new ReactiveEffect(getter, () => { 10 | if (!this._dirty) { 11 | this._dirty = true; 12 | } 13 | }); 14 | } 15 | get value() { 16 | if (this._dirty) { 17 | this._dirty = false; 18 | this._value = this._effect.run(); 19 | } 20 | return this._value; 21 | } 22 | } 23 | export function computed(getter) { 24 | return new computedRefImpl(getter); 25 | } 26 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "./shared"; 2 | let activeEffect; 3 | let shouldTrick; 4 | export class ReactiveEffect { 5 | private _fn: any; 6 | private deps = []; 7 | private active: boolean = true; 8 | onStop?: () => void; 9 | constructor(fn, public scheduler?) { 10 | this._fn = fn; 11 | } 12 | run() { 13 | if (!this.active) { 14 | return this._fn(); 15 | } 16 | shouldTrick = true; 17 | // 收集依赖 18 | activeEffect = this; 19 | const result = this._fn(); 20 | shouldTrick = false; 21 | return result; 22 | } 23 | stop() { 24 | if (this.active) { 25 | clearUpEffect(this); 26 | this.onStop && this.onStop(); 27 | this.active = false; 28 | } 29 | } 30 | } 31 | function clearUpEffect(effect) { 32 | effect.deps.forEach((dep: any) => { 33 | dep.delete(effect); 34 | }); 35 | } 36 | // 依赖收集 37 | const targetMap = new Map(); 38 | export function track(target, key) { 39 | if (!isTracking()) return; 40 | let depsMap = targetMap.get(target); 41 | if (!depsMap) { 42 | depsMap = new Map(); 43 | targetMap.set(target, depsMap); 44 | } 45 | let dep = depsMap.get(key); 46 | if (!dep) { 47 | dep = new Set(); 48 | depsMap.set(key, dep); 49 | } 50 | 51 | trackEffect(dep); 52 | } 53 | export function trackEffect(dep) { 54 | if (dep.has(activeEffect)) return; 55 | 56 | dep.add(activeEffect); 57 | activeEffect.deps.push(dep); 58 | } 59 | export function isTracking() { 60 | return activeEffect !== undefined && shouldTrick; 61 | } 62 | export function effect(fn, options: any = {}) { 63 | // 上来就要调用fn 64 | const scheduler = options.scheduler; //获取 65 | const _effect = new ReactiveEffect(fn, scheduler); 66 | extend(_effect, options); //挂载所有options的值 67 | _effect.run(); 68 | const runner: any = _effect.run.bind(_effect); 69 | runner.effect = _effect; 70 | return runner; 71 | } 72 | 73 | export function trigger(target, key) { 74 | let depsMap = targetMap.get(target); 75 | let deps = depsMap.get(key); 76 | triggerEffect(deps); 77 | } 78 | export function triggerEffect(deps) { 79 | for (const effect of deps) { 80 | if (effect.scheduler) { 81 | effect.scheduler(); 82 | } else { 83 | effect.run(); 84 | } 85 | } 86 | } 87 | export function stop(runner) { 88 | runner.effect.stop(); 89 | } 90 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export function add(a:number, b:number) { 2 | return a + b; 3 | } -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mutableHandlers, 3 | readonlyHandlers, 4 | shallowReadonlyHandlers, 5 | } from "./bseHandlers"; 6 | import { isObject } from "./shared/index"; 7 | export const enum ReactiveFlags { 8 | IS_REACTIVE = "__v_isReactive__", 9 | IS_READ_ONLY = "__v_isReadonly__", 10 | } 11 | 12 | export function reactive(raw) { 13 | return new Proxy(raw, mutableHandlers); 14 | } 15 | 16 | export function readonly(raw) { 17 | return new Proxy(raw, readonlyHandlers); 18 | } 19 | export function isReactive(value) { 20 | return !!value[ReactiveFlags.IS_REACTIVE]; 21 | } 22 | export function isReadOnly(value) { 23 | return !!value[ReactiveFlags.IS_READ_ONLY]; 24 | } 25 | 26 | export function shallowReadonly(raw) { 27 | if (!isObject(raw)) { 28 | console.warn(`target ${raw} 必须是一个对象`); 29 | return raw; 30 | } 31 | return new Proxy(raw, shallowReadonlyHandlers); 32 | } 33 | 34 | export function isProxy(raw: any): boolean { 35 | return isReadOnly(raw) || isReactive(raw); 36 | } 37 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { isTracking, trackEffect, triggerEffect } from "./effect"; 2 | import { reactive } from "./reactive"; 3 | import { hasChange, isObject } from "./shared"; 4 | 5 | class RefImpl { 6 | private _value: any; 7 | public dep; 8 | public _v_isRef: boolean = true; 9 | private _rawValue: any; 10 | constructor(value) { 11 | this._rawValue = value; 12 | this._value = convert(value); 13 | // 判断对象是不是对象 14 | this.dep = new Set(); 15 | } 16 | set value(newValue: any) { 17 | // 对比的时候 18 | if (hasChange(newValue, this._rawValue)) { 19 | this._value = convert(newValue); 20 | this._rawValue = newValue; 21 | triggerEffect(this.dep); 22 | } 23 | } 24 | get value() { 25 | if (isTracking()) { 26 | trackEffect(this.dep); 27 | } 28 | 29 | return this._value; 30 | } 31 | } 32 | function convert(value: any) { 33 | return isObject(value) ? reactive(value) : value; 34 | } 35 | export function ref(value) { 36 | return new RefImpl(value); 37 | } 38 | export function isRef(ref) { 39 | // 如果有值就一定是ref 40 | return !!ref._v_isRef; 41 | } 42 | export function unRef(ref) { 43 | return isRef(ref) ? ref.value : ref; 44 | } 45 | export function proxyRefs(raw: any) { 46 | return new Proxy(raw, { 47 | get(target, key) { 48 | return unRef(Reflect.get(target, key)); 49 | }, 50 | set(target, key, value) { 51 | if (isRef(target[key]) && !isRef(value)) { 52 | return (target[key].value = value); 53 | } else { 54 | return Reflect.set(target, key, value); 55 | } 56 | }, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /src/reactivity/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const extend = Object.assign; 2 | export function isObject(obj) { 3 | return obj !== null && typeof obj === 'object'; 4 | } 5 | export function hasChange(val, newValue) { 6 | return !Object.is(val, newValue); 7 | } -------------------------------------------------------------------------------- /src/reactivity/tests/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "../computed" 2 | import { reactive } from "../reactive" 3 | 4 | // 边写一个测试用例 5 | describe('computed', () => { 6 | it('computed path', () => { 7 | const user = reactive({ age: 11 }) 8 | const age = computed(() => { 9 | return user.age+1 10 | }) 11 | expect(age.value).toBe(12) 12 | }) 13 | it('computed should be lazy', () => { 14 | const user = reactive({ age: 1 }) 15 | const getter = jest.fn(() => { 16 | return user.age 17 | }) 18 | const cValue = computed(getter); 19 | expect(getter).not.toHaveBeenCalled(); 20 | expect(cValue.value).toBe(1); 21 | expect(getter).toHaveBeenCalledTimes(1); //调用了多少次 22 | cValue.value; //再次触发看是否只调用1次 23 | expect(getter).toHaveBeenCalledTimes(1); 24 | user.age = 2; 25 | expect(cValue.value).toBe(2); 26 | 27 | }) 28 | }) -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | 4 | describe("effect", () => { 5 | it("happy path", () => { 6 | const user = reactive({ age: 10 }); 7 | let nextAge; 8 | effect(() => { 9 | nextAge = user.age + 1; 10 | }); 11 | expect(nextAge).toBe(11); 12 | // 更新 13 | user.age++; 14 | expect(nextAge).toBe(12); 15 | }); 16 | it("", () => { 17 | let foo = 10; 18 | const runner=effect(() => { 19 | foo++; 20 | return 'foo'; 21 | }); 22 | expect(foo).toBe(11); 23 | const r = runner(); 24 | expect(foo).toBe(12); 25 | expect(r).toBe('foo') 26 | 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReactive, reactive } from "../reactive"; 2 | 3 | describe("reactive", () => { 4 | it("happy path", () => { 5 | const origin = { foo: 1 }; 6 | const observed = reactive(origin); 7 | expect(observed).not.toBe(origin); 8 | expect(observed.foo).toBe(1); 9 | 10 | expect(isReactive(observed)).toBe(true); 11 | expect(isReactive(origin)).toBe(false); 12 | expect(isProxy(observed)).toBe(true); 13 | }); 14 | it("notsed reactive path", () => { 15 | const origin = { 16 | notsed: { 17 | foo: 1, 18 | }, 19 | array: [{ bar: 2 }], 20 | }; 21 | const observed = reactive(origin); 22 | expect(isReactive(observed.notsed)).toBe(true); 23 | expect(isReactive(observed.array)).toBe(true); 24 | expect(isReactive(observed.array[0])).toBe(true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadOnly, readonly, isProxy } from "../reactive"; 2 | 3 | describe("readonly", () => { 4 | it("happy path", () => { 5 | // no set 6 | 7 | const origin = { foo: 1, bar: { baz: 2 } }; 8 | const wrapped = readonly(origin); 9 | expect(wrapped).not.toBe(origin); 10 | expect(wrapped.foo).toBe(1); 11 | expect(isReadOnly(wrapped)).toBe(true); 12 | expect(isProxy(wrapped)).toBe(true); 13 | }); 14 | it("set warning", () => { 15 | console.warn = jest.fn(); 16 | const user = readonly({ age: 2 }); 17 | user.age = 3; 18 | expect(console.warn).toBeCalled(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/reactivity/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 | }); 28 | 29 | it("should make nested properties reactive", () => { 30 | const a = ref({ 31 | count: 1, 32 | }); 33 | let dummy; 34 | effect(() => { 35 | dummy = a.value.count; 36 | }); 37 | expect(dummy).toBe(1); 38 | a.value.count = 2; 39 | expect(dummy).toBe(2); 40 | }); 41 | 42 | it("isRef", () => { 43 | const a = ref(1); 44 | const user = reactive({ 45 | age: 1, 46 | }); 47 | expect(isRef(a)).toBe(true); 48 | expect(isRef(1)).toBe(false); 49 | expect(isRef(user)).toBe(false); 50 | }); 51 | 52 | it("unRef", () => { 53 | const a = ref(1); 54 | expect(unRef(a)).toBe(1); 55 | expect(unRef(1)).toBe(1); 56 | }); 57 | 58 | it("proxyRefs", () => { 59 | const user = { 60 | age: ref(10), 61 | name: "xiaohong", 62 | }; 63 | 64 | const proxyUser = proxyRefs(user); 65 | expect(user.age.value).toBe(10); 66 | expect(proxyUser.age).toBe(10); 67 | expect(proxyUser.name).toBe("xiaohong"); 68 | 69 | proxyUser.age = 20; 70 | 71 | expect(proxyUser.age).toBe(20); 72 | expect(user.age.value).toBe(20); 73 | 74 | proxyUser.age = ref(10); 75 | expect(proxyUser.age).toBe(10); 76 | expect(user.age.value).toBe(10); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadOnly, shallowReadonly } from "../reactive"; 2 | 3 | describe("shallowReadonly", () => { 4 | test("should not make non-reactive properties reactive", () => { 5 | const props = shallowReadonly({ n: { foo: 1 } }); 6 | expect(isReadOnly(props)).toBe(true); 7 | expect(isReadOnly(props.n)).toBe(false); 8 | }); 9 | 10 | it("should call console.warn when set", () => { 11 | console.warn = jest.fn(); 12 | const user = shallowReadonly({ 13 | age: 10, 14 | }); 15 | 16 | user.age = 11; 17 | expect(console.warn).toHaveBeenCalled(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | // 存 4 | export function provide(key, value) { 5 | const currentInstance: any = getCurrentInstance(); 6 | 7 | if (currentInstance) { 8 | let { provides } = currentInstance; 9 | console.log("存provides====", currentInstance); 10 | const parentProviders = 11 | currentInstance.parent && currentInstance.parent.provides; 12 | if (provides === parentProviders) { 13 | provides = currentInstance.provides = Object.create(parentProviders); 14 | } 15 | provides[key] = value; 16 | } 17 | } 18 | // 取 19 | export function inject(key, defaultValue) { 20 | const currentInstance: any = getCurrentInstance(); 21 | console.log("inject=====parentProviders", currentInstance); 22 | if (currentInstance) { 23 | const { parent } = currentInstance; 24 | const parentProviders = parent.provides; 25 | if (key in parentProviders) { 26 | return parentProviders[key]; 27 | } else if (defaultValue) { 28 | if (typeof defaultValue === "function") { 29 | return defaultValue(); 30 | } 31 | return defaultValue; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/runtime-core/componemtPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from "../shared/sharedFlags"; 2 | 3 | const publicProxyMaps = { 4 | $el: (instance) => instance.vnode.el, 5 | $slots:(instance) => instance.slots, 6 | }; 7 | export const publicInstanceProxyHandler = { 8 | get({ _: instance }, key) { 9 | if (key in instance.setupState) { 10 | return instance.setupState[key]; 11 | } 12 | const { props } = instance; 13 | 14 | if (hasOwn(instance.setupState, key)) { 15 | return instance.setupState[key]; 16 | } else if (hasOwn(props, key)) { 17 | return props[key]; 18 | } 19 | const publicGetter = publicProxyMaps[key]; 20 | if (publicGetter) { 21 | return publicGetter(instance); 22 | } 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { shallowReadonly } from "../reactivity/reactive"; 2 | import { publicInstanceProxyHandler } from "./componemtPublicInstance"; 3 | import { emit } from "./componentEmit"; 4 | import { initProps } from "./componentProps"; 5 | import { initSlots } from "./componentSlots"; 6 | 7 | export function createComponentInstance(vnode, parent) { 8 | console.log("调试====", parent); 9 | const component = { 10 | vnode: vnode, 11 | type: vnode.type, 12 | setupState: {}, 13 | props: {}, 14 | provides:parent?parent.provides: {}, 15 | parent, 16 | slot: {}, 17 | isMounted:false, 18 | subTree:{}, 19 | emit: () => {}, 20 | }; 21 | component.emit = emit.bind(null, component) as any; //拿到instance 22 | return component; 23 | } 24 | export function setComponentInstance(instance) { 25 | // initProps 26 | // initSlots 27 | initSlots(instance, instance.vnode.children); 28 | initProps(instance, instance.vnode.props); 29 | setupStatefulComponent(instance); 30 | } 31 | function setupStatefulComponent(instance) { 32 | setCurrentInstance(instance); 33 | const Component = instance.type; 34 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandler); 35 | const { setup } = Component; 36 | if (setup) { 37 | const setupResult = setup(shallowReadonly(instance.props), { 38 | emit: instance.emit, 39 | }); //props在子组件不去改变 40 | setCurrentInstance(null); 41 | handelSetupResult(setupResult, instance); 42 | } 43 | } 44 | 45 | function handelSetupResult(setupResult: any, instance: any) { 46 | if (typeof setupResult === "object") { 47 | instance.setupState = setupResult; 48 | } 49 | finishComponentSetup(instance); 50 | } 51 | function finishComponentSetup(instance: any) { 52 | const Component = instance.type; 53 | if (Component.render) { 54 | instance.render = Component.render; 55 | } 56 | } 57 | 58 | let currentInstance = null; 59 | export function getCurrentInstance() { 60 | return currentInstance; 61 | } 62 | export function setCurrentInstance(instance: any) { 63 | currentInstance = instance; 64 | } 65 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerKey } from "../shared/sharedFlags"; 2 | 3 | export function emit(instance, event,...args: any[]): void { 4 | console.log("event====", event); 5 | const { props } = instance; 6 | // TPP 7 | // 先去写一个特定的行为再去写一个通用的行为 8 | 9 | const handlersName = toHandlerKey(camelize(event)); 10 | const handlers = props[handlersName]; 11 | handlers && handlers(...args); 12 | } 13 | -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | 2 | export function initProps(instance, rawProps) { 3 | instance.props = rawProps||{} 4 | } -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/sharedFlags"; 2 | 3 | export function initSlots(instance, children) { 4 | // 是否是数组,如果不是就给包裹一层 5 | // instance.slots = Array.isArray(children) ? children : [children]; 6 | const { vnode } = instance; 7 | if (vnode.shapeFlags & ShapeFlags.SLOT_CHILDREN) { 8 | const slot = {}; 9 | for (let key in children) { 10 | const value = children[key]; 11 | slot[key] = (props) => normalizeSlotsValue(value(props)); 12 | } 13 | instance.slots = slot; 14 | } 15 | } 16 | function normalizeSlotsValue(value) { 17 | return Array.isArray(value) ? value : [value]; 18 | } 19 | -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | import { render } from "./render"; 2 | import { createVnode } from "./vnode"; 3 | 4 | export function createApp(rootComponent) { 5 | return { 6 | mount(rootContainer) { 7 | // 先转换成虚拟节点 8 | // 所有节点都基于虚拟节点 9 | const vnode = createVnode(rootComponent); 10 | render(vnode, rootContainer); 11 | }, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVnode } from "./vnode"; 2 | export function h(type, props?, children?) { 3 | return createVnode(type, props, children); 4 | } 5 | -------------------------------------------------------------------------------- /src/runtime-core/helper/renderSlot.ts: -------------------------------------------------------------------------------- 1 | import { createVnode } from "../vnode"; 2 | 3 | export function renderSlot(slots, name?,props?) { 4 | const slot = slots[name]; 5 | if (slot) { 6 | if (typeof slot === "function") { 7 | return createVnode("Fragment", {}, slot(props)); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | export { createApp } from "./createApp"; 2 | export { h } from "./h"; 3 | export { renderSlot } from "./helper/renderSlot"; 4 | export { createTextVnode } from "./vnode"; 5 | export { getCurrentInstance } from "./component"; 6 | export { inject, provide } from "./apiInject"; 7 | export { createRender } from './render'; 8 | export {ref} from '../reactivity/ref' 9 | -------------------------------------------------------------------------------- /src/runtime-core/render.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../reactivity/effect"; 2 | import { isObject } from "../reactivity/shared/index"; 3 | import { hostPatchProp } from "../runtime-dom"; 4 | import { ShapeFlags } from "../shared/sharedFlags"; 5 | import { createComponentInstance, setComponentInstance } from "./component"; 6 | 7 | export function render(vnode, container) { 8 | patch(null, vnode, container, null); 9 | console.log("render==", vnode, container); 10 | } 11 | function patch(n1, n2, container, parentComponent) { 12 | // 处理组件 13 | // 先判断是不是element 14 | // fragment只渲染children 15 | const { type, shapeFlags } = n2; 16 | 17 | switch (type) { 18 | case "Fragment": 19 | processFragment(n1, n2, container, parentComponent); 20 | break; 21 | case "Text": 22 | processText(n1, n2, container); 23 | break; 24 | default: 25 | if (shapeFlags & ShapeFlags.ELEMENT) { 26 | processElement(n1, n2, container, parentComponent); 27 | } else if (shapeFlags & ShapeFlags.STATEFUL_COMPONENT) { 28 | processComponent(n1, n2, container, parentComponent); 29 | } 30 | break; 31 | } 32 | } 33 | 34 | function processComponent(n1, vnode: any, container: any, parentComponent) { 35 | mountComponent(vnode, container, parentComponent); 36 | } 37 | function mountComponent( 38 | initialVNode: any, 39 | container: any, 40 | parentComponent: any 41 | ) { 42 | const instance = createComponentInstance(initialVNode, parentComponent); 43 | setComponentInstance(instance); 44 | setupRenderEffect(instance, container, initialVNode); 45 | } 46 | 47 | function setupRenderEffect(instance: any, container: any, initialVNode: any) { 48 | effect(() => { 49 | if (!instance.isMounted) { 50 | const { proxy } = instance; 51 | console.log("初始化===", proxy); 52 | 53 | const subTree = (instance.subTree = instance.render.call(proxy)); 54 | patch(null, subTree, container, instance); 55 | initialVNode.el = subTree.el; 56 | instance.isMounted = true; 57 | } else { 58 | console.log("update"); 59 | const { proxy } = instance; 60 | const subTree = instance.render.call(proxy); 61 | const prevSubTree = instance.subTree; 62 | instance.subTree = subTree; 63 | patch(prevSubTree, subTree, container, instance); 64 | 65 | // 更新 66 | } 67 | }); 68 | } 69 | function processElement(n1, n2: any, container: any, parentComponent) { 70 | if (!n1) { 71 | // 初始化 72 | mountElement(n2, container, parentComponent); 73 | } else { 74 | // 更新 75 | patchElement(n1, n2, container, parentComponent); 76 | } 77 | } 78 | function patchElement(n1, n2, container, parentComponent) { 79 | console.log("patchElement"); 80 | const oldProps = n1.props || {}; 81 | const newProps = n2.props || {}; 82 | const el = (n2.el = n1.el); 83 | patchChildren(n1,n2) 84 | patchProp(el,oldProps, newProps); 85 | } 86 | function patchChildren(n1, n2) { 87 | const {shapeFlag} = n2.shapeFlag; 88 | const {prevShapeFlag} = n1.shapeFlag; 89 | if(shapeFlag & ShapeFlags.TEXT_CHILDREN){ 90 | if(prevShapeFlag & ShapeFlags.ARROW_CHILDREN){ 91 | // 把老的清空 92 | unMountChildren(n1.children) 93 | } 94 | } 95 | 96 | 97 | } 98 | function unMountChildren(children){ 99 | for(let i =0;i< children.length;i++){ 100 | const el=children[i].el; 101 | // hostRemove(el); 102 | } 103 | } 104 | function patchProp(el,oldProps, newProps) { 105 | // 遍历新的props 106 | for (const key in newProps) { 107 | const prevProp = oldProps[key]; 108 | const nextProp = newProps[key]; 109 | console.log("111", prevProp, nextProp); 110 | 111 | // 如果props不想等就准备更新 112 | if (prevProp !== nextProp) { 113 | hostPatchProp(el, key, prevProp, nextProp); 114 | } 115 | } 116 | for (const key in oldProps) { 117 | // 如果不在新的props就需要删除当前熟悉 118 | if(!(key in newProps)){ 119 | hostPatchProp(el, key, oldProps[key], null); 120 | 121 | } 122 | } 123 | } 124 | function mountElement(vnode: any, container: any, parentComponent) { 125 | const el = (vnode.el = document.createElement(vnode.type)); 126 | const { children, props, shapeFlags } = vnode; 127 | if (shapeFlags & ShapeFlags.TEXT_CHILDREN) { 128 | el.textContent = children; 129 | } else if (shapeFlags & ShapeFlags.ARROW_CHILDREN) { 130 | mountChild(children, el, parentComponent); 131 | } 132 | 133 | for (const key in props) { 134 | const value = props[key]; 135 | const isOn = (key) => /^on[A-Z]/.test(key); 136 | if (isOn(key)) { 137 | const event = key.slice(2).toLowerCase(); 138 | el.addEventListener(event, value); 139 | } else { 140 | el.setAttribute(key, value); 141 | } 142 | } 143 | 144 | container.appendChild(el); 145 | } 146 | function mountChild(vnode: any, container: any, parentComponent) { 147 | console.log("mountChild", vnode); 148 | if (Array.isArray(vnode)) { 149 | vnode.forEach((el) => { 150 | patch(null, el, container, parentComponent); 151 | }); 152 | } 153 | } 154 | function processFragment(n1, vnode: any, container: any, parentComponent) { 155 | mountChild(vnode, container, parentComponent); 156 | } 157 | function processText(n1, vnode: any, container: any) { 158 | // 只渲染文字 159 | const { children } = vnode; 160 | const textNode = (vnode.el = document.createTextNode(children)); 161 | container.append(textNode); 162 | } 163 | 164 | export function createRender(options) {} 165 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/sharedFlags"; 2 | 3 | export const Text = Symbol("Text"); 4 | export function createVnode(type, props?, children?) { 5 | const vnode = { 6 | type, 7 | props, 8 | children, 9 | shapeFlags: getShapeFlags(type), 10 | el: null, 11 | }; 12 | // children 位运算符 13 | if (typeof children === "string") { 14 | vnode.shapeFlags |= ShapeFlags.TEXT_CHILDREN; 15 | } else if (Array.isArray(children)) { 16 | vnode.shapeFlags |= ShapeFlags.ARROW_CHILDREN; 17 | } 18 | // 组件 slot 19 | if (vnode.shapeFlags & ShapeFlags.STATEFUL_COMPONENT) { 20 | if (typeof children === "object") { 21 | vnode.shapeFlags |= ShapeFlags.SLOT_CHILDREN; 22 | } 23 | } 24 | return vnode; 25 | } 26 | export function createTextVnode(text: string) { 27 | return createVnode('Text', {}, text); 28 | } 29 | function getShapeFlags(type: any) { 30 | return typeof type === "string" 31 | ? ShapeFlags.ELEMENT 32 | : ShapeFlags.STATEFUL_COMPONENT; 33 | } 34 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRender } from "../runtime-core/render"; 2 | function createElement(type) { 3 | return document.createElement(type); 4 | } 5 | function patchProp(el, key, prevValue, nextValue) { 6 | const isOn = (key) => /^on[A-Z]/.test(key); 7 | if (isOn(key)) { 8 | const event = key.slice(2).toLowerCase(); 9 | el.addEventListener(event, nextValue); 10 | } else { 11 | el.setAttribute(key, nextValue); 12 | } 13 | } 14 | export function hostPatchProp(el, key, prevValue, nextValue) { 15 | const isOn = (key) => /^on[A-Z]/.test(key); 16 | if (isOn(key)) { 17 | const event = key.slice(2).toLowerCase(); 18 | el.addEventListener(event, nextValue); 19 | } else { 20 | if (nextValue === undefined || nextValue === null) { 21 | el.removeAttribute(key, nextValue); 22 | } else { 23 | el.setAttribute(key, nextValue); 24 | } 25 | } 26 | } 27 | function insert(el, parent) { 28 | parent.appendChild(el); 29 | } 30 | 31 | const renderer = createRender({ 32 | createElement, 33 | patchProp, 34 | insert, 35 | hostPatchProp, 36 | }); 37 | -------------------------------------------------------------------------------- /src/shared/sharedFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags { 2 | ELEMENT = 1, //01 3 | STATEFUL_COMPONENT = 1 << 1, //10 4 | TEXT_CHILDREN = 1 << 2, //100 5 | ARROW_CHILDREN = 1 << 3, //1000 6 | SLOT_CHILDREN = 1 << 4, // 7 | } 8 | export const hasOwn = (val, key) => 9 | Object.prototype.hasOwnProperty.call(val, key); 10 | export const capitalize = (str: string) => { 11 | return str.charAt(0).toUpperCase() + str.slice(1); 12 | }; 13 | export const toHandlerKey = (str: string) => { 14 | return str ? `on${capitalize(str)}` : ""; 15 | }; 16 | export const camelize = (str: string) => { 17 | return str.replace(/-(\w)/g, (_, c: string) => { 18 | return c ? c.toUpperCase() : ""; 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /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"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "esnext", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | } 101 | } 102 | --------------------------------------------------------------------------------