├── .gitignore ├── .vscode ├── cpell.json └── settings.json ├── README.md ├── babel.config.js ├── example ├── apiInject │ ├── App.js │ └── index.html ├── componentEmit │ ├── App.js │ ├── foo.js │ ├── index.html │ └── main.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ ├── index.html │ └── main.js ├── componentslot │ ├── App.js │ ├── foo.js │ ├── index.html │ └── main.js ├── currentInstance │ ├── App.js │ ├── foo.js │ ├── index.html │ └── main.js ├── customRenderer │ ├── App.js │ ├── index.html │ └── main.js ├── helloworld │ ├── App.js │ ├── foo.js │ ├── index.html │ └── main.js ├── nextTicker │ ├── App.js │ ├── index.html │ └── main.js ├── patchChildren │ ├── App.js │ ├── ArrayToAarray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js └── update │ ├── App.js │ ├── index.html │ └── main.js ├── finite-state-machine └── index.js ├── lib ├── guide-mini-vue.cjs.js └── guide-mini-vue.esm.js ├── package.json ├── rollup.config.js ├── src ├── compiler-core │ ├── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── parse.ts │ │ └── transform.ts │ └── tests │ │ ├── __snapshots__ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts ├── drawio │ ├── parse.drawio.svg │ ├── reg.drawio.svg │ └── transform.drawio.svg ├── index.ts ├── reactivity │ ├── baseHandlers.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── test │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── index.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── componentUpdateUtils.ts │ ├── createApp.ts │ ├── h.ts │ ├── index.ts │ ├── renderSlots.ts │ ├── renderer.ts │ ├── scheduler.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ ├── Shapeflags.ts │ └── index.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | package.json -------------------------------------------------------------------------------- /.vscode/cpell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": [ 5 | "mkdirp", 6 | "tsmerge", 7 | "githubusercontent", 8 | "streetsidesoftware", 9 | "vsmarketplacebadge", 10 | "visualstudio" 11 | ], 12 | "flagWords": [ 13 | "hte" 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #

mini-vue

2 | # 内容参考仓库 https://github.com/cuixiaorui/mini-vue 3 | ## 基于TDD单侧驱动逻辑 大体实现了以下流程 4 | ## reactivity模块 5 | * effect&reactive&依赖收集&触发依赖 6 | * effect的runner&scheduler 7 | * effect的stop 8 | * readonly系列 9 | * isProxy 10 | * ref系列 11 | * computed 12 | * rollup打包 13 | ## runtime-core 14 | * 初始化element 15 | * shapeFlags 16 | * 注册事件功能 17 | * 组件props 18 | * 组件emit 19 | * 组件slots 20 | * provide-inject 21 | * 自定义渲染器custom renderer [上一模块](#mini-vue) 22 | ## runtime-dom 23 | * 更新element的流程搭建 24 | * 更新element的props 25 | * 更新element的children 26 | * 更新element的children双端对比diff算法1 27 | * 更新element的children双端对比diff算法2 28 | * 更新element的children双端对比diff算法3 [上一模块](#reactivity模块) 29 | ## 编译模块 30 | * nextTick 31 | * 编译模板概述 32 | * 解析插值功能 33 | * 解析element 34 | * 解析text功能 35 | * 解析三种联合类型 36 | * parse的实现原理&有限状态机 [上一模块](#runtime-core) 37 | ## 代码编译---------------------- 38 | ## template编译------------------ 39 | [回到顶部](#mini-vue) 40 | 41 | vue3 core-code重点 42 | 总结笔记 https://juejin.cn/column/7089050969648365581
43 | 44 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets:[["@babel/preset-env",{targets:{node:"current"}}], 3 | "@babel/preset-typescript"] // 按照当前的node环境 4 | } -------------------------------------------------------------------------------- /example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | // 组件 provide 和 inject功能 2 | import {h,provide,inject} from '../../lib/guide-mini-vue.esm.js' 3 | 4 | const Provider = { 5 | name:"Provider", 6 | setup(){ 7 | provide("foo","fooVal") 8 | provide("bar","barVal") 9 | }, 10 | render() { 11 | return h("div",{},[h("p",{},"Provider"),h(ProviderTwo)]) 12 | } 13 | } 14 | const ProviderTwo = { 15 | name:"ProviderTwo", 16 | setup(){ 17 | provide("foo",'fooTwo') 18 | const foo = inject('foo') 19 | return{ 20 | foo 21 | } 22 | }, 23 | render() { 24 | return h("div",{},[h("p",{},`ProviderTwo foo:${this.foo}`),h(Consumer)]) 25 | } 26 | } 27 | const Consumer = { 28 | name:"Consumer", 29 | setup(){ 30 | const foo = inject("foo") 31 | const bar = inject("bar") 32 | // const baz = inject('baz','bazDefault') 33 | const baz = inject('baz',()=>'bazDefault') 34 | return { 35 | foo, 36 | bar, 37 | baz 38 | } 39 | }, 40 | render(){ 41 | return h("div",{},`Consumer: -${this.foo} - ${this.bar} - ${this.baz}`) 42 | } 43 | } 44 | 45 | export default{ 46 | name:'App', 47 | setup(){}, 48 | render(){ 49 | return h('div',{},[h("p",{},'apiInject'),h(Provider)]) 50 | } 51 | } -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 18 | 19 | -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import {h} from '../../lib/guide-mini-vue.esm.js' 2 | import { Foo } from './foo.js' 3 | window.self = null 4 | export const App = { 5 | name:"App", 6 | // .vue 7 | // 我们还未实现template的编译功能 我们先去实现我们的render函数 8 | render(){ 9 | window.self = this 10 | 11 | return h('div',{ 12 | id:"root", 13 | class:["red","hard"], 14 | onClick(){ 15 | // console.log('click') 16 | }, 17 | onMousedown(){ 18 | // console.log('123') 19 | } 20 | }, 21 | // setupState 22 | // $el 23 | // [h("p",{class:"red"},"hi"), 24 | // h("p",{class:'blue'},"mini-vue")] 25 | [h("div",{},"hi, "+ this.msg),h(Foo,{count:1})],'hi ' + this.msg) 26 | }, 27 | setup(){ 28 | // composition api 29 | return { 30 | msg:'mini-vue' 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /example/componentEmit/foo.js: -------------------------------------------------------------------------------- 1 | import {h} from '../../lib/guide-mini-vue.esm.js' 2 | export const Foo ={ 3 | setup(props) { 4 | // porps.count 5 | // console.log(props) 6 | // 3 props是shallowReadOnly属性 7 | props.count++ 8 | }, 9 | render(){ 10 | return h('div',{},"foo" + this.count) 11 | } 12 | } -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createAPP} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | 6 | const rootContainer = document.querySelector('#app') 7 | createAPP(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js' 2 | import Child from './Child.js' 3 | export const App = { 4 | name:"App", 5 | setup(){ 6 | const msg = ref('123') 7 | const count = ref(1) 8 | window.msg = msg 9 | 10 | const changeChildProps = ()=>{ 11 | msg.value = '456' 12 | console.log(msg.value) 13 | } 14 | 15 | const changeCount = ()=>{ 16 | count.value++ 17 | console.log(count.value) 18 | } 19 | return { 20 | msg,changeChildProps,changeCount,count 21 | } 22 | }, 23 | render(){ 24 | return h('div',{}, 25 | [h("div",{},"你好"), 26 | h('button',{ 27 | onClick:this.changeChildProps 28 | },'change child props'), 29 | h(Child,{ 30 | msg:this.msg}), 31 | h('button',{ 32 | onClick:this.changeCount 33 | },'change self count'), 34 | h('p',{},'count: '+this.count)]) 35 | } 36 | } -------------------------------------------------------------------------------- /example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import {h} from '../../lib/guide-mini-vue.esm.js' 2 | export default { 3 | name:'Child', 4 | setup(props,{emit}) {}, 5 | render(proxy) { 6 | return h('div',{},[h('div',{},'child - props - msg: ' + this.$props.msg)]) 7 | }, 8 | } -------------------------------------------------------------------------------- /example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/componentUpdate/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createApp} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | 6 | const rootContainer = document.querySelector('#app') 7 | createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/componentslot/App.js: -------------------------------------------------------------------------------- 1 | import {h,createTextVNode} from '../../lib/guide-mini-vue.esm.js' 2 | import { Foo } from './foo.js' 3 | export const App = { 4 | name:"App", 5 | render(){ 6 | const app = h('div',{},"App") 7 | // 接受两个数据形式 数组和单个 8 | // 把数组的形式换成对象的key模式 9 | // const foo = h(Foo,{},[h("p",{},"123"),h('p',{},'234')]) 10 | const foo = h(Foo,{},{ 11 | // 对text节点进行特殊的处理 12 | header:({age})=> [h("p",{},"header" + age),createTextVNode("你好呀")], 13 | footer: () => h('p',{},'footer') 14 | }) 15 | return h('div',{},[app,foo])}, 16 | setup(){ 17 | return { 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /example/componentslot/foo.js: -------------------------------------------------------------------------------- 1 | import {h,renderSlots} from '../../lib/guide-mini-vue.esm.js' 2 | export const Foo ={ 3 | setup() { 4 | return {} 5 | }, 6 | render(){ 7 | const foo = h("p",{},"foo") 8 | // Foo .vnode . children 9 | console.log(this.$slots) 10 | // children ==> 必须是虚拟节点 11 | // 我们需要将内部的东西转换成虚拟节点 12 | 13 | // 将下面的逻辑封装成函数 帮助我们渲染我们的slots 14 | // 添加渲染指定位置的逻辑 15 | // 具名插槽的实现 通过指定名字和指定的渲染位置 16 | // 实现作用域插槽 把我们foo组件内部的变量传出去 17 | const age = 10 18 | return h('div',{},[renderSlots(this.$slots,"header",{age}),foo,renderSlots(this.$slots,"footer")]) 19 | } 20 | } -------------------------------------------------------------------------------- /example/componentslot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/componentslot/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createAPP} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | 6 | const rootContainer = document.querySelector('#app') 7 | createAPP(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import {h,getCurrentInstance} from '../../lib/guide-mini-vue.esm.js' 2 | import { Foo } from './foo.js' 3 | export const App = { 4 | name:"App", 5 | render(){ 6 | // emit 7 | return h('div',{},[ h('p',{},"getCurremtInstance Demo"),h(Foo)]) 8 | }, 9 | setup(){ 10 | const instance = getCurrentInstance() 11 | console.log('App: ',instance) 12 | } 13 | } -------------------------------------------------------------------------------- /example/currentInstance/foo.js: -------------------------------------------------------------------------------- 1 | import {getCurrentInstance, h} from '../../lib/guide-mini-vue.esm.js' 2 | export const Foo ={ 3 | name:'Foo', 4 | setup() { 5 | const instance = getCurrentInstance() 6 | console.log("Foo",instance) 7 | return {} 8 | }, 9 | render(){ 10 | return h('div',{},"foo") 11 | } 12 | } -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createAPP} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | 6 | const rootContainer = document.querySelector('#app') 7 | createAPP(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import {h} from '../../lib/guide-mini-vue.esm.js' 2 | 3 | 4 | export const App = { 5 | setup(props) { 6 | return{ 7 | x:100, 8 | y:100 9 | } 10 | }, 11 | render() { 12 | return h('rect',{x:this.x,y:this.y}) 13 | }, 14 | } -------------------------------------------------------------------------------- /example/customRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /example/customRenderer/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createRenderer} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | console.log(PIXI) 6 | 7 | const game = new PIXI.Application({ 8 | width:500, 9 | height:500 10 | }) 11 | 12 | document.body.append(game.view) 13 | 14 | const renderer = createRenderer({ 15 | createElement(type){ 16 | if(type === 'rect'){ 17 | const rect = new PIXI.Graphics() 18 | rect.beginFill(0xff0000) 19 | rect.drawRect(0,0,100,100) 20 | rect.endFill() 21 | 22 | return rect 23 | } 24 | }, 25 | 26 | patchProp(el,key,val){ 27 | el[key] = val 28 | }, 29 | 30 | insert(el,parent){ 31 | parent.addChild(el) 32 | } 33 | }) 34 | 35 | renderer.createApp(App).mount(game.stage) 36 | 37 | // const rootContainer = document.querySelector('#app') 38 | // createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/helloworld/App.js: -------------------------------------------------------------------------------- 1 | import {h} from '../../lib/guide-mini-vue.esm.js' 2 | import { Foo } from './foo.js' 3 | export const App = { 4 | name:"App", 5 | render(){ 6 | // emit 7 | return h('div',{ 8 | }, 9 | [h("div",{},"App"),h(Foo,{ 10 | // emit类似于我们的element设置的on事件 11 | onAdd(a,b){ 12 | // console.log("onAdd",a,b) 13 | }, 14 | onAddFoo(){ 15 | // console.log('onAddFoo') 16 | } 17 | })]) 18 | }, 19 | setup(){ 20 | return { 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /example/helloworld/foo.js: -------------------------------------------------------------------------------- 1 | import {h} from '../../lib/guide-mini-vue.esm.js' 2 | export const Foo ={ 3 | setup(props,{emit}) { 4 | const emitAdd = ()=>{ 5 | // console.log("emit Add") 6 | emit("add-foo",1,2) 7 | emit('add-foo') 8 | } 9 | return { 10 | emitAdd 11 | } 12 | 13 | }, 14 | render(){ 15 | const btn = h('button',{ 16 | onClick:this.emitAdd 17 | },"emitAdd") 18 | const foo = h("p",{},"foo") 19 | return h('div',{},[foo,btn]) 20 | } 21 | } -------------------------------------------------------------------------------- /example/helloworld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/helloworld/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createApp} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | 6 | const rootContainer = document.querySelector('#app') 7 | createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js' 2 | export const App = { 3 | name:"App", 4 | setup(){ 5 | const count = ref(1) 6 | 7 | function onClick(){ 8 | for (let i = 0; i <100; i++) { 9 | console.log('update') 10 | count.value = i 11 | } 12 | } 13 | return { 14 | onClick, 15 | count 16 | } 17 | }, 18 | render(){ 19 | const button = h('button',{onClick:this.onClick},'update') 20 | const p = h('p',{},'count ' + this.count ) 21 | return h('div',{},[button,p]) 22 | }, 23 | } -------------------------------------------------------------------------------- /example/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createApp} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | 6 | const rootContainer = document.querySelector('#app') 7 | createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import {h} from '../../lib/guide-mini-vue.esm.js' 2 | import ArrayToText from './ArrayToText.js' 3 | import ArrayToArray from './ArrayToAarray.js' 4 | import TextToText from './TextToText.js' 5 | import TextToArray from './TextToArray.js' 6 | export default{ 7 | name:"App", 8 | setup(){ 9 | 10 | }, 11 | render(){ 12 | return h('div',{tId:1},[ 13 | h('p',{},'主页'), 14 | // 老的是数组 新的是文本 15 | // h(ArrayToText), 16 | // 老的是文本新的是文本 17 | // h(TextToText) 18 | // 老的是文本新的是数组 19 | // h(TextToArray) 20 | // 老的是数组新的数组 21 | h(ArrayToArray) 22 | ]) 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /example/patchChildren/ArrayToAarray.js: -------------------------------------------------------------------------------- 1 | // 新的是array 2 | // 老的也是array 3 | 4 | import {ref,h} from '../../lib/guide-mini-vue.esm.js' 5 | 6 | // 1.左侧的对比 7 | // (a,b)c 8 | // (a,b)d e 9 | // const prevChildren = [ 10 | // h('p',{key:'A'},'A'), 11 | // h('p',{key:'B'},'B'), 12 | // h('p',{key:'C'},'C') 13 | // ] 14 | // const nextChildren = [ 15 | // h('p',{key:'A'},'A'), 16 | // h('p',{key:'B'},'B'), 17 | // h('p',{key:'D'},'D'), 18 | // h('p',{key:'E'},'E') 19 | // ] 20 | // 2.右侧的对比 21 | // a (b c) 22 | // d e(b c) 23 | // const prevChildren = [ 24 | // h('p',{key:'A'},'A'), 25 | // h('p',{key:'B'},'B'), 26 | // h('p',{key:'C'},'C') 27 | // ] 28 | // const nextChildren = [ 29 | // h('p',{key:'D'},'D'), 30 | // h('p',{key:'E'},'E'), 31 | // h('p',{key:'B'},'B'), 32 | // h('p',{key:'C'},'C') 33 | // ] 34 | 35 | // 3.新的比老的长左侧 36 | // 创建新的 37 | // 左侧 38 | // (a,b) 39 | // (a,b,c) 40 | // i=2,e1 =1,e2=2 41 | // const prevChildren = [h('p',{key:'A'},'A'),h('p',{key:'B'},'B')] 42 | // const nextChildren = [ 43 | // h('p',{key:'A'},'A'), 44 | // h('p',{key:'B'},'B'), 45 | // h('p',{key:'C'},'C'), 46 | // h('p',{key:'D'},'D')] 47 | 48 | // 右侧 49 | // (a,b) 50 | // c (a,b) 51 | // i=0 e1 = -1 e2 = 0 52 | // const prevChildren = [h('p',{key:'A'},'A'),h('p',{key:'B'},'B')] 53 | // const nextChildren = [ 54 | // h('p',{key:'D'},'D'), 55 | // h('p',{key:'C'},'C'), 56 | // h('p',{key:'A'},'A'), 57 | // h('p',{key:'B'},'B')] 58 | 59 | // 4 老的比新的长 右侧 60 | // 删除老的 61 | // 右侧 62 | // (a,b) c d 63 | // (a,b) 64 | // const prevChildren = [h('p',{key:'A'},'A'),h('p',{key:'B'},'B'),h('p',{key:'C'},'C'),h('p',{key:'D'},'D')] 65 | // const nextChildren = [ 66 | // h('p',{key:'A'},'A'), 67 | // h('p',{key:'B'},'B')] 68 | // 老的比新的长 左侧 69 | // 删除老的 70 | // a,b(c,d) 71 | // (c,d) 72 | // const prevChildren = [h('p',{key:'A'},'A'),h('p',{key:'B'},'B'),h('p',{key:'C'},'C'),h('p',{key:'D'},'D')] 73 | // const nextChildren = [ 74 | // h('p',{key:'C'},'C'), 75 | // h('p',{key:'D'},'D')] 76 | 77 | // 5对比中间的部分 78 | // 删除老的 (在老的里面存在,新的里面不存在) 79 | // 5.1 80 | // a,b(c,d),f,g 81 | // a,b(e,c),f,g 82 | // D 节点在新的里面没有的 需要删除 83 | // C 节点在props也发生了变化 84 | 85 | // const prevChildren = [h('p',{key:'A'},'A'), 86 | // h('p',{key:'B'},'B'), 87 | // h('p',{key:'C',id:'c-prev'},'C'), 88 | // h('p',{key:'D'},'D'), 89 | // h('p',{key:'F'},'F'), 90 | // h('p',{key:'G'},'G')] 91 | // const nextChildren = [h('p',{key:'A'},'A'), 92 | // h('p',{key:'B'},'B'), 93 | // h('p',{key:'E'},'E'), 94 | // h('p',{key:'C',id:'c-next'},'C'), 95 | // h('p',{key:'F'},'F'), 96 | // h('p',{key:'G'},'G')] 97 | 98 | // 5.1.1 99 | // a,b,(c,e,d),f,g 100 | // a,b,(e,c),f,g 101 | // 中间部分 老的比新的多 那么多出来的直接就可以被干掉 逻辑优化 102 | // const prevChildren = [h('p',{key:'A'},'A'), 103 | // h('p',{key:'B'},'B'), 104 | // h('p',{key:'C',id:'c-prev'},'C'), 105 | // h('p',{key:'E'},'E'), 106 | // h('p',{key:'D'},'D'), 107 | // h('p',{key:'F'},'F'), 108 | // h('p',{key:'G'},'G')] 109 | // const nextChildren = [h('p',{key:'A'},'A'), 110 | // h('p',{key:'B'},'B'), 111 | // h('p',{key:'E'},'E'), 112 | // h('p',{key:'C',id:'c-next'},'C'), 113 | // h('p',{key:'F'},'F'), 114 | // h('p',{key:'G'},'G')] 115 | 116 | // 2--移动 (节点存在于新的和老的里面 但是位置变了) 117 | 118 | // 2.1 119 | // a,b(c,d,e),f,g 120 | // a,b(e,c,d),f,g 121 | // 最长子序列: [1,2] 122 | 123 | // const prevChildren = [h('p',{key:'A'},'A'), 124 | // h('p',{key:'B'},'B'), 125 | // h('p',{key:'C'},'C'), 126 | // h('p',{key:'D'},'D'), 127 | // h('p',{key:'E'},'E'), 128 | // h('p',{key:'F'},'F'), 129 | // h('p',{key:'G'},'G')] 130 | // const nextChildren = [h('p',{key:'A'},'A'), 131 | // h('p',{key:'B'},'B'), 132 | // h('p',{key:'E'},'E'), 133 | // h('p',{key:'C'},'C'), 134 | // h('p',{key:'D'},'D'), 135 | // h('p',{key:'F'},'F'), 136 | // h('p',{key:'G'},'G')] 137 | 138 | // 3.创建新的节点 139 | // a,b(c,e),f,g 140 | // a,b(e,c,d),f,g 141 | // d 节点在老的节点中不存在, 新的里面存在 所以需要创建 142 | 143 | // const prevChildren = [h('p',{key:'A'},'A'), 144 | // h('p',{key:'B'},'B'), 145 | // h('p',{key:'C'},'C'), 146 | // h('p',{key:'E'},'E'), 147 | // h('p',{key:'F'},'F'), 148 | // h('p',{key:'G'},'G')] 149 | // const nextChildren = [h('p',{key:'A'},'A'), 150 | // h('p',{key:'B'},'B'), 151 | // h('p',{key:'E'},'E'), 152 | // h('p',{key:'C'},'C'), 153 | // h('p',{key:'D'},'D'), 154 | // h('p',{key:'F'},'F'), 155 | // h('p',{key:'G'},'G')] 156 | 157 | // 综合的例子验证所有的逻辑点 158 | // a,b,(c,d,e,z),f,g 159 | // a,b,(d,c,y,e),f,g 160 | 161 | // const prevChildren = [h('p',{key:'A'},'A'), 162 | // h('p',{key:'B'},'B'), 163 | // h('p',{key:'C'},'C'), 164 | // h('p',{key:'D'},'D'), 165 | // h('p',{key:'E'},'E'), 166 | // h('p',{key:'Z'},'Z'), 167 | // h('p',{key:'F'},'F'), 168 | // h('p',{key:'G'},'G')] 169 | // const nextChildren = [h('p',{key:'A'},'A'), 170 | // h('p',{key:'B'},'B'), 171 | // h('p',{key:'D'},'D'), 172 | // h('p',{key:'C'},'C'), 173 | // h('p',{key:'Y'},'Y'), 174 | // h('p',{key:'E'},'E'), 175 | // h('p',{key:'F'},'F'), 176 | // h('p',{key:'G'},'G')] 177 | 178 | // fix C节点应该是移动的 而不是删除之后重新创建的 179 | const prevChildren = [ 180 | h('p',{key:'A'},'A'), 181 | h('p',{},'C'), 182 | h('p',{key:'B'},'B'), 183 | h('p',{key:'D'},'D') 184 | ] 185 | const nextChildren = [ 186 | h('p',{key:'A'},'A'), 187 | h('p',{key:'B'},'B'), 188 | h('p',{},'C'), 189 | h('p',{key:'D'},'D') 190 | ] 191 | 192 | 193 | 194 | 195 | 196 | 197 | export default { 198 | name:'ArrayToArray', 199 | setup() { 200 | const isChange = ref(false) 201 | window.isChange = isChange 202 | return { 203 | isChange, 204 | } 205 | }, 206 | render() { 207 | const self = this 208 | return self.isChange === true?h('div',{},nextChildren):h('div',{},prevChildren) 209 | }, 210 | } -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | import {ref,h} from '../../lib/guide-mini-vue.esm.js' 2 | // 新的是文本 3 | // 老的是数组 4 | const nextChildren = 'newChildren' 5 | const prevChildren = [h('div',{},'A'),h('div',{},'B')]; 6 | 7 | export default { 8 | name:'ArrayToText', 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | return { 13 | isChange, 14 | } 15 | }, 16 | render() { 17 | const self = this 18 | return self.isChange === true?h('div',{},nextChildren):h('div',{},prevChildren) 19 | }, 20 | } -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | import {ref,h} from '../../lib/guide-mini-vue.esm.js' 2 | // 新的是文本 3 | // 老的是数组 4 | const nextChildren = 'newChildren' 5 | const prevChildren = [h('div',{},'A'),h('div',{},'B')]; 6 | 7 | export default { 8 | name:'ArrayToText', 9 | setup() { 10 | const isChange = ref(false) 11 | window.isChange = isChange 12 | return { 13 | isChange, 14 | } 15 | }, 16 | render() { 17 | const self = this 18 | return self.isChange === true?h('div',{},prevChildren):h('div',{},nextChildren) 19 | }, 20 | } -------------------------------------------------------------------------------- /example/patchChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 新的是文本 2 | // 老的也是文本 3 | 4 | import {ref,h} from '../../lib/guide-mini-vue.esm.js' 5 | 6 | const prevChildren = 'oldChildren' 7 | const nextChildren = 'newChildren' 8 | 9 | export default { 10 | name:'TextToText', 11 | setup() { 12 | const isChange = ref(false) 13 | window.isChange = isChange 14 | return { 15 | isChange 16 | } 17 | }, 18 | render(){ 19 | const self = this 20 | return self.isChange === true? h('div',{},nextChildren):h('div',{},prevChildren) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createApp} from '../../lib/guide-mini-vue.esm.js' 4 | import App from './App.js' 5 | 6 | const rootContainer = document.querySelector('#root') 7 | createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import {h,ref} from '../../lib/guide-mini-vue.esm.js' 2 | export const App = { 3 | name:"App", 4 | setup(){ 5 | 6 | const count = ref(0) 7 | const onClick = ()=>{ 8 | count.value++ 9 | } 10 | const props = ref({ 11 | foo:'foo', 12 | bar:'bar' 13 | }) 14 | const onChangePropsDemo1 = ()=>{ 15 | props.value.foo = 'new-foo' 16 | } 17 | const onChangePropsDemo2 = ()=>{ 18 | props.value.foo = undefined 19 | } 20 | const onChangePropsDemo3 = ()=>{ 21 | props.value = { 22 | foo:'foo' 23 | } 24 | } 25 | 26 | return { 27 | count, 28 | onClick, 29 | onChangePropsDemo1, 30 | onChangePropsDemo2, 31 | onChangePropsDemo3, 32 | props 33 | } 34 | }, 35 | render(){ 36 | console.log(this.count) 37 | // emit 38 | return h('div',{ 39 | id:'root', 40 | ...this.props 41 | }, 42 | [h("div",{},"count" + this.count), // 依赖收集 43 | h('button',{ 44 | onClick:this.onClick 45 | },'click' 46 | ), 47 | h('button',{onClick:this.onChangePropsDemo1},'changeProps -值改变了-修改'), 48 | h('button',{onClick:this.onChangePropsDemo2},'changeProps -值变成了undefined-删除'), 49 | h('button',{onClick:this.onChangePropsDemo3},'changeProps -值在新的里面没有了-删除'),]) 50 | 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | // Vue3 134 现在来到了我们的组件处理的功能实现 先创建我们的runtime-core文件夹 2 | // example文件夹 index.html main.js App.js 3 | import {createApp} from '../../lib/guide-mini-vue.esm.js' 4 | import {App} from './App.js' 5 | 6 | const rootContainer = document.querySelector('#app') 7 | createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /finite-state-machine/index.js: -------------------------------------------------------------------------------- 1 | // 利用有限状态机来模拟正则表达式 2 | 3 | 4 | // /abc/.test('') 5 | 6 | function test(string){ 7 | let startIndex 8 | let endIndex 9 | let i 10 | let result = [] 11 | 12 | function waitForA(char){ 13 | if(char === 'a'){ 14 | startIndex = i 15 | return waitForB 16 | } 17 | return waitForA 18 | } 19 | 20 | function waitForB(char){ 21 | if(char === 'b'){ 22 | return waitForC 23 | } 24 | i = i-1 25 | return waitForA 26 | } 27 | 28 | function waitForC(char){ 29 | if(char === 'c'){ 30 | endIndex = i 31 | return end 32 | } 33 | return waitForA 34 | } 35 | 36 | function end(){ 37 | return end 38 | } 39 | 40 | let currentState = waitForA; 41 | for ( i = 0; i < string.length; i++) { 42 | let nextState = currentState(string[i]) 43 | currentState = nextState 44 | // 判断是否为结束状态 45 | if(currentState === end){ 46 | console.log(startIndex,endIndex) 47 | result.push({ 48 | start:startIndex, 49 | end:endIndex 50 | }) 51 | console.log(result) 52 | currentState = waitForA 53 | } 54 | } 55 | console.log(result) 56 | } 57 | 58 | console.log(test('ccxaabcrhertgabcppoancabc')) -------------------------------------------------------------------------------- /lib/guide-mini-vue.esm.js: -------------------------------------------------------------------------------- 1 | const extend = Object.assign; 2 | // 87定义并导出isObject函数 3 | const isObject = (val) => { 4 | return val !== null && typeof val === 'object'; 5 | }; 6 | // 108 定义并导出 hasChange函数 7 | const hasChanged = (value, newValue) => { 8 | return !Object.is(value, newValue); 9 | }; 10 | // 将我们的hasOwn函数提取导出 11 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 12 | // 对emit字符串绑定事件的处理函数 13 | const cameLize = (str) => { 14 | return str.replace(/-(\w)/g, (_, c) => { 15 | return c ? c.toUpperCase() : ''; 16 | }); 17 | }; 18 | const capitalize = (str) => { 19 | return str.charAt(0).toUpperCase() + str.slice(1); 20 | }; 21 | const toHandlerKey = (str) => { 22 | return str ? 'on' + capitalize(str) : ""; 23 | }; 24 | 25 | // 32- 全局fn状态变量定义,当fn被执行时将其指向我们的实例对象 26 | let activeEffect; 27 | let shouldTrack; 28 | // 17-根据面向对象思想,我们抽离出一个类来执行 29 | class ReactiveEffect { 30 | // 21-我们可以通过内部的构造函数来替换一个等价的fn 下面调用得也就换成了this._fn 31 | constructor(fn, scheduler) { 32 | this.deps = []; 33 | this.active = true; 34 | this._fn = fn; 35 | this.scheduler = scheduler; 36 | } 37 | // 20-响应下面的函数调用来创造一个run函数 38 | run() { 39 | // 33-将实例对象指向全局变量 40 | activeEffect = this; 41 | // 77 我们的收集依赖其实就是在我们的run函数调用后执行的 因此我们可以在这里进行响应的区分 42 | // 我们可以用active来判断是否是stop状态 43 | if (!this.active) { 44 | // 78 当前状态为stop直接调用我们的fn并返回 45 | return this._fn(); 46 | } 47 | // 79 不是我们的stop状态就控制我们的全局变量的状态 48 | shouldTrack = true; 49 | activeEffect = this; 50 | const result = this._fn(); 51 | // 调用完后重置状态 52 | shouldTrack = false; 53 | // 42- 接下方的函数调用,我们需要将fn的返回值返回出去,将下面的方法调用return 54 | //return this._fn() 55 | return result; // 80 56 | } 57 | ; 58 | // 51-实现stop方法 59 | stop() { 60 | // 53执行dep的清空 后执行我们的单侧 61 | // 54优化 将dep清空提取出来 62 | // 55防止多次调用后重复执行已经清空的循环 我们可以添加一个状态判断 63 | if (this.active) { 64 | cleanupEffect(this); 65 | // 58-stop的回调函数,在其后面执行 66 | if (this.onStop) { 67 | this.onStop(); 68 | } 69 | } 70 | this.active = false; 71 | // this.deps.forEach((dep:any)=>{ 72 | // dep.delete(this) 73 | // }) 74 | } 75 | } 76 | function cleanupEffect(effect) { 77 | effect.deps.forEach((dep) => { 78 | dep.delete(effect); 79 | }); 80 | effect.deps.length = 0; // 小的优化 81 | } 82 | // 26- 根据Map容器来进行键值对存储 这样可以替换下面的dep定义 83 | const targetMap = new Map(); 84 | // 82 activeEffect 和 shouldTrack都要用来判断是否在track转态 以此来命名包装函数 85 | function isTracking() { 86 | return shouldTrack && activeEffect !== undefined; 87 | } 88 | // 24- 定义并导出收集依赖函数 89 | function track(target, key) { 90 | // 81-这两行代码我们依旧可以进行一定的优化 用一个函数来包装 91 | if (!isTracking()) 92 | return; 93 | // if(!activeEffect) return; 94 | // if(!shouldTrack) return 95 | // 27-根据target取到我们的key,dep集合容器 96 | // 29-解决初始化不存在的问题 97 | let depsMap = targetMap.get(target); 98 | if (!depsMap) { 99 | // 没有我们就创建一个并存储 100 | depsMap = new Map(); 101 | targetMap.set(target, depsMap); 102 | } 103 | // 28-取到我们的dep 104 | let dep = depsMap.get(key); 105 | // 30-与上面同理解决初始化的问题 106 | if (!dep) { 107 | dep = new Set(); 108 | depsMap.set(key, dep); 109 | } 110 | // 60 解决activeEffect可能为undefinded的报错 执行yarn test发现测试通过 111 | // if(!activeEffect) return; 80-这段代码可以置顶,避免多余的dep操作,因为这个过程不需要进行依赖收集 112 | // 34-这时候当我们获取到依赖时就可以建立起联系 下面我们就可以去实现trigger了 113 | // 76 对我们的stop优化加一个状态的判断 114 | // if(!shouldTrack) return 80-这段代码可以置顶,避免多余的dep操作,因为这个过程不需要进行依赖收集 115 | // 83 这里面也可以进行一个优化,当我们的dep池中已经有activeEffect这个依赖 可以跳过后续的过程 116 | // if(dep.has(activeEffect)) return 117 | // dep.add(activeEffect) 118 | // 52- 将activeEffect与我们的dep进行反向联系 我们可以在activeEffect中定义一个deps去进行反向收集 119 | // activeEffect.deps.push(dep) 120 | trackEffects(dep); 121 | // 31- 这时候我们可以将fn放到dep当中 我们可以定义一个全局状态变量,来判断fn是否成功加入 122 | // 所谓的收集依赖 就是当我们引入了对象中的每一个key时就需要一个容器将相应的依赖放入其中 123 | // 25-因为我们的依赖是不可重复的,我们可以利用Set来处理 124 | // const dep = new Set() 125 | // 我们的target,key,dep的关系是一一对应的,所谓我们可以一一对应的存储 target=>key=>dep 126 | } 127 | // 101 对于ref用到的track中的逻辑代码进行抽离封装导出 128 | function trackEffects(dep) { 129 | if (dep.has(activeEffect)) 130 | return; 131 | dep.add(activeEffect); 132 | activeEffect.deps.push(dep); 133 | } 134 | // 36- 定义并导出我们的trigger 135 | function trigger(target, key) { 136 | // 37-基于target,key 找到所有的dep并调用其中的fn 137 | let depsMap = targetMap.get(target); 138 | let dep = depsMap.get(key); 139 | triggerEffects(dep); 140 | // 38-循环dep调用即可 141 | // for (const effect of dep) { 142 | // 46-响应数据时也就是执行我们的run 但是当我们第二次执行时需要调用scheduler 而不是调用run函数中的fn函数 143 | // if(effect.scheduler){ 144 | // effect.scheduler() 145 | // }else{ 146 | // effect.run() 147 | // } 148 | // } 149 | // 39-运行yarn test执行所有的单元测试通过 150 | } 151 | // 104 抽离ref中用到的代码进行封装导出 152 | function triggerEffects(dep) { 153 | for (const effect of dep) { 154 | if (effect.scheduler) { 155 | effect.scheduler(); 156 | } 157 | else { 158 | effect.run(); 159 | } 160 | } 161 | } 162 | // 16- 与reactive相同 导出effect 163 | // 45- 接受第二个参数 164 | function effect(fn, options = {}) { 165 | // 因为是触发响应,所以接受的是一个函数fn 并且需要先调用一次 166 | // const scheduler = options.scheduler 167 | // 18 创建上面类的一个实例,并将fn传入进去 168 | const _effect = new ReactiveEffect(fn, options.scheduler); // 47 将 scheduler传入到构造函数中去,并接受他 169 | // 57 将onStop引入到类中 170 | // _effect.onStop = options.onStop 171 | // 59 代码优化 使用extend 这些公共的工具函数我们可以将其抽离到指定文件夹内 src/shared 172 | // Object.assign(_effect,options) 173 | extend(_effect, options); 174 | // 19-我们希望可以通过该实例调用上面类的方法时来间接调用fn函数 175 | _effect.run(); 176 | // 41- 结回effect中的调用 在这里就相当于是调用了实例的方法.我们可以返回该函数 在通过一些处理 177 | const runner = _effect.run.bind(_effect); 178 | runner.effect = _effect; 179 | return runner; 180 | } 181 | 182 | const publicPropertiesMap = { 183 | $el: (i) => i.vnode.el, 184 | // $slots 185 | $slots: (i) => i.slots, 186 | $props: (i) => i.props 187 | }; 188 | const PublicInstanceProxyHandlers = { 189 | get({ _: instance }, key) { 190 | // 先从setupstate中获取值 191 | const { setupState, props } = instance; 192 | // if(key in setupState){ 193 | // return setupState[key] 194 | // } 195 | // 将上面的逻辑进行重构 加上我们的props的逻辑 196 | // const hasOwn = (val,key)=> Object.prototype.hasOwnProperty.call(val,key) 197 | if (hasOwn(setupState, key)) { 198 | return setupState[key]; 199 | } 200 | else if (hasOwn(props, key)) { 201 | return props[key]; 202 | } 203 | // key=>el 204 | const publicGetter = publicPropertiesMap[key]; 205 | if (publicGetter) { 206 | return publicGetter(instance); 207 | } 208 | // if(key === '$el'){ 209 | // return instance.vnode.el 210 | // } 211 | } 212 | }; 213 | 214 | function initProps(instance, rawProps) { 215 | instance.props = rawProps || {}; 216 | // attr 217 | } 218 | 219 | // 65 我们的 createGetter没必要每次都进行调用 可以利用缓存的作用在初始化时调用一次,后续只需要读取缓存 220 | const get = createGetter(); 221 | // set也可以进行同样的处理 222 | const set = createSetter(); 223 | const readonlyGet = createGetter(true); 224 | const shallowReadonlyGet = createGetter(true, true); 225 | function createGetter(isReadonly = false, shallow = false) { 226 | return function get(target, key) { 227 | // 68 判断我们的value是否触发了get 228 | // console.log(key) 下面我们就可以根据这个key来判断是否是我们的reactive 229 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 230 | return !isReadonly; 231 | } 232 | else if (key === "__v_isReadonly" /* IS_READONLY */) { // 进行IS_READONLY的判断 233 | return isReadonly; 234 | } 235 | const res = Reflect.get(target, key); 236 | // 94 如果我们的类型是shallowReadonly就不需要进行下面的逻辑代码,我们可以借鉴readonly的执行,引入一个状态变量 237 | if (shallow) { 238 | return res; 239 | } 240 | // 86 我们可以判断我们的res是否是一个对象 如果是的话可以将子对象进行转换 241 | // isObject这种全局方法我们可以抽离到index中去 242 | if (isObject(res)) { 243 | // 90 当我们的res属于readonly是不应该执行我们的reactive 所以需要在此处进行判断 244 | // 逻辑完成后执行测试 测试通过 245 | return isReadonly ? readonly(res) : reactive(res); 246 | // return reactive(res) 247 | } 248 | if (!isReadonly) { 249 | track(target, key); 250 | } 251 | return res; 252 | }; 253 | } 254 | function createSetter() { 255 | return function set(target, key, value) { 256 | const res = Reflect.set(target, key, value); 257 | trigger(target, key); 258 | return res; 259 | }; 260 | } 261 | const mutibleHandlers = { 262 | // get:createGetter, 263 | get, 264 | // set:createSetter 265 | set 266 | }; 267 | const readonlyHandlers = { 268 | // get:createGetter(true), 269 | get: readonlyGet, 270 | // readonly中的set不需要处理 因为它是只读的 271 | set(target, key, value) { 272 | console.warn(`key:"${String(key)}" set 失败 因为 target 是 readonly`, target); 273 | return true; 274 | } 275 | }; 276 | // 93 shallowReadonlyHandlers 所谓的不需要进行深层次的处理 就要回到我们上面的track也就是收集依赖的进程中去进行逻辑处理 277 | // 我们的shallowReadonlyHandlers应该和我们的readonly很类似 我们可以利用之前的extend工具进行对象整合 278 | // 94 因为整合了我们的readonlyHandlers我们可以吧readonlyHandlers的警告用例也使用上 279 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 280 | get: shallowReadonlyGet 281 | }); 282 | 283 | // import { track,trigger } from "./effect"; 284 | function reactive(raw) { 285 | // 10-recative在本质上就是利用proxy来实现的代理和拦截 286 | // 11-因此我们需要明白什么时候去触发他的Get和Set 287 | // 注意需要在config中设置lib开启DOM和ES6 288 | // return new Proxy(raw,mutibleHandlers 289 | return createReactivepObject(raw, mutibleHandlers); 290 | // target 指向的就是我们的对象,key指向的就是我们访问的属性 比如之前foo 291 | // get(target,key){ 292 | // // 12-使用Reflect将拦截的值映射出去,与PROXY配合使用 293 | // const res = Reflect.get(target,key) 294 | // // 缺少的步骤 未收集初始对象的所有依赖 TODO 依赖收集 295 | // track(target,key) // 23-依赖收集 在effect中进行处理 296 | // return res ; 297 | // } 298 | // get:createGetter(), 299 | // set:createSetter() 300 | // 13-前两个参数与上面的相同,value就是对应的key指向的值 301 | // set(target,key,value){ 302 | // // 14-同样使用Reflect 303 | // const res = Reflect.set(target,key,value) 304 | // // 同样缺少的是触发所有的依赖 TODO 触发依赖 305 | // // 35-trigger实现同样在effect中 306 | // trigger(target,key) 307 | // return res; 308 | // // 15-此时执行单元测试命令 yarn test reactive会发现测试通过 309 | // } 310 | // ) 311 | } 312 | // 61 定义导出readonly 313 | function readonly(raw) { 314 | // 同样也是返回一个Proxy 因为不需要set所以不需要进行依赖收集和触发依赖 315 | return createReactivepObject(raw, readonlyHandlers); 316 | // return new Proxy(raw,readonlyHandlers 317 | // get(target,key){ 318 | // const res = Reflect.get(target,key) 319 | // // track(target,key) 320 | // return res ; 321 | // } 322 | // get:createGetter(true), 323 | // set(target,key,value){ 324 | // const res = Reflect.set(target,key,value) 不需要进行映射 325 | // trigger(target,key) 326 | // return true; 327 | // ) 328 | } 329 | // 92 定义导出shallowReadonly 330 | function shallowReadonly(raw) { 331 | // 回到我们的baseHandlers去创建 332 | return createReactivepObject(raw, shallowReadonlyHandlers); 333 | } 334 | // 63 继续抽离return proxy 335 | function createReactivepObject(target, baseHandlers) { 336 | if (!isObject(target)) { 337 | console.warn(`target${target} 必须是一个对象`); 338 | return target; 339 | } 340 | return new Proxy(target, baseHandlers); 341 | } 342 | 343 | function emit(instance, event, ...args) { 344 | console.log("emit", emit); 345 | // instance.props => event 346 | const { props } = instance; 347 | // add => Add 348 | // add-foo => addFoo 349 | const handlerName = toHandlerKey(cameLize(event)); 350 | // TPP 先去写一个特定的行为 在去重构成通用的行为 351 | const handler = props[handlerName]; 352 | handler && handler(...args); 353 | } 354 | 355 | function initSlots(instance, children) { 356 | // slots 357 | const { vnode } = instance; 358 | // 是SLOT组件才进行相应的处理 359 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) { 360 | normalizeObjectSlots(children, instance.slots); 361 | } 362 | // 判断children的数据类型做逻辑处理 363 | // instance.slots = Array.isArray(children)? children: [children] 364 | // children为对象的处理方式 365 | // const slots = {} 366 | // for (const key in children) { 367 | // const value = children[key] 368 | // // slot 369 | // slots[key] = normalizeSlotValue(value) 370 | // } 371 | // instance.slots = slots 372 | } 373 | function normalizeObjectSlots(children, slots) { 374 | for (const key in children) { 375 | const value = children[key]; 376 | // slot 377 | slots[key] = (props) => normalizeSlotValue(value(props)); 378 | } 379 | } 380 | function normalizeSlotValue(value) { 381 | return Array.isArray(value) ? value : [value]; 382 | } 383 | 384 | // 111 函数抽离 回到ref执行我们的第三个单侧 385 | function trackRefvalue(ref) { 386 | if (isTracking()) { 387 | // trackEffects(this.dep) 这里的this就是我们的ref 388 | trackEffects(ref.dep); 389 | } 390 | } 391 | // 100 定义一个类来执行逻辑 392 | class Refimpl { 393 | constructor(value) { 394 | this.__v_isRef = true; 395 | // this._value = value 396 | // 113 判断我们的value是否是一个对象 如果是对象需要用我们的reactive进行处理 397 | // 注意的是我们在下面set中对比的时候 如果被reactive处理了返回的是一个Proxy对象 398 | // 而我们需要进行对比的是2个普通的对象 399 | // 我们可以吧value在进行判断之前进行一个存储 400 | this._rawValue = value; 401 | // this._value = isObject(value)?reactive(value):value 402 | this._value = convert(value); // 重构函数替换 403 | this.dep = new Set(); 404 | } 405 | // 102 收集依赖完成 406 | get value() { 407 | // 105 在我们进行收集依赖之前需要判断是否被effect处理 408 | // 没处理activeEffect就为undefined 409 | // 110 下面这段代码我们也可以进行抽离 410 | // if(isTracking()){ 411 | // trackEffects(this.dep)} 412 | trackRefvalue(this); 413 | return this._value; 414 | } 415 | // 103 set可以调用之前设置的trigger的逻辑,同样将ref用到的逻辑代码进行抽离 416 | set value(newValue) { 417 | // 106 当我们修改后的值与之前的值相同时不进行依赖的触发 418 | // 107 这段代码我们可以提取到我们的公共函数库中 419 | // if(Object.is(newValue,this._value)) return 420 | // 109 封装后的函数进行替换 421 | // 114 接受上面的处理后 此处进行对比的应该是我们的未被reactive包裹的value值 422 | if (hasChanged(newValue, this._rawValue)) { 423 | // 进到这里表示值发生改变 值发生改变才进行依赖触发 424 | // 104 在触发我们trigger之前一定是我们的value先发生了改变 425 | // 115 同样我们的newValue也需要进行判断处理 接下来我们可以对我们的逻辑代码进行重构 426 | this._rawValue = newValue; 427 | // 下面的三元我们可以进行一个抽离 428 | // this._value = isObject(newValue)?reactive(newValue):newValue 429 | this._value = convert(newValue); // 重构函数替换 430 | triggerEffects(this.dep); 431 | } 432 | } 433 | } 434 | // 116 三元对比的重构 435 | function convert(value) { 436 | return isObject(value) ? reactive(value) : value; 437 | } 438 | // 99 定义并导出ref函数 439 | function ref(value) { 440 | return new Refimpl(value); 441 | } 442 | // 118定义并导出isRef 443 | function isRef(ref) { 444 | // 我们可以在类中创建一个标识 445 | // 119 双重取反排除undefined的报错 逻辑处理完成 446 | return !!ref.__v_isRef; 447 | } 448 | // 121 定义导出我们的unRef 449 | function unRef(ref) { 450 | // 看看是不是ref => ref.value 451 | return isRef(ref) ? ref.value : ref; 452 | } 453 | // 123 定义导出proxyRefs 454 | function proxyRefs(objectWithRefs) { 455 | // 设法让我们得知调用了ref的get和set方法 456 | return new Proxy(objectWithRefs, { 457 | get(target, key) { 458 | // get => age(ref)是ref对象就给他返回value值 459 | // not ref 返回他本身 这个逻辑实际上就是我们的unRef 460 | // 124 get逻辑实现完成 461 | return unRef(Reflect.get(target, key)); 462 | }, 463 | set(target, key, value) { 464 | // 125 判断我们的对象是一个ref类型 并且它的值不是一个ref类型 这种情况才去修改它原先的值 465 | if (isRef(target[key]) && !isRef(value)) { 466 | return target[key].value = value; 467 | } 468 | else { 469 | // 当给定对象的值是一个ref对象时 我们直接让他替换原先的值 470 | return Reflect.set(target, key, value); 471 | } 472 | } 473 | }); 474 | } 475 | 476 | let currentInstance = null; 477 | function creatComponentInstance(vnode, parent) { 478 | console.log("kaobei", parent); 479 | const component = { 480 | vnode, 481 | type: vnode.type, 482 | setupState: {}, 483 | props: {}, 484 | next: null, 485 | slots: {}, 486 | subTree: {}, 487 | provides: parent ? parent.provides : {}, 488 | parent, 489 | isMounted: false, 490 | emit: () => { } 491 | }; 492 | component.emit = emit.bind(null, component); 493 | return component; 494 | } 495 | function setupComponent(instance) { 496 | // TODO 497 | initProps(instance, instance.vnode.props); 498 | initSlots(instance, instance.vnode.children); 499 | instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); 500 | setupStatefulComponent(instance); 501 | } 502 | function setupStatefulComponent(instance) { 503 | const component = instance.vnode.type; 504 | const { setup } = component; 505 | if (setup) { 506 | // currentInstance = instance 507 | setCurrentInstance(instance); 508 | // 我们的setup可以返回一个对象或者是函数 509 | // 当我们返回一个函数时 就可以把它认为是我们的render函数 510 | // 如果返回的是一个对象 会把这个对象注入到我们组件的上下文中 511 | const setupResult = setup(shallowReadonly(instance.props), { emit: instance.emit }); 512 | setCurrentInstance(null); 513 | handleSetupResult(instance, setupResult); 514 | } 515 | } 516 | function handleSetupResult(instance, setupResult) { 517 | // function object 518 | // TODO function 519 | if (typeof setupResult === 'object') { 520 | instance.setupState = proxyRefs(setupResult); 521 | } 522 | finishComponentSetup(instance); 523 | } 524 | function finishComponentSetup(instance) { 525 | // Implement 526 | const Component = instance.type; 527 | // if(Component.render){ 假设render一定有值 528 | instance.render = Component.render; 529 | // } 530 | } 531 | function getCurrentInstance() { 532 | return currentInstance; 533 | } 534 | function setCurrentInstance(instance) { 535 | currentInstance = instance; 536 | } 537 | 538 | const Fragment = Symbol('Fragment'); 539 | const Text = Symbol('Text'); 540 | function createVNode(type, props, children) { 541 | const vnode = { 542 | type, 543 | props, 544 | children, 545 | component: null, 546 | key: props && props.key, 547 | shapeFlag: getShapeFlag(type), 548 | el: null 549 | }; 550 | // children 551 | if (typeof children === 'string') { 552 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 553 | } 554 | else if (Array.isArray(children)) { 555 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 556 | } 557 | // 如何判定给定的参数是一个slot参数 558 | // 必须是一个组件节点 并且它的children必须是一个Object 559 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 560 | if (typeof children === 'object') { 561 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */; 562 | } 563 | } 564 | return vnode; 565 | } 566 | function getShapeFlag(type) { 567 | return typeof type === 'string' ? 1 /* ELEMENT */ : 2 /* STATEFUL_COMPONENT */; 568 | } 569 | function createTextVNode(text) { 570 | return createVNode(Text, {}, text); 571 | } 572 | 573 | // import { render } from "./renderer"; 574 | // render 575 | function createAppAPI(render) { 576 | return function createAPP(rootComponent) { 577 | return { 578 | // 接受一个根容器 579 | mount(rootContainer) { 580 | // 在vue3都会将所有的元素转换成虚拟节点 581 | // 所有的逻辑操作都会基于vnode来执行 582 | const vnode = createVNode(rootComponent); 583 | render(vnode, rootContainer); 584 | } 585 | }; 586 | }; 587 | } 588 | 589 | function shouldUpdateComponent(preVnode, nextVnode) { 590 | const { props: preprops } = preVnode; 591 | const { props: nextprops } = nextVnode; 592 | for (const key in nextprops) { 593 | if (nextprops[key] !== preprops[key]) { 594 | return true; 595 | } 596 | else { 597 | return false; 598 | } 599 | } 600 | } 601 | 602 | const queue = []; 603 | // 引入一个开关 604 | let isFlushPending = false; 605 | let p = Promise.resolve(); 606 | function nextTick(fn) { 607 | return fn ? p.then(fn) : p; 608 | } 609 | function queueJobs(job) { 610 | if (queue.includes(job)) ; 611 | else { 612 | queue.push(job); 613 | } 614 | queueFlush(); 615 | } 616 | function queueFlush() { 617 | if (isFlushPending) 618 | return; 619 | isFlushPending = true; 620 | nextTick(flushJobs); 621 | // Promise.resolve().then(()=>{ 622 | // 这段代码可以利用上面的nextTick来执行 将下面的代码进行抽离 623 | // }) 624 | } 625 | function flushJobs() { 626 | isFlushPending = false; // 重置开关 627 | let job; 628 | while (job = queue.shift()) { 629 | job && job(); 630 | } 631 | } 632 | 633 | function createRenderer(options) { 634 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText } = options; 635 | function render(vnode, container) { 636 | // 构建patch方法 方便后续的递归 637 | patch(null, vnode, container, null, null); 638 | } 639 | // 改为接受 两个虚拟节点 n1 表示之前的虚拟节点 n2表示最新的虚拟节点 640 | function patch(n1, n2, container, parentComponent, anchor) { 641 | const { type, shapeFlag } = n2; 642 | // 增加一种类型只渲染我们的children 643 | // Fragment => 只渲染我们的children 644 | switch (type) { 645 | case Fragment: 646 | processFragment(n1, n2, container, parentComponent, anchor); 647 | break; 648 | case Text: 649 | processText(n1, n2, container); 650 | break; 651 | default: // vnode => flag 我们当前虚拟节点的类型都称之为我们的flag 652 | // 比如我们的字符串就作为元素来对待 653 | // if(typeof vnode.type === 'string'){ 654 | if (shapeFlag & 1 /* ELEMENT */) { 655 | // 上面的判断可以使用位运算符来进行替换 656 | // 当虚拟节点的类型是一个字符串时,就作为一个元素节点 657 | processElement(n1, n2, container, parentComponent, anchor); 658 | // isObject(vnode.type) 同样进行替换 659 | } 660 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) { 661 | processComponent(n1, n2, container, parentComponent, anchor); 662 | } 663 | break; 664 | } 665 | // 去处理组件 666 | // 判断 是不是 element类型 667 | // 是element类型就处理element 668 | // processElement() 669 | // 是component就处理component 670 | // console.log(vnode.type,vnode) 671 | // shapeflags 672 | } 673 | function processText(n1, n2, container) { 674 | const { children } = n2; 675 | const textNode = (n2.el = document.createTextNode(children)); 676 | container.append(textNode); 677 | } 678 | function processFragment(n1, n2, container, parentComponent, anchor) { 679 | // implement 680 | // mountChildren的功能实际上就是遍历了我们的children 并再次进行patch 681 | mountChildren(n2.children, container, parentComponent, anchor); 682 | } 683 | // 作为元素的处理方式 684 | function processElement(n1, n2, container, parentComponent, anchor) { 685 | // console.log('processElement') 686 | if (!n1) { 687 | // element 主要有初始化init和更新update 688 | mountElement(n2, container, parentComponent, anchor); 689 | } 690 | else { 691 | patchElement(n1, n2, container, parentComponent, anchor); 692 | } 693 | } 694 | function patchElement(n1, n2, container, parentComponent, anchor) { 695 | console.log('patchElement'); 696 | console.log('n1', n1); 697 | console.log('n2', n2); 698 | console.log('container', container); 699 | const oldProps = n1.props || {}; 700 | const newProps = n2.props || {}; 701 | const el = (n2.el = n1.el); 702 | patchChildren(n1, n2, el, parentComponent, anchor); 703 | patchProps(el, oldProps, newProps); 704 | } 705 | function patchChildren(n1, n2, container, parentComponent, anchor) { 706 | const prevShapeFlag = n1.shapeFlag; 707 | const { shapeFlag } = n2; 708 | const c1 = n1.children; 709 | const c2 = n2.children; 710 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 711 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) { 712 | // 1.把n1的元素(children)清空 713 | unmountChildren(n1.children); 714 | } 715 | if (c1 !== c2) { 716 | // 2.设置text 717 | hostSetElementText(container, c2); 718 | } 719 | } 720 | else { 721 | if (prevShapeFlag & 4 /* TEXT_CHILDREN */) { 722 | // 清空原来的文本 723 | hostSetElementText(container, ''); 724 | // 直接将children进行mount 725 | mountChildren(c2, container, parentComponent, anchor); 726 | } 727 | else { 728 | // diff array with array 729 | patchKeyedChildren(c1, c2, container, parentComponent, anchor); 730 | } 731 | } 732 | } 733 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) { 734 | const l2 = c2.length; 735 | let i = 0; 736 | let e1 = c1.length - 1; 737 | let e2 = l2 - 1; 738 | function isSomeVNodeType(n1, n2) { 739 | // type 740 | // key 741 | return n1.type === n2.type && n1.key === n2.key; 742 | } 743 | // 左侧 744 | while (i <= e1 && i <= e2) { 745 | const n1 = c1[i]; 746 | const n2 = c2[i]; 747 | if (isSomeVNodeType(n1, n2)) { 748 | patch(n1, n2, container, parentComponent, parentAnchor); 749 | } 750 | else { 751 | break; 752 | } 753 | i++; 754 | } 755 | console.log(i); 756 | // 右侧 757 | while (i <= e1 && i <= e2) { 758 | const n1 = c1[e1]; 759 | const n2 = c2[e2]; 760 | if (isSomeVNodeType(n1, n2)) { 761 | patch(n1, n2, container, parentComponent, parentAnchor); 762 | } 763 | else { 764 | break; 765 | } 766 | e1--; 767 | e2--; 768 | } 769 | // 新的比老的多 需要进行创建 770 | if (i > e1) { 771 | if (i <= e2) { 772 | const nextPos = e2 + 1; 773 | const anchor = e2 + 1 < l2 ? c2[nextPos].el : null; 774 | while (i <= e2) { 775 | patch(null, c2[i], container, parentComponent, anchor); 776 | i++; 777 | } 778 | } 779 | } 780 | else if (i > e2) { // 老的比新的多 需要删除 781 | while (i <= e1) { 782 | hostRemove(c1[i].el); 783 | i++; 784 | } 785 | } 786 | else { 787 | // 中间乱序对比的部分 788 | let s1 = i; // e1 789 | let s2 = i; // e2 790 | const toBePatched = e2 - s2 + 1; // e2中乱序的数量 791 | let patched = 0; // 记录当前处理的数量 792 | const keyToNewIndexMap = new Map(); 793 | const newIndexToOldIndexMap = new Array(toBePatched); 794 | // 判断是否需要进行移动 逻辑优化 795 | let moved = false; 796 | let maxNewIndexSoFar = 0; 797 | // 重置新节点数组的索引值 798 | for (let i = 0; i < toBePatched; i++) { 799 | newIndexToOldIndexMap[i] = 0; 800 | } 801 | for (let i = s2; i <= e2; i++) { 802 | const nextChild = c2[i]; 803 | keyToNewIndexMap.set(nextChild.key, i); 804 | } 805 | for (let i = s1; i <= e1; i++) { 806 | const prevChild = c1[i]; 807 | if (patched >= toBePatched) { 808 | hostRemove(prevChild.el); 809 | continue; 810 | } 811 | // 有key直接找映射表 812 | let newIndex; 813 | if (prevChild.key !== null) { 814 | newIndex = keyToNewIndexMap.get(prevChild.key); 815 | } 816 | else { // 没有key继续遍历 817 | for (let j = s2; j <= e2; j++) { 818 | // 借助已经封装好的方法 819 | if (isSomeVNodeType(prevChild, c2[j])) { 820 | newIndex = j; 821 | break; 822 | } 823 | } 824 | } 825 | // 新值中没有老值,进行删除 826 | if (newIndex === undefined) { 827 | hostRemove(prevChild.el); 828 | } 829 | else { 830 | // 新值大于记录的值 重置最大的值 831 | if (newIndex >= maxNewIndexSoFar) { 832 | maxNewIndexSoFar = newIndex; 833 | } 834 | else { 835 | // 新值小于记录的值说明进行位置的移动 836 | moved = true; 837 | } 838 | // 证明新节点是存在的 在此处将老节点进行遍历对新节点进行重新赋值 839 | // 因为此处我们的索引计算包含了前面的部分所以需要减去前面的部分也就是s2 840 | // 由于新节点可能在老节点中是不存在的 所以需要考虑到为0的情况 可以将我们的i加1处理 841 | newIndexToOldIndexMap[newIndex - s2] = i + 1; 842 | // 存在继续进行深度对比 843 | patch(prevChild, c2[newIndex], container, parentComponent, null); 844 | patched++; 845 | } 846 | } 847 | // 给最长递增子序列算法准备进行处理的数组 848 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; // 需要进行位置的移动时才调用算法,减少不必要的逻辑代码 849 | let j = increasingNewIndexSequence.length - 1; 850 | // 获取到我们的最长递增子序列这是一个数组,需要将我们的老值进行遍历 然后 851 | // 利用两个指针分别指向我们的最长递增子序列和我们的老值 如果老值没有匹配 则说明需要进行位置移动 852 | // toBePatched就是我们的新值的中间乱序的长度 853 | for (let i = toBePatched - 1; i >= 0; i--) { 854 | const nextIndex = i + s2; 855 | const nextChild = c2[nextIndex]; 856 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null; 857 | if (newIndexToOldIndexMap[i] === 0) { 858 | // 在旧值中找不到新值的映射时就需要新创建 859 | patch(null, nextChild, container, parentComponent, anchor); 860 | } 861 | else if (moved) { // 需要移动时才进入相关的逻辑判断 862 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 863 | console.log('需要进行位置移动'); 864 | hostInsert(nextChild.el, container, anchor); 865 | } 866 | else { 867 | // 不需要进行移动的话 将j的指针右移 868 | j--; 869 | } 870 | } 871 | } 872 | } 873 | } 874 | function unmountChildren(children) { 875 | for (let i = 0; i < children.length; i++) { 876 | const el = children[i].el; 877 | // remove 878 | // insert 879 | hostRemove(el); 880 | } 881 | } 882 | function patchProps(el, oldProps, newProps) { 883 | if (oldProps !== newProps) { 884 | for (const key in newProps) { 885 | const prevProp = oldProps[key]; 886 | const nextProp = newProps[key]; 887 | if (prevProp !== nextProp) { 888 | hostPatchProp(el, key, prevProp, nextProp); 889 | } 890 | } 891 | if (Object.keys(oldProps).length > 0) { 892 | for (const key in oldProps) { 893 | if (!(key in newProps)) { 894 | hostPatchProp(el, key, oldProps[key], null); 895 | } 896 | } 897 | } 898 | } 899 | } 900 | function mountElement(vnode, container, parentComponent, anchor) { 901 | // canvas 902 | // new Element() 903 | // 作为元素的处理 基于vnode来创建元素 904 | // 我们所谓的虚拟节点中的内容主要由 type props children 这里的type一般有string和array 905 | // 还是按照我们正常创建元素的方式来创建虚拟DOM 906 | // 这里的el是属于我们的element类型也就是div的,并不是我们认为的初始化的虚拟节点 907 | const el = (vnode.el = hostCreateElement(vnode.type)); 908 | // string array 909 | const { children, shapeFlag } = vnode; 910 | // 字符串类型的处理方式 911 | // children 912 | if (shapeFlag & 4 /* TEXT_CHILDREN */) { 913 | // if(typeof children === 'string'){ 914 | // textchildren 915 | el.textContent = children; 916 | // arraychildren 917 | // Array.isArray(children) 918 | } 919 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) { 920 | // 逻辑抽离 函数封装 921 | mountChildren(vnode.children, el, parentComponent, anchor); 922 | // children.forEach(v=>{ 923 | // patch(v,el) 924 | // }) 925 | } 926 | // props 927 | const { props } = vnode; 928 | for (let key in props) { 929 | const val = props[key]; 930 | // const isOn = key=> /^on[A-Z]/.test(key) 931 | // console.log(key) 932 | // // 如果我们的key是我们的onclick我们就可以给他添加一个点击事件 933 | // if(isOn(key)){ 934 | // el.addEventListener(key.slice(2).toLowerCase(),val) 935 | // }else{ 936 | // el.setAttribute(key,val) 937 | // } 938 | hostPatchProp(el, key, null, val); 939 | } 940 | // canvas 941 | // el.x = 10 942 | // addChild() 943 | // container.append(el) 944 | hostInsert(el, container, anchor); 945 | } 946 | function mountChildren(children, container, parentComponent, anchor) { 947 | children.forEach(v => { 948 | patch(null, v, container, parentComponent, anchor); 949 | }); 950 | } 951 | function processComponent(n1, n2, container, parentComponent, anchor) { 952 | // 当n1没有值时才进行创建 953 | if (!n1) { 954 | mountComponent(n2, container, parentComponent, anchor); 955 | } 956 | else { 957 | // 有值就进行更新逻辑 958 | updateComponent(n1, n2); 959 | } 960 | } 961 | function updateComponent(n1, n2) { 962 | const instance = n2.component = n1.component; 963 | // 判断组件实例是否应该更新 964 | if (shouldUpdateComponent(n1, n2)) { 965 | instance.next = n2; 966 | instance.update(); 967 | } 968 | else { 969 | // 不需要更新时也需要将组件的状态进行更新 970 | n2.el = n1.el; 971 | instance.vnode = n2; 972 | } 973 | } 974 | function mountComponent(initialvnode, container, parentComponent, anchor) { 975 | // throw new Error('Function not implementd') 976 | const instance = initialvnode.component = creatComponentInstance(initialvnode, parentComponent); 977 | setupComponent(instance); 978 | setupRenderEffect(instance, initialvnode, container, anchor); 979 | } 980 | function setupRenderEffect(instance, initialvnode, container, anchor) { 981 | instance.update = effect(() => { 982 | if (!instance.isMounted) { 983 | console.log('init'); 984 | const { proxy } = instance; 985 | const subTree = instance.subTree = instance.render.call(proxy); 986 | // console.log(subTree) 987 | // vndoeTree => patch 988 | // vnode => element =>mountElement 989 | patch(null, subTree, container, instance, anchor); 990 | // 我们这里的subTree就是我们的根节点,我们所要赋值的el可以在subTree上找到 991 | // 传入我们的虚拟节点 992 | initialvnode.el = subTree.el; 993 | instance.isMounted = true; 994 | } 995 | else { 996 | console.log('update'); 997 | // 需要一个更新之后的vnode 998 | const { next, vnode } = instance; 999 | if (next) { 1000 | next.el = vnode.el; 1001 | updateComponentPreRender(instance, next); 1002 | } 1003 | const { proxy } = instance; 1004 | const subTree = instance.render.call(proxy); 1005 | const prevSubTree = instance.subTree; 1006 | instance.subTree = subTree; 1007 | // console.log('current',subTree) 1008 | // console.log('pre',prevSubTree) 1009 | patch(prevSubTree, subTree, container, instance, anchor); 1010 | } 1011 | }, { 1012 | scheduler() { 1013 | console.log('update -- scheduler'); 1014 | queueJobs(instance.update); 1015 | } 1016 | }); 1017 | } 1018 | return { 1019 | createApp: createAppAPI(render) 1020 | }; 1021 | } 1022 | function updateComponentPreRender(instance, nextVnode) { 1023 | instance.vnode = nextVnode; 1024 | instance.next = null; 1025 | instance.props = nextVnode.props; 1026 | } 1027 | // 最长递增子序列算法 1028 | function getSequence(arr) { 1029 | const p = arr.slice(); 1030 | const result = [0]; // 存储长度为i的递增子序列的索引 1031 | let j, u, v, c; 1032 | const len = arr.length; 1033 | for (let i = 0; i < len; i++) { 1034 | const arrI = arr[i]; 1035 | if (arrI !== 0) { 1036 | // 把j赋值为数组最后一项 1037 | j = result[result.length - 1]; 1038 | // result存储的最后一个值小于当前值 1039 | if (arr[j] < arrI) { 1040 | // 存储在result更新前的最后一个索引的值 1041 | p[i] = j; 1042 | result.push(i); 1043 | continue; 1044 | } 1045 | u = 0; 1046 | v = result.length - 1; 1047 | // 二分搜索 查找比arrI小的节点 更新result的值 1048 | while (u < v) { 1049 | c = (u + v) >> 1; 1050 | if (arr[result[c]] < arrI) { 1051 | u = c + 1; 1052 | } 1053 | else { 1054 | v = c; 1055 | } 1056 | } 1057 | if (arrI < arr[result[u]]) { 1058 | if (u > 0) { 1059 | p[i] = result[u - 1]; 1060 | } 1061 | result[u] = i; 1062 | } 1063 | } 1064 | } 1065 | u = result.length; 1066 | v = result[u - 1]; 1067 | // 回溯数组 找到最终的索引 1068 | while (u-- > 0) { 1069 | result[u] = v; 1070 | v = p[v]; 1071 | } 1072 | return result; 1073 | } 1074 | 1075 | function h(type, props, children) { 1076 | return createVNode(type, props, children); 1077 | } 1078 | 1079 | function renderSlots(slots, name, props) { 1080 | const slot = slots[name]; 1081 | if (slot) { 1082 | if (typeof slot === 'function') { 1083 | // children是不可以有 array 1084 | // 只需要把children 渲染出来 1085 | return createVNode(Fragment, {}, slot(props)); 1086 | } 1087 | } 1088 | } 1089 | 1090 | // 存值 1091 | function provide(key, value) { 1092 | // getCurrentInstance必须在setup作用域下才能获取到有效的currentInstance 1093 | const currentInstance = getCurrentInstance(); 1094 | if (currentInstance) { 1095 | let { provides } = currentInstance; 1096 | const parentProvides = currentInstance.parent.provides; 1097 | // init 1098 | if (provides === parentProvides) { 1099 | provides = currentInstance.provides = Object.create(parentProvides); 1100 | } 1101 | provides[key] = value; 1102 | } 1103 | } 1104 | // 取值 1105 | function inject(key, defaultValue) { 1106 | const currentInstance = getCurrentInstance(); 1107 | if (currentInstance) { 1108 | const parentProvides = currentInstance.parent.provides; 1109 | if (key in parentProvides) { 1110 | return parentProvides[key]; 1111 | } 1112 | else if (defaultValue) { 1113 | if (typeof defaultValue === 'function') 1114 | return defaultValue(); 1115 | } 1116 | } 1117 | } 1118 | 1119 | function createElement(type) { 1120 | // console.log('createElement------------') 1121 | return document.createElement(type); 1122 | } 1123 | function patchProp(el, key, prevVal, nextVal) { 1124 | // console.log('patchProp---------------') 1125 | const isOn = key => /^on[A-Z]/.test(key); 1126 | // console.log(key) 1127 | // 如果我们的key是我们的onclick我们就可以给他添加一个点击事件 1128 | if (isOn(key)) { 1129 | el.addEventListener(key.slice(2).toLowerCase(), nextVal); 1130 | } 1131 | else { 1132 | if (nextVal === undefined || nextVal === null) { 1133 | el.removeAttribute(key); 1134 | } 1135 | else { 1136 | el.setAttribute(key, nextVal); 1137 | } 1138 | } 1139 | } 1140 | function insert(child, parent, anchor) { 1141 | // console.log('insert',el,parent) 1142 | // parent.append(el) 1143 | parent.insertBefore(child, anchor || null); 1144 | } 1145 | function remove(children) { 1146 | const parent = children.parentNode; 1147 | if (parent) { 1148 | parent.removeChild(children); 1149 | } 1150 | } 1151 | function setElementText(el, text) { 1152 | el.textContent = text; 1153 | } 1154 | const renderer = createRenderer({ 1155 | createElement, 1156 | patchProp, 1157 | insert, 1158 | remove, 1159 | setElementText 1160 | }); 1161 | function createApp(...args) { 1162 | return renderer.createApp(...args); 1163 | } 1164 | 1165 | export { createApp, createRenderer, createTextVNode, getCurrentInstance, h, inject, nextTick, provide, proxyRefs, ref, renderSlots }; 1166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minivue-code", 3 | "version": "1.0.0", 4 | "main": "lib/guide-mini-vue.cjs.js", 5 | "module": "lib/guide-mini-vue.esm.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "jest", 9 | "build": "rollup -c rollup.config.js" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.17.8", 13 | "@babel/preset-env": "^7.16.11", 14 | "@babel/preset-typescript": "^7.16.7", 15 | "@types/jest": "^27.4.1", 16 | "babel-jest": "^27.5.1", 17 | "jest": "^27.5.1", 18 | "rollup": "^2.70.1", 19 | "tslib": "^2.3.1", 20 | "typescript": "^4.6.3" 21 | }, 22 | "dependencies": { 23 | "@rollup/plugin-typescript": "^8.3.1" 24 | } 25 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript' 2 | import pkg from './package.json' 3 | // rollup 配置信息 天然支持我们的ESM语法 4 | // rollup 不理解我们的ts语法,所以需要先安装对应的模块 5 | export default{ 6 | // 入口 7 | input:"./src/index.ts", 8 | // 出口 9 | output:[ 10 | // 1.cjs => commomjs 11 | // 2. esm 12 | { 13 | format:'cjs', // 标记着要打包成什么样 14 | file:pkg.main // 打包后文件名 15 | }, 16 | // esm的配置 17 | { 18 | format:'es', 19 | file:pkg.module 20 | } 21 | ], 22 | plugins:[ 23 | // 引入即可 24 | typescript() 25 | ] 26 | } -------------------------------------------------------------------------------- /src/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const enum NodeTypes { 4 | INTERPOLATION, 5 | SIMPLE_EXPRESSION, 6 | ELEMENT, 7 | TEXT 8 | } -------------------------------------------------------------------------------- /src/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast" 2 | 3 | export function generate(ast){ 4 | const context = createCodegenContext() 5 | const {push} = context 6 | 7 | genFunctionPreamble(ast,context) 8 | // const VueBinging = "Vue" 9 | // const helpers = ["toDisplayString"] 10 | // const aliasHelper = (s)=>`${s}:_${s}`; 11 | // push(`const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinging}`) 12 | // push('\n') 13 | // push('return ') 14 | // let code = '' 15 | // code += 'return ' 16 | 17 | const functionName = 'render' 18 | const args = ['_ctx','_cache'] 19 | const signature = args.join(', ') 20 | // const node = ast.codegenNode 21 | console.log(ast) 22 | push(`function ${functionName}(${signature}){`) 23 | // code += `function ${functionName}(${signature}){` 24 | push(`return`) 25 | // code += `return` 26 | genNode(ast.codegenNode,context) 27 | // code = genNode(ast,code) 28 | push('}') 29 | // code += '}' 30 | 31 | return {code:context.code} 32 | } 33 | 34 | function genFunctionPreamble(ast,context){ 35 | const {push} = context 36 | const VueBinging = "Vue" 37 | // const helpers = ["toDisplayString"] 38 | const aliasHelper = (s)=>`${s}:_${s}`; 39 | if(ast.helpers.length>0){ 40 | push(`const { ${ast.helpers.map(aliasHelper).join(', ')} } = ${VueBinging}`) 41 | } 42 | push('\n') 43 | push('return ') 44 | } 45 | 46 | function createCodegenContext():any{ 47 | const context = { 48 | code:'', 49 | push(source){ 50 | context.code += source 51 | } 52 | } 53 | return context 54 | } 55 | 56 | function genNode(node:any,context){ 57 | switch (node.type) { 58 | case NodeTypes.TEXT: 59 | genText(node,context) 60 | // Text类型 61 | // const {push} = context 62 | // const node = ast.codegenNode 63 | // push(`'${node.content}'`) 64 | // code += `return '${node.content}'` 65 | // return code 66 | break; 67 | case NodeTypes.INTERPOLATION: 68 | 69 | geninterpolation(node,context) 70 | break; 71 | case NodeTypes.SIMPLE_EXPRESSION: 72 | genExpression(node,context) 73 | default: 74 | break; 75 | } 76 | 77 | } 78 | 79 | function genExpression(node:any,context:any){ 80 | const {push} = context 81 | 82 | push(`_ctx.${node.content}`) 83 | } 84 | 85 | function genText(node:any,context:any){ 86 | const {push} = context 87 | push(`'${node.content}'`) 88 | } 89 | 90 | function geninterpolation(node:any,context:any){ 91 | const {push} = context 92 | push(`_toDisplayString(`) 93 | genNode(node.content,context) 94 | push(")") 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/compiler-core/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast" 2 | 3 | const enum TagType{ 4 | Start, 5 | End 6 | } 7 | export function baseParse(content:string){ 8 | // 创建上下文对象 9 | const context = createParseContext(content) 10 | return createRoot(parseChildren(context,[])) 11 | } 12 | 13 | function parseChildren(context,ancestors){ 14 | const nodes:any = [] 15 | while (!isEnd(context,ancestors)) { 16 | 17 | let node 18 | // 检测字符串是否以某某开头 19 | const s = context.source 20 | if(s.startsWith('{{')){ 21 | node = parseInterpolation(context) 22 | // 首位是左尖括号 23 | }else if(s[0] === '<'){ 24 | // 第二位是字母 25 | if(/[a-z]/i.test(s[1])){ 26 | console.log('element') 27 | node = parseElement(context,ancestors) 28 | } 29 | } 30 | // text的处理 31 | if(!node){ 32 | node = parseText(context) 33 | } 34 | nodes.push(node) 35 | } 36 | return nodes 37 | } 38 | // 循环执行解析children的状态函数 39 | function isEnd(context,ancestors){ 40 | const s = context.source 41 | // 遇到结束标签 42 | if(s.startsWith('= 0; i--) { 44 | const tag = ancestors[i].tag 45 | if(startsWithEndTagOpen(s,tag)){ 46 | return true 47 | } 48 | } 49 | } 50 | // if(ancestors && s.startsWith(``)){ 51 | // return true 52 | // } 53 | // 当source没有值 54 | return !s 55 | } 56 | 57 | function parseText(context:any){ 58 | // 对我们children中的text进行逻辑拓展 59 | let endIndex = context.source.length // 默认值 60 | const endToken = ['<','{{'] 61 | for (let i = 0; i < endToken.length; i++) { 62 | const index = context.source.indexOf(endToken[i]) 63 | // 存在花括弧 需要停止 64 | if(index !== -1 && endIndex > index){ 65 | endIndex = index 66 | } 67 | } 68 | 69 | // 主要步骤 1 获取内容content 70 | // const content = context.source.slice(0,context.source.length) 71 | const content = parseTextData(context,endIndex) 72 | 73 | // 2.推进编译进程 74 | // advanceBy(content,content.length) 75 | console.log(context.source) 76 | 77 | return { 78 | type:NodeTypes.TEXT, 79 | content:content 80 | } 81 | } 82 | // 提取裁剪方法 83 | function parseTextData(context:any,length){ 84 | const content = context.source.slice(0,length) 85 | advanceBy(context,content.length) 86 | return content 87 | } 88 | 89 | function parseElement(context:any,ancestors){ 90 | // 解析tag 91 | // 删除处理完成的代码 92 | const element:any = parseTag(context,TagType.Start) 93 | ancestors.push(element) 94 | // 联合类型进行递归调用 95 | element.children = parseChildren(context,ancestors) 96 | ancestors.pop() 97 | 98 | // 处理没有结束标签的逻辑 判断前后标签内容是否一致 99 | if(startsWithEndTagOpen(context.source,element.tag)){ 100 | parseTag(context,TagType.End) 101 | }else{ 102 | throw new Error(`缺少结束标签:${element.tag}`) 103 | } 104 | console.log('------------',context.source) 105 | 106 | return element 107 | } 108 | 109 | function startsWithEndTagOpen(source,tag){ 110 | return source.startsWith('{ 7 | 8 | 9 | // 快照测试 对我们当前的code进行一次快照 当我们代码或逻辑改动 前后两次的快照不一样就会报错 10 | // 1.抓BUG 11 | // 2.有意更新 12 | it('string',()=>{ 13 | 14 | const ast = baseParse('hi') 15 | transform(ast) 16 | const {code} = generate(ast) 17 | expect(code).toMatchSnapshot() 18 | }) 19 | 20 | // 插值解析 21 | it('interpolation',()=>{ 22 | 23 | const ast = baseParse('{{message}}') 24 | transform(ast) 25 | const {code} = generate(ast) 26 | // 快照测试 对我们当前的code进行一次快照 当我们代码或逻辑改动 前后两次的快照不一样就会报错 27 | // 1.抓BUG 28 | // 2.有意更新 29 | expect(code).toMatchSnapshot() 30 | }) 31 | }) -------------------------------------------------------------------------------- /src/compiler-core/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from '../src/ast' 2 | import {baseParse} from '../src/parse' 3 | describe('Parse',()=>{ 4 | 5 | describe('interpolation',()=>{ 6 | 7 | const ast = baseParse("{{message}}") 8 | 9 | // root 10 | expect(ast.children[0]).toStrictEqual({ 11 | type:NodeTypes.INTERPOLATION, 12 | content:{ 13 | type:NodeTypes.SIMPLE_EXPRESSION, 14 | content:'message' 15 | } 16 | }) 17 | }) 18 | }) 19 | describe('element',()=>{ 20 | 21 | describe('simple element div',()=>{ 22 | 23 | const ast = baseParse("
") 24 | 25 | // root 26 | expect(ast.children[0]).toStrictEqual({ 27 | type:NodeTypes.ELEMENT, 28 | tag:'div', 29 | children:[] 30 | }) 31 | 32 | 33 | 34 | }) 35 | 36 | }) 37 | 38 | describe('text',()=>{ 39 | 40 | it('simple text',()=>{ 41 | 42 | const ast = baseParse("some text") 43 | 44 | // root 45 | expect(ast.children[0]).toStrictEqual({ 46 | type:NodeTypes.TEXT , 47 | content:'some text' 48 | }) 49 | 50 | 51 | 52 | }) 53 | 54 | }) 55 | 56 | // 三种联合类型的单侧 57 | test('hello word',()=>{ 58 | 59 | const ast = baseParse('
hi,{{message}}
') 60 | 61 | expect(ast.children[0]).toStrictEqual({ 62 | type:NodeTypes.ELEMENT, 63 | tag:'div', 64 | children:[ 65 | { 66 | type:NodeTypes.TEXT , 67 | content:'hi,'}, 68 | { 69 | type:NodeTypes.INTERPOLATION, 70 | content:{ 71 | type:NodeTypes.SIMPLE_EXPRESSION, 72 | content:'message' 73 | } 74 | } 75 | ] 76 | 77 | 78 | }) 79 | 80 | 81 | }) 82 | 83 | // 嵌套元素的单侧 84 | test('Nested element',()=>{ 85 | const ast = baseParse('

hi

{{message}}
') 86 | 87 | expect(ast.children[0]).toStrictEqual({ 88 | type:NodeTypes.ELEMENT, 89 | tag:'div', 90 | children:[ 91 | { 92 | type:NodeTypes.ELEMENT, 93 | tag:'p', 94 | children:[{type:NodeTypes.TEXT , 95 | content:'hi' 96 | }] 97 | }, 98 | { 99 | type:NodeTypes.INTERPOLATION, 100 | content:{ 101 | type:NodeTypes.SIMPLE_EXPRESSION, 102 | content:'message' 103 | } 104 | } 105 | ] 106 | }) 107 | }) 108 | 109 | // 缺少结束标签希望会报错 110 | test('should throw error when lack end tag',()=>{ 111 | expect(()=>{ 112 | baseParse('
') 113 | }).toThrow(`缺少结束标签:span`) 114 | }) 115 | 116 | -------------------------------------------------------------------------------- /src/compiler-core/tests/transform.spec.ts: -------------------------------------------------------------------------------- 1 | import { transform } from "../src/transform" 2 | import { baseParse } from "../src/parse" 3 | import { NodeTypes } from "../src/ast" 4 | 5 | 6 | describe('transform',()=>{ 7 | 8 | it('happy path',()=>{ 9 | const ast = baseParse('
hi,{{message}}
') 10 | // 插件体系的思想 11 | const plugin = (node)=>{ 12 | if(node.type === NodeTypes.TEXT){ 13 | node.content = node.content + ' mini-vue' 14 | } 15 | } 16 | // 传入options 17 | transform(ast,{ 18 | nodeTransforms:[plugin] 19 | }) 20 | 21 | const nodeText = ast.children[0].children[0] 22 | expect(nodeText.content).toBe('hi, mini-vue') 23 | 24 | }) 25 | }) -------------------------------------------------------------------------------- /src/drawio/parse.drawio.svg: -------------------------------------------------------------------------------- 1 |
parse原理-有限状态机
初始转态
插值
{{ 
end
}}
<div>hi,{{mesage}}</div>
processElement
a-z
<
parseTag开始
parseChildren
parseTag结束
Text
非以上
递归调用
root
element
Text
插值
-------------------------------------------------------------------------------- /src/drawio/reg.drawio.svg: -------------------------------------------------------------------------------- 1 |
有限状态机(finite state machine)

读取一组输入然后根据这些输入来更改为不同的状态
a
b
c
String
abc
end
acc
状态错误
返回a等待下次输入
用我们的有限状态机去解读正则表达式的匹配过程
-------------------------------------------------------------------------------- /src/drawio/transform.drawio.svg: -------------------------------------------------------------------------------- 1 |
<div>hi,{{message}}</div>
root
element
Text
插值
content
hi, +mini-vue
树的遍历-深度优先搜索
实现插件体系,将程序的动态逻辑抽离到外部执行
内部只需要将传入的option进行调用
-------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // 整个mini-vue的出口 *表示将内部所有的东西都进行导出 2 | export * from './runtime-dom' 3 | export * from './reactivity' -------------------------------------------------------------------------------- /src/reactivity/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { isObject,extend } from '../shared' 2 | // 88-引入完成后单侧执行通过 接下来我们可以去实现与reactive类似的readonly功能 回到我们的测试用例 3 | import {track,trigger} from './effect' 4 | import { reactive, ReactiveFlags, readonly, shallowReadonly } from './reactive' 5 | 6 | // 65 我们的 createGetter没必要每次都进行调用 可以利用缓存的作用在初始化时调用一次,后续只需要读取缓存 7 | const get = createGetter() 8 | // set也可以进行同样的处理 9 | const set = createSetter() 10 | const readonlyGet = createGetter(true) 11 | const shallowReadonlyGet = createGetter(true,true) 12 | 13 | function createGetter(isReadonly = false,shallow = false){ 14 | return function get(target,key){ 15 | // 68 判断我们的value是否触发了get 16 | // console.log(key) 下面我们就可以根据这个key来判断是否是我们的reactive 17 | if(key === ReactiveFlags.IS_REACTIVE){ 18 | return !isReadonly 19 | }else if(key === ReactiveFlags.IS_READONLY){ // 进行IS_READONLY的判断 20 | return isReadonly 21 | } 22 | 23 | const res = Reflect.get(target,key) 24 | 25 | // 94 如果我们的类型是shallowReadonly就不需要进行下面的逻辑代码,我们可以借鉴readonly的执行,引入一个状态变量 26 | if(shallow){ 27 | return res 28 | } 29 | 30 | 31 | // 86 我们可以判断我们的res是否是一个对象 如果是的话可以将子对象进行转换 32 | // isObject这种全局方法我们可以抽离到index中去 33 | if(isObject(res)){ 34 | // 90 当我们的res属于readonly是不应该执行我们的reactive 所以需要在此处进行判断 35 | // 逻辑完成后执行测试 测试通过 36 | return isReadonly? readonly(res):reactive(res) 37 | // return reactive(res) 38 | } 39 | 40 | if(!isReadonly){ 41 | track(target,key) 42 | } 43 | return res 44 | } 45 | } 46 | function createSetter(){ 47 | return function set(target,key,value){ 48 | const res =Reflect.set(target,key,value) 49 | trigger(target,key) 50 | return res 51 | } 52 | }; 53 | 54 | export const mutibleHandlers = { 55 | // get:createGetter, 56 | get, 57 | // set:createSetter 58 | set 59 | } 60 | export const readonlyHandlers = { 61 | // get:createGetter(true), 62 | get:readonlyGet, 63 | // readonly中的set不需要处理 因为它是只读的 64 | set(target,key,value){ 65 | console.warn(`key:"${String(key)}" set 失败 因为 target 是 readonly`,target) 66 | return true; 67 | }} 68 | 69 | // 93 shallowReadonlyHandlers 所谓的不需要进行深层次的处理 就要回到我们上面的track也就是收集依赖的进程中去进行逻辑处理 70 | // 我们的shallowReadonlyHandlers应该和我们的readonly很类似 我们可以利用之前的extend工具进行对象整合 71 | // 94 因为整合了我们的readonlyHandlers我们可以吧readonlyHandlers的警告用例也使用上 72 | export const shallowReadonlyHandlers = extend({},readonlyHandlers, { 73 | get:shallowReadonlyGet 74 | }) -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect" 2 | // 引入 ReactiveEffect 3 | class ComputedRefImp{ 4 | private _dirty: boolean = true 5 | private _getter: any 6 | private _value:any 7 | private _effect:any 8 | constructor(getter){ 9 | this._getter = getter 10 | // 133利用ReactiveEffect中触发的schduler避免去执行我们的run 11 | this._effect = new ReactiveEffect(getter,()=>{ 12 | if(!this._dirty){ 13 | this._dirty = true 14 | } 15 | }) 16 | } 17 | //129 触发我们的get方法将computed包裹的方法进行返回 18 | get value(){ 19 | // 131 当我们调用完一次get后 将这个状态锁住 不再调用我们的方法 只把相应的值给返回出去 20 | 21 | // 132 当我们依赖的响应式对象的值发生改变时应该去改变我们的dirty 22 | // 去追踪我们的响应式对象的值发生变化时,我们可以利用effect中的ReactiveEffect 23 | 24 | 25 | if(this._dirty){ 26 | // 初次调用才会进来 27 | this._dirty = false 28 | return this._value = this._effect() 29 | // 这里的调用可以用我们的effect来替换 30 | } 31 | return this._value 32 | } 33 | } 34 | // 127 定义导出computed函数 35 | export function computed(getter){ 36 | // 128 我们可以返回一个类的实例来实现,因为类中可以触发相应的方法 37 | return new ComputedRefImp(getter) 38 | } -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "../shared"; 2 | // 32- 全局fn状态变量定义,当fn被执行时将其指向我们的实例对象 3 | let activeEffect; 4 | let shouldTrack; 5 | 6 | // 17-根据面向对象思想,我们抽离出一个类来执行 7 | export class ReactiveEffect{ 8 | private _fn:any; 9 | public scheduler:Function | undefined 10 | deps = []; 11 | active = true; 12 | onStop?: ()=> void; // 可有可无 13 | // 21-我们可以通过内部的构造函数来替换一个等价的fn 下面调用得也就换成了this._fn 14 | constructor(fn,scheduler?:Function){ // 加上public使其能够被外界获取到 15 | this._fn = fn 16 | this.scheduler = scheduler 17 | } 18 | 19 | // 20-响应下面的函数调用来创造一个run函数 20 | run(){ 21 | // 33-将实例对象指向全局变量 22 | activeEffect = this; 23 | 24 | // 77 我们的收集依赖其实就是在我们的run函数调用后执行的 因此我们可以在这里进行响应的区分 25 | // 我们可以用active来判断是否是stop状态 26 | if(!this.active){ 27 | // 78 当前状态为stop直接调用我们的fn并返回 28 | return this._fn() 29 | } 30 | // 79 不是我们的stop状态就控制我们的全局变量的状态 31 | shouldTrack = true 32 | activeEffect = this 33 | const result = this._fn() 34 | // 调用完后重置状态 35 | shouldTrack =false 36 | // 42- 接下方的函数调用,我们需要将fn的返回值返回出去,将下面的方法调用return 37 | //return this._fn() 38 | return result // 80 39 | }; 40 | // 51-实现stop方法 41 | stop(){ 42 | // 53执行dep的清空 后执行我们的单侧 43 | // 54优化 将dep清空提取出来 44 | // 55防止多次调用后重复执行已经清空的循环 我们可以添加一个状态判断 45 | if(this.active){ 46 | cleanupEffect(this) 47 | // 58-stop的回调函数,在其后面执行 48 | if(this.onStop){ 49 | this.onStop(); 50 | } 51 | } 52 | this.active = false 53 | // this.deps.forEach((dep:any)=>{ 54 | // dep.delete(this) 55 | // }) 56 | } 57 | } 58 | function cleanupEffect(effect){ 59 | effect.deps.forEach((dep:any)=>{ 60 | dep.delete(effect) 61 | }) 62 | effect.deps.length = 0 // 小的优化 63 | } 64 | // 26- 根据Map容器来进行键值对存储 这样可以替换下面的dep定义 65 | const targetMap = new Map() 66 | 67 | // 82 activeEffect 和 shouldTrack都要用来判断是否在track转态 以此来命名包装函数 68 | export function isTracking(){ 69 | return shouldTrack && activeEffect !== undefined 70 | } 71 | 72 | // 24- 定义并导出收集依赖函数 73 | export function track(target,key){ 74 | // 81-这两行代码我们依旧可以进行一定的优化 用一个函数来包装 75 | if(!isTracking()) return 76 | // if(!activeEffect) return; 77 | // if(!shouldTrack) return 78 | 79 | // 27-根据target取到我们的key,dep集合容器 80 | 81 | // 29-解决初始化不存在的问题 82 | let depsMap = targetMap.get(target); 83 | if(!depsMap){ 84 | // 没有我们就创建一个并存储 85 | depsMap = new Map() 86 | targetMap.set(target,depsMap); 87 | } 88 | // 28-取到我们的dep 89 | let dep = depsMap.get(key) 90 | // 30-与上面同理解决初始化的问题 91 | if(!dep){ 92 | dep = new Set() 93 | depsMap.set(key,dep) 94 | } 95 | // 60 解决activeEffect可能为undefinded的报错 执行yarn test发现测试通过 96 | 97 | // if(!activeEffect) return; 80-这段代码可以置顶,避免多余的dep操作,因为这个过程不需要进行依赖收集 98 | 99 | // 34-这时候当我们获取到依赖时就可以建立起联系 下面我们就可以去实现trigger了 100 | 101 | // 76 对我们的stop优化加一个状态的判断 102 | 103 | // if(!shouldTrack) return 80-这段代码可以置顶,避免多余的dep操作,因为这个过程不需要进行依赖收集 104 | 105 | // 83 这里面也可以进行一个优化,当我们的dep池中已经有activeEffect这个依赖 可以跳过后续的过程 106 | 107 | // if(dep.has(activeEffect)) return 108 | // dep.add(activeEffect) 109 | // 52- 将activeEffect与我们的dep进行反向联系 我们可以在activeEffect中定义一个deps去进行反向收集 110 | // activeEffect.deps.push(dep) 111 | trackEffects(dep) 112 | // 31- 这时候我们可以将fn放到dep当中 我们可以定义一个全局状态变量,来判断fn是否成功加入 113 | // 所谓的收集依赖 就是当我们引入了对象中的每一个key时就需要一个容器将相应的依赖放入其中 114 | // 25-因为我们的依赖是不可重复的,我们可以利用Set来处理 115 | // const dep = new Set() 116 | // 我们的target,key,dep的关系是一一对应的,所谓我们可以一一对应的存储 target=>key=>dep 117 | }; 118 | // 101 对于ref用到的track中的逻辑代码进行抽离封装导出 119 | export function trackEffects(dep){ 120 | if(dep.has(activeEffect)) return 121 | dep.add(activeEffect) 122 | activeEffect.deps.push(dep) 123 | 124 | } 125 | 126 | // 36- 定义并导出我们的trigger 127 | export function trigger(target,key){ 128 | // 37-基于target,key 找到所有的dep并调用其中的fn 129 | 130 | let depsMap = targetMap.get(target) 131 | let dep = depsMap.get(key) 132 | 133 | triggerEffects(dep) 134 | // 38-循环dep调用即可 135 | // for (const effect of dep) { 136 | // 46-响应数据时也就是执行我们的run 但是当我们第二次执行时需要调用scheduler 而不是调用run函数中的fn函数 137 | // if(effect.scheduler){ 138 | // effect.scheduler() 139 | // }else{ 140 | // effect.run() 141 | // } 142 | // } 143 | // 39-运行yarn test执行所有的单元测试通过 144 | }; 145 | // 104 抽离ref中用到的代码进行封装导出 146 | export function triggerEffects(dep){ 147 | for (const effect of dep) { 148 | if(effect.scheduler){ 149 | effect.scheduler() 150 | }else{ 151 | effect.run() 152 | } 153 | } 154 | } 155 | 156 | // 16- 与reactive相同 导出effect 157 | 158 | // 45- 接受第二个参数 159 | export function effect(fn,options:any = {}){ 160 | // 因为是触发响应,所以接受的是一个函数fn 并且需要先调用一次 161 | // const scheduler = options.scheduler 162 | // 18 创建上面类的一个实例,并将fn传入进去 163 | const _effect = new ReactiveEffect(fn,options.scheduler) // 47 将 scheduler传入到构造函数中去,并接受他 164 | // 57 将onStop引入到类中 165 | // _effect.onStop = options.onStop 166 | // 59 代码优化 使用extend 这些公共的工具函数我们可以将其抽离到指定文件夹内 src/shared 167 | // Object.assign(_effect,options) 168 | extend(_effect,options) 169 | // 19-我们希望可以通过该实例调用上面类的方法时来间接调用fn函数 170 | _effect.run(); 171 | 172 | 173 | // 41- 结回effect中的调用 在这里就相当于是调用了实例的方法.我们可以返回该函数 在通过一些处理 174 | 175 | const runner:any = _effect.run.bind(_effect) 176 | runner.effect=_effect 177 | return runner 178 | } 179 | 180 | // 49-实现stop方法使其在调用后不在执行effect的方法,除非手动再次执行 181 | export function stop(runner){ 182 | // 50-为了让我们的实例去执行我们的stop函数,需要在上面的effect函数中产生联系,并返回 下面我们就回到类中去实现这个方法 183 | runner.effect.stop() 184 | } -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export {ref,proxyRefs} from './ref' -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | // import { track,trigger } from "./effect"; 2 | import { isObject } from "../shared"; 3 | import { mutibleHandlers,readonlyHandlers,shallowReadonlyHandlers } from "./baseHandlers"; 4 | // 9-定义并导出实现的函数 5 | // 62代码重构优化 6 | // function createGetter(isReadonly = false){ 7 | // return function get(target,key){. 8 | // const res = Reflect.get(target,key) 9 | // if(!isReadonly){ 10 | // track(target,key) 11 | // } 12 | // } 13 | // } 14 | // function createSetter(){ 15 | // return function set(target,key,value){ 16 | // const res =Reflect.set(target,key,value) 17 | // trigger(target,key) 18 | // return res 19 | // } 20 | // } 21 | 22 | // 68 将我们的value调用抽离出来 使用一个枚举 23 | export const enum ReactiveFlags{ 24 | IS_REACTIVE = '__v_isReactive', 25 | IS_READONLY = '__v_isReadonly' 26 | } 27 | 28 | export function reactive(raw){ 29 | // 10-recative在本质上就是利用proxy来实现的代理和拦截 30 | // 11-因此我们需要明白什么时候去触发他的Get和Set 31 | // 注意需要在config中设置lib开启DOM和ES6 32 | 33 | 34 | // return new Proxy(raw,mutibleHandlers 35 | return createReactivepObject(raw,mutibleHandlers) 36 | // target 指向的就是我们的对象,key指向的就是我们访问的属性 比如之前foo 37 | // get(target,key){ 38 | // // 12-使用Reflect将拦截的值映射出去,与PROXY配合使用 39 | // const res = Reflect.get(target,key) 40 | 41 | // // 缺少的步骤 未收集初始对象的所有依赖 TODO 依赖收集 42 | // track(target,key) // 23-依赖收集 在effect中进行处理 43 | // return res ; 44 | // } 45 | 46 | // get:createGetter(), 47 | // set:createSetter() 48 | 49 | // 13-前两个参数与上面的相同,value就是对应的key指向的值 50 | // set(target,key,value){ 51 | // // 14-同样使用Reflect 52 | // const res = Reflect.set(target,key,value) 53 | 54 | // // 同样缺少的是触发所有的依赖 TODO 触发依赖 55 | // // 35-trigger实现同样在effect中 56 | // trigger(target,key) 57 | // return res; 58 | // // 15-此时执行单元测试命令 yarn test reactive会发现测试通过 59 | // } 60 | 61 | // ) 62 | } 63 | 64 | // 61 定义导出readonly 65 | export function readonly(raw){ 66 | // 同样也是返回一个Proxy 因为不需要set所以不需要进行依赖收集和触发依赖 67 | return createReactivepObject(raw,readonlyHandlers) 68 | // return new Proxy(raw,readonlyHandlers 69 | // get(target,key){ 70 | // const res = Reflect.get(target,key) 71 | 72 | // // track(target,key) 73 | // return res ; 74 | // } 75 | // get:createGetter(true), 76 | // set(target,key,value){ 77 | // const res = Reflect.set(target,key,value) 不需要进行映射 78 | // trigger(target,key) 79 | // return true; 80 | 81 | // ) 82 | } 83 | 84 | // 92 定义导出shallowReadonly 85 | export function shallowReadonly(raw){ 86 | // 回到我们的baseHandlers去创建 87 | return createReactivepObject(raw,shallowReadonlyHandlers) 88 | } 89 | 90 | // 67 定义isReactive的出口 91 | export function isReactive(value){ 92 | // 分析 我们在creatGetter中传入了一个readonly变量已经帮助我们来区分我们的get操作的是一个什么类型 93 | // 当我们的value进行调用时其实就会触发我们的get 94 | 95 | // return value['is_reactive'] 96 | // 70 当我们的vaklue中不是一个proxy,没有挂载reactive类时,会结算为undefined,此时将他进行一个布尔值运算即可 97 | return !!value[ReactiveFlags.IS_REACTIVE] 98 | } 99 | 100 | // 72 定以isReadonly出口 101 | export function isReadonly(value){ 102 | // 73同样在ReactiveFlags中进行挂载 然后在我们的creatGetter中进行判断 103 | return !!value[ReactiveFlags.IS_READONLY] 104 | } 105 | // 97-定义并导出是否为Proxy函数逻辑 106 | export function isProxy(value){ 107 | // 逻辑代码只需要判断是否为响应式对象即可 108 | return isReactive(value) || isReadonly(value) 109 | } 110 | // 63 继续抽离return proxy 111 | function createReactivepObject(target,baseHandlers){ 112 | if(!isObject(target)){ 113 | console.warn( `target${target} 必须是一个对象`) 114 | return target 115 | } 116 | return new Proxy(target,baseHandlers) 117 | } 118 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChanged, isObject } from "../shared"; 2 | import { isTracking, trackEffects,triggerEffects } from "./effect"; 3 | import { reactive } from "./reactive"; 4 | // 111 函数抽离 回到ref执行我们的第三个单侧 5 | function trackRefvalue(ref){ 6 | if(isTracking()){ 7 | // trackEffects(this.dep) 这里的this就是我们的ref 8 | trackEffects(ref.dep) 9 | } 10 | } 11 | // 100 定义一个类来执行逻辑 12 | class Refimpl { 13 | // 当我们对ref进行依赖收集时 其实可以直接调用我们的track函数,但是我们的ref对应的值只有一个value 14 | // 所以我们需要对track函数中用到的逻辑进行一个抽离.我们也可以在类中去创建一个dep 15 | public dep; 16 | private _value: any; 17 | private _rawValue: any; 18 | public __v_isRef = true; 19 | constructor(value) { 20 | // this._value = value 21 | // 113 判断我们的value是否是一个对象 如果是对象需要用我们的reactive进行处理 22 | // 注意的是我们在下面set中对比的时候 如果被reactive处理了返回的是一个Proxy对象 23 | // 而我们需要进行对比的是2个普通的对象 24 | // 我们可以吧value在进行判断之前进行一个存储 25 | this._rawValue = value 26 | // this._value = isObject(value)?reactive(value):value 27 | this._value = convert(value) // 重构函数替换 28 | this.dep = new Set() 29 | } 30 | // 102 收集依赖完成 31 | get value(){ 32 | // 105 在我们进行收集依赖之前需要判断是否被effect处理 33 | // 没处理activeEffect就为undefined 34 | 35 | // 110 下面这段代码我们也可以进行抽离 36 | // if(isTracking()){ 37 | // trackEffects(this.dep)} 38 | trackRefvalue(this) 39 | return this._value 40 | } 41 | // 103 set可以调用之前设置的trigger的逻辑,同样将ref用到的逻辑代码进行抽离 42 | set value(newValue){ 43 | // 106 当我们修改后的值与之前的值相同时不进行依赖的触发 44 | // 107 这段代码我们可以提取到我们的公共函数库中 45 | // if(Object.is(newValue,this._value)) return 46 | // 109 封装后的函数进行替换 47 | // 114 接受上面的处理后 此处进行对比的应该是我们的未被reactive包裹的value值 48 | if(hasChanged(newValue,this._rawValue)){ 49 | // 进到这里表示值发生改变 值发生改变才进行依赖触发 50 | // 104 在触发我们trigger之前一定是我们的value先发生了改变 51 | // 115 同样我们的newValue也需要进行判断处理 接下来我们可以对我们的逻辑代码进行重构 52 | this._rawValue = newValue 53 | // 下面的三元我们可以进行一个抽离 54 | // this._value = isObject(newValue)?reactive(newValue):newValue 55 | this._value =convert(newValue) // 重构函数替换 56 | triggerEffects(this.dep) 57 | } 58 | } 59 | } 60 | // 116 三元对比的重构 61 | function convert(value){ 62 | return isObject(value)?reactive(value):value 63 | } 64 | 65 | // 99 定义并导出ref函数 66 | export function ref(value){ 67 | return new Refimpl(value) 68 | } 69 | 70 | // 118定义并导出isRef 71 | export function isRef(ref){ 72 | // 我们可以在类中创建一个标识 73 | // 119 双重取反排除undefined的报错 逻辑处理完成 74 | return !!ref.__v_isRef 75 | } 76 | // 121 定义导出我们的unRef 77 | export function unRef(ref){ 78 | // 看看是不是ref => ref.value 79 | return isRef(ref)?ref.value:ref; 80 | } 81 | 82 | // 123 定义导出proxyRefs 83 | export function proxyRefs(objectWithRefs){ 84 | // 设法让我们得知调用了ref的get和set方法 85 | return new Proxy(objectWithRefs,{ 86 | get(target,key){ 87 | // get => age(ref)是ref对象就给他返回value值 88 | // not ref 返回他本身 这个逻辑实际上就是我们的unRef 89 | // 124 get逻辑实现完成 90 | return unRef(Reflect.get(target,key)) 91 | }, 92 | set(target,key,value){ 93 | // 125 判断我们的对象是一个ref类型 并且它的值不是一个ref类型 这种情况才去修改它原先的值 94 | if(isRef(target[key]) && !isRef(value)){ 95 | return target[key].value = value 96 | }else{ 97 | // 当给定对象的值是一个ref对象时 我们直接让他替换原先的值 98 | return Reflect.set(target,key,value) 99 | } 100 | } 101 | }) 102 | } -------------------------------------------------------------------------------- /src/reactivity/test/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import {computed} from "../computed" 2 | import { reactive } from "../reactive" 3 | // 126 computed计算属性的断言 4 | describe('computed',()=>{ 5 | // 计算属性具有缓存功能 6 | it('happy path',()=>{ 7 | const user = reactive({ 8 | age:1 9 | }) 10 | // 接受一个函数 11 | const age = computed(()=>{ 12 | return user.age 13 | }) 14 | 15 | expect(age.value).toBe(1) 16 | 17 | }) 18 | 19 | // 第二个单侧 20 | it('should comput lazily',()=>{ 21 | const value = reactive({ 22 | foo:1 23 | }) 24 | const getter = jest.fn(()=>{ 25 | return value.foo 26 | }) 27 | const cValue = computed(getter) 28 | 29 | // 130 lazy 计算属性的懒执行 不会重复调用 30 | expect(getter).not.toHaveBeenCalled() 31 | 32 | expect(cValue.value).toBe(1) 33 | expect(getter).toHaveBeenCalledTimes(1) 34 | // should not compute again 35 | cValue.value 36 | expect(getter).toHaveBeenCalledTimes(1) 37 | 38 | // should not compute untill need 当给定的值发生变化时还是执行一次 39 | value.foo = 2 // set逻辑会触发我们的trigger 会重新执行我们的getter 40 | expect(getter).toBeCalledTimes(1) 41 | 42 | // now it should compute 43 | expect(cValue.value).toBe(2) 44 | expect(getter).toHaveBeenCalledTimes(2) 45 | 46 | // should not compute again 47 | cValue.value 48 | expect(getter).toHaveBeenCalledTimes(2) 49 | }) 50 | }) -------------------------------------------------------------------------------- /src/reactivity/test/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import {reactive} from '../reactive' 2 | import {effect,stop} from '../effect' 3 | // 22-此时去掉我们的user.age++步骤,执行我们的测试命令 会发现测试通过 4 | // 此时我们剩下的问题就是依赖收集和触发依赖 5 | describe('effect',()=>{ 6 | it.skip('happy path',()=>{ 7 | // 1-包装一个初始对象,设置初始值 8 | const user = reactive({ 9 | age:10 10 | }); 11 | 12 | let nextAge; 13 | // 2-设置响应对象 14 | effect(()=>{ 15 | nextAge = user.age + 1 16 | } 17 | ); 18 | // 3-断言出最后预计的结果 19 | expect(nextAge).toBe(11) 20 | 21 | // 4-初始对象的值发生变化 update 22 | user.age++; 23 | // 5-希望响应对象的值也发生同样的变化 24 | expect(nextAge).toBe(12); 25 | // 40-effect功能的完善 在我们调用effec时其实有后续的一套功能执行 effect(fn)=> function(runner)=> return 返回值 26 | 27 | // 43-逻辑完成执行测试 28 | it('should return runner when call runner',()=>{ 29 | let foo = 10 30 | const runner = effect(()=>{ 31 | foo++; 32 | return foo; 33 | } 34 | ) 35 | expect(foo).toBe(11) 36 | const r = runner(); 37 | expect(foo).toBe(12) 38 | expect(r).toBe('foo') 39 | } 40 | ) 41 | 42 | // 44 实现effect的scheduler功能 43 | it('scheduler',()=>{ 44 | // 通过effect的第二个参数给定的一个schecdule的 fn 45 | // effect 第一次执行的时候还会执行fn 46 | // 当响应式对象set update 不会执行fn 而是执行scheduler 47 | // 如果说当执行runner的时候 会再次执行fn 48 | let dummy; 49 | let run:any; 50 | const scheduler = jest.fn(()=>{ 51 | run = runner 52 | } 53 | ); 54 | const obj = reactive({foo:1}); 55 | const runner = effect(()=>{ 56 | dummy = obj.foo 57 | },{ scheduler } 58 | ); 59 | expect(scheduler).not.toHaveBeenCalled(); 60 | expect(dummy.toBe(1)); 61 | // should be called on first trigger 62 | obj.foo++; 63 | expect(scheduler).toHaveBeenCalledTimes(1); 64 | // should not run yet 65 | expect(dummy).toBe(1); 66 | // manually run 67 | run(); 68 | // should have run 69 | expect(dummy).toBe(2) 70 | 71 | // 48- 基本逻辑完成 执行单元测试 结果通过 72 | }); 73 | // stop功能实现 74 | it('stop',()=>{ 75 | let dummy; 76 | const obj = reactive({prop:1}); 77 | const runner = effect(()=>{ 78 | dummy = obj.prop 79 | } 80 | ); 81 | obj.prop = 2; 82 | expect(dummy).toBe(2); 83 | stop(runner); // 当stop调用后响应值不再更新 但是当runner函数再次调用后又会继续更新 84 | 85 | // 75 stop功能的优化 obj.prop = 3 || obj.prop++ 这两种表达式的测试结果不同,因为我们的obj.prop只涉及到我们的set 86 | // 而我们的 obj.prop++可以拆分为 obj.prop = obj.prop + 1 这个过程既包括了我们的get 也包括了我们的set 87 | // 而在触发我们的get操作时他又会去重新收集我们的依赖,这个过程抵消了我们stop中清除依赖的操作 所以导致我们的结果不同 88 | // 我们需要回到我们的trak函数中在收集依赖前对他进行一定的处理 89 | 90 | // obj.prop = 3; 91 | obj.prop++ 92 | 93 | expect(dummy).toBe(2); 94 | 95 | // stopped effect should still be manually callable 96 | runner() 97 | expect(dummy).toBe(3); 98 | }) 99 | 100 | // 56 onStop功能实现 所谓的onStop 就是当stop被执行时的一个确认stop被执行的函数,允许用户在这个函数中做一些事情 单侧如下 101 | it('onStop',()=>{ 102 | const obj = reactive({ 103 | foo:1 104 | }); 105 | const onStop = jest.fn(); 106 | let dummy; 107 | const runner = effect(()=>{ 108 | dummy = obj.foo; 109 | },{ 110 | onStop, 111 | } 112 | ); 113 | stop(runner) 114 | expect(onStop).toBeCalledTimes(1); 115 | } 116 | ) 117 | }) 118 | } 119 | ) -------------------------------------------------------------------------------- /src/reactivity/test/index.spec.ts: -------------------------------------------------------------------------------- 1 | // import {add} from '../index' 2 | // it("init",()=>{ 3 | // expect(add(1,1)).toBe(2) 4 | // } 5 | // ) 6 | -------------------------------------------------------------------------------- /src/reactivity/test/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import {isReactive,reactive,isProxy} from '../reactive' 2 | // 由effec拆分出来的测试块 3 | describe('reactive',()=>{ 4 | 5 | it('happy path',()=>{ 6 | // 6-继续初始化对象 7 | const original = {foo:1}; 8 | // 7-设置响应对象 9 | const observed = reactive(original); 10 | // 8-断言两者内存地址不一致,但内部数据一致 11 | expect(observed).not.toBe(original); 12 | expect(observed.foo).toBe(1); 13 | // 接下来就需要去实现 14 | 15 | // 66 我们的对象是否是isReactive类型的判断 去到reactive实现 验证了我们的对象是否是一个PROXY 16 | expect(isReactive(observed)).toBe(true) 17 | // 69 接下来验证我们的对象如果不是一个PROXY 会不会出错 18 | expect(isReactive(original)).toBe(false) 19 | // 96-加入判断是否为Proxy的断言 20 | expect(isProxy(observed)).toBe(true) 21 | } 22 | ) 23 | 24 | // 85 解决reactive对象有嵌套对象的case 回到我们basehandler返回的res 25 | test('nested reactive',()=>{ 26 | const original = { 27 | nested:{ 28 | foo:1 29 | }, 30 | array:[{bar:2}] 31 | }; 32 | const observed = reactive(original); 33 | expect(isReactive(observed.nested)).toBe(true) 34 | expect(isReactive(observed.array)).toBe(true) 35 | expect(isReactive(observed.array[0])).toBe(true) 36 | } 37 | ) 38 | 39 | } 40 | 41 | ) -------------------------------------------------------------------------------- /src/reactivity/test/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | // 60 readonly功能实现 该方法是由reactive导出 2 | import {readonly,isReadonly,isProxy} from '../reactive' 3 | describe('readonly',()=>{ 4 | it('happy path',()=>{ 5 | // not set 6 | const original = {foo:1,bar:{baz:2}}; 7 | const wrapped = readonly(original) 8 | expect(wrapped).not.toBe(original) 9 | // 71 实现我们的isReadonly功能 10 | expect(isReadonly(wrapped)).toBe(true) 11 | // 74 测试如果不是isReadonly类型的结果 执行我们所有的单侧,测试通过 功能实现 12 | expect(isReadonly(original)).toBe(false) 13 | // 89 嵌套对象的readonly断言 回到我们的basehandler进行功能实现 14 | expect(isReadonly(wrapped.bar)).toBe(true) 15 | expect(isReadonly(original.bar)).toBe(false) 16 | expect(wrapped.foo).toBe(1); 17 | // 检测是否为proxy 18 | expect(isProxy(wrapped)).toBe(true) 19 | } 20 | ); 21 | // 64 readonly的第二个功能点 调用set发出警告 22 | it('should call warning then set',()=>{ 23 | // console.warn() 24 | // mock 通过mock我们可以去构建一个假的警告方法 最后来进行验证 25 | console.warn = jest.fn() // jest.fn会创建一个函数,这个函数上有一些特殊的属性可以方便我们后续去做断言测试 26 | const user = readonly({ 27 | age:10 28 | }); 29 | user.age = 11 30 | expect(console.warn).toBeCalled 31 | } 32 | ) 33 | } 34 | ) -------------------------------------------------------------------------------- /src/reactivity/test/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | import {isRef, proxyRefs, ref, unRef} from '../ref' 4 | // 98 ref 单侧 5 | describe('ref',()=>{ 6 | // 101-基本的逻辑单侧 7 | it('happy path',()=>{ 8 | const a = ref(1) 9 | expect(a.value).toBe(1) 10 | }); 11 | 12 | // 102-第二个单侧 13 | it('should be reactive',()=>{ 14 | const a = ref(1) 15 | let dummy; 16 | let calls = 0; 17 | effect(()=>{ 18 | calls++; 19 | dummy = a.value 20 | }); 21 | // 正常的依赖收集和触发依赖 22 | expect(calls).toBe(1); 23 | expect(dummy).toBe(1); 24 | // set功能 105 单侧执行通过 25 | a.value = 2; 26 | expect(calls).toBe(2); 27 | expect(dummy).toBe(2); 28 | // same value should be trigger 边缘case=>相同的值不会重复触发响应 只需要在我们的set中执行新旧值的判断即可 29 | a.value = 2; // 逻辑代码完成后单侧通过 30 | expect(calls).toBe(2) 31 | expect(dummy).toBe(2) 32 | }) 33 | 34 | // 112 第三个单侧 当我们的ref接受一个对象时,判断是否会进行更新 35 | it('should make nested properties reactive',()=>{ 36 | const a = ref({ 37 | count:1 38 | }); 39 | let dummy; 40 | effect(()=>{ 41 | dummy = a.value.count 42 | }); 43 | expect(dummy).toBe(1) 44 | a.value.count = 2; 45 | expect(dummy).toBe(2) 46 | }) 47 | 48 | // 117 isRef工具函数 49 | // 逻辑代码完成 单侧执行成功 50 | it('isRef',()=>{ 51 | const a = ref(1); 52 | const user = reactive({ 53 | age:1 54 | }) 55 | expect(isRef(a)).toBe(true), 56 | expect(isRef(1)).toBe(false) 57 | expect(isRef(user)).toBe(false) 58 | }) 59 | 60 | // 120 unRef工具函数 61 | // 逻辑代码完成 单侧执行成功 62 | it('unRef',()=>{ 63 | const a =ref(1); 64 | expect(unRef(a)).toBe(1); 65 | expect(unRef(1)).toBe(1); 66 | }) 67 | 68 | // 122 proxyRef功能 所谓的proxyRef是指我们的ref类型的值被proxyRef包装后 69 | // 当我们再次访问ref中的值时不需要进行.value的获取 更加的方便 这种应用场景一般是在我们的模板中出现 70 | it('proxyRefs',()=>{ 71 | const user = { 72 | age:ref(10), 73 | name:'xiaohong' 74 | } 75 | // proxyRef的get 76 | const proxyUser = proxyRefs(user) 77 | expect(user.age.value).toBe(10) 78 | expect(proxyUser.age).toBe(10) 79 | expect(proxyUser.name).toBe('xiaohong') 80 | 81 | // proxyRef的set 82 | proxyUser.age = 20; 83 | expect(proxyUser.age).toBe(20) 84 | expect(user.age.value).toBe(20) 85 | 86 | proxyUser.age = ref(10); 87 | expect(proxyUser.age).toBe(10) 88 | expect(user.age.value).toBe(10) 89 | }) 90 | } 91 | ) -------------------------------------------------------------------------------- /src/reactivity/test/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadonly,shallowReadonly } from "../reactive"; 2 | 3 | // 91 shallowReadonly 功能实现 所谓的shallowReadonly就是指我们只需要对我们对象的表层数据进行处理,不需要对嵌套的数据进行处理 4 | 5 | describe('shallowReadonly',()=>{ 6 | test('should not make non-reactive properties reactive',()=>{ 7 | const props = shallowReadonly({n:{foo:1}}); 8 | expect(isReadonly(props)).toBe(true) 9 | expect(isReadonly(props.n)).toBe(false) 10 | }); 11 | // 95-基本逻辑代码完成后 引入readonly的set操作警告的断言 执行单侧成功 接下来我们实现isproxy的功能 12 | it('should call console.warn then when set',()=>{ 13 | // console.warn() 14 | // mock 通过mock我们可以去构建一个假的警告方法 最后来进行验证 15 | console.warn = jest.fn() // jest.fn会创建一个函数,这个函数上有一些特殊的属性可以方便我们后续去做断言测试 16 | const user = shallowReadonly({ 17 | age:10 18 | }); 19 | user.age = 11 20 | expect(console.warn).toHaveBeenCalled() 21 | }) 22 | 23 | }) -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | 4 | 5 | // 存值 6 | export function provide(key,value){ 7 | // getCurrentInstance必须在setup作用域下才能获取到有效的currentInstance 8 | const currentInstance:any = getCurrentInstance() 9 | 10 | if(currentInstance){ 11 | let {provides} = currentInstance 12 | const parentProvides = currentInstance.parent.provides 13 | // init 14 | if(provides === parentProvides){ 15 | provides = currentInstance.provides = Object.create(parentProvides) 16 | } 17 | provides[key] = value 18 | } 19 | } 20 | // 取值 21 | export function inject(key,defaultValue){ 22 | const currentInstance:any = getCurrentInstance() 23 | 24 | if(currentInstance){ 25 | const parentProvides = currentInstance.parent.provides 26 | if(key in parentProvides){ 27 | return parentProvides[key] 28 | }else if(defaultValue){ 29 | if(typeof defaultValue === 'function') 30 | return defaultValue() 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance" 2 | import {initProps} from './componentProps' 3 | import { shallowReadonly } from "../reactivity/reactive" 4 | import { emit } from "./componentEmit" 5 | import { initSlots } from "./componentSlots" 6 | import { proxyRefs } from "../reactivity" 7 | let currentInstance = null 8 | export function creatComponentInstance(vnode,parent){ 9 | console.log("kaobei",parent) 10 | const component = { 11 | vnode, 12 | type:vnode.type, 13 | setupState:{}, 14 | props:{}, 15 | next:null, 16 | slots:{}, 17 | subTree:{}, 18 | provides:parent?parent.provides:{}, 19 | parent, 20 | isMounted:false, 21 | emit:()=>{} 22 | } 23 | component.emit = emit.bind(null,component) as any 24 | return component 25 | } 26 | 27 | export function setupComponent(instance){ 28 | // TODO 29 | initProps(instance,instance.vnode.props) 30 | initSlots(instance,instance.vnode.children) 31 | instance.proxy = new Proxy({_:instance},PublicInstanceProxyHandlers) 32 | setupStatefulComponent(instance) 33 | } 34 | 35 | function setupStatefulComponent (instance:any){ 36 | const component = instance.vnode.type 37 | 38 | 39 | const {setup} = component 40 | 41 | 42 | if(setup){ 43 | // currentInstance = instance 44 | setCurrentInstance(instance) 45 | // 我们的setup可以返回一个对象或者是函数 46 | // 当我们返回一个函数时 就可以把它认为是我们的render函数 47 | // 如果返回的是一个对象 会把这个对象注入到我们组件的上下文中 48 | const setupResult = setup(shallowReadonly(instance.props),{emit:instance.emit}) 49 | setCurrentInstance(null) 50 | handleSetupResult(instance,setupResult) 51 | } 52 | } 53 | 54 | function handleSetupResult(instance,setupResult:any){ 55 | // function object 56 | // TODO function 57 | 58 | if(typeof setupResult === 'object'){ 59 | instance.setupState = proxyRefs(setupResult) 60 | } 61 | 62 | finishComponentSetup(instance) 63 | 64 | } 65 | 66 | function finishComponentSetup(instance:any){ 67 | // Implement 68 | const Component = instance.type 69 | 70 | // if(Component.render){ 假设render一定有值 71 | instance.render = Component.render 72 | // } 73 | } 74 | 75 | export function getCurrentInstance(){ 76 | return currentInstance 77 | } 78 | 79 | export function setCurrentInstance(instance){ 80 | currentInstance = instance 81 | } -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { cameLize, toHandlerKey } from "../shared" 2 | 3 | export function emit(instance,event,...args){ 4 | console.log("emit",emit) 5 | // instance.props => event 6 | const {props} = instance 7 | // add => Add 8 | // add-foo => addFoo 9 | 10 | const handlerName = toHandlerKey(cameLize(event)) 11 | 12 | // TPP 先去写一个特定的行为 在去重构成通用的行为 13 | const handler = props[handlerName] 14 | handler && handler(...args) 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function initProps(instance,rawProps){ 4 | instance.props = rawProps || {} 5 | 6 | // attr 7 | } -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from "../shared" 2 | 3 | const publicPropertiesMap = { 4 | $el:(i)=> i.vnode.el, 5 | // $slots 6 | $slots:(i)=> i.slots, 7 | $props:(i)=> i.props 8 | 9 | } 10 | export const PublicInstanceProxyHandlers = { 11 | get({_:instance},key){ 12 | // 先从setupstate中获取值 13 | const {setupState,props} = instance 14 | // if(key in setupState){ 15 | // return setupState[key] 16 | // } 17 | // 将上面的逻辑进行重构 加上我们的props的逻辑 18 | // const hasOwn = (val,key)=> Object.prototype.hasOwnProperty.call(val,key) 19 | if(hasOwn(setupState,key)){ 20 | return setupState[key] 21 | }else if(hasOwn(props,key)){ 22 | return props[key] 23 | } 24 | // key=>el 25 | const publicGetter = publicPropertiesMap[key] 26 | if(publicGetter){ 27 | return publicGetter(instance) 28 | } 29 | // if(key === '$el'){ 30 | // return instance.vnode.el 31 | // } 32 | } 33 | } -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/Shapeflags" 2 | 3 | export function initSlots(instance,children){ 4 | // slots 5 | const {vnode} = instance 6 | // 是SLOT组件才进行相应的处理 7 | if(vnode.shapeFlag & ShapeFlags.SLOT_CHILDREN){ 8 | normalizeObjectSlots(children,instance.slots) 9 | } 10 | 11 | // 判断children的数据类型做逻辑处理 12 | // instance.slots = Array.isArray(children)? children: [children] 13 | // children为对象的处理方式 14 | // const slots = {} 15 | // for (const key in children) { 16 | // const value = children[key] 17 | // // slot 18 | // slots[key] = normalizeSlotValue(value) 19 | // } 20 | // instance.slots = slots 21 | } 22 | function normalizeObjectSlots(children:any,slots:any){ 23 | for (const key in children) { 24 | const value = children[key] 25 | // slot 26 | slots[key] = (props) => normalizeSlotValue(value(props)) 27 | } 28 | } 29 | function normalizeSlotValue(value){ 30 | return Array.isArray(value)? value: [value] 31 | } -------------------------------------------------------------------------------- /src/runtime-core/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(preVnode,nextVnode){ 2 | 3 | const {props:preprops} = preVnode 4 | const {props:nextprops} = nextVnode 5 | 6 | for (const key in nextprops) { 7 | if(nextprops[key] !== preprops[key]){ 8 | return true 9 | }else{ 10 | return false 11 | } 12 | } 13 | 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | // import { render } from "./renderer"; 2 | import { createVNode } from "./vnode"; 3 | 4 | // render 5 | 6 | export function createAppAPI(render){ 7 | 8 | return function createAPP( rootComponent){ 9 | 10 | 11 | return { 12 | // 接受一个根容器 13 | mount(rootContainer){ 14 | // 在vue3都会将所有的元素转换成虚拟节点 15 | // 所有的逻辑操作都会基于vnode来执行 16 | 17 | const vnode = createVNode(rootComponent); 18 | 19 | 20 | render(vnode,rootContainer) 21 | } 22 | } 23 | 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | export function h(type,props?,children?){ 3 | return createVNode(type,props,children) 4 | } -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | // 导出的出口文件 2 | // export { createAPP } from "./createApp"; 3 | export {h} from './h' 4 | export {renderSlots} from './renderSlots' 5 | export {createTextVNode} from './vnode' 6 | export {getCurrentInstance} from './component' 7 | export {provide,inject} from './apiInject' 8 | export {createRenderer } from './renderer' 9 | export {nextTick} from './scheduler' -------------------------------------------------------------------------------- /src/runtime-core/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from "./vnode"; 2 | 3 | export function renderSlots(slots,name,props){ 4 | const slot = slots[name] 5 | if(slot){ 6 | if(typeof slot === 'function'){ 7 | // children是不可以有 array 8 | // 只需要把children 渲染出来 9 | return createVNode(Fragment,{},slot(props)) 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../reactivity/effect" 2 | import { isObject } from "../shared" 3 | import { ShapeFlags } from "../shared/Shapeflags" 4 | import { creatComponentInstance, setupComponent } from "./component" 5 | import { createAppAPI } from "./createApp" 6 | import { Fragment,Text } from "./vnode" 7 | import {shouldUpdateComponent} from './componentUpdateUtils' 8 | import { queueJobs } from "./scheduler" 9 | export function createRenderer(options){ 10 | 11 | const {createElement:hostCreateElement, 12 | patchProp:hostPatchProp, 13 | insert:hostInsert, 14 | remove:hostRemove, 15 | setElementText:hostSetElementText} = options 16 | 17 | function render(vnode,container){ 18 | // 构建patch方法 方便后续的递归 19 | patch(null,vnode,container,null,null) 20 | } 21 | // 改为接受 两个虚拟节点 n1 表示之前的虚拟节点 n2表示最新的虚拟节点 22 | function patch(n1,n2,container,parentComponent,anchor){ 23 | const {type,shapeFlag} = n2 24 | // 增加一种类型只渲染我们的children 25 | // Fragment => 只渲染我们的children 26 | switch (type) { 27 | case Fragment: 28 | processFragment(n1,n2,container,parentComponent,anchor) 29 | break; 30 | case Text: 31 | processText(n1,n2,container) 32 | break; 33 | default: // vnode => flag 我们当前虚拟节点的类型都称之为我们的flag 34 | // 比如我们的字符串就作为元素来对待 35 | // if(typeof vnode.type === 'string'){ 36 | if(shapeFlag & ShapeFlags.ELEMENT ){ 37 | // 上面的判断可以使用位运算符来进行替换 38 | // 当虚拟节点的类型是一个字符串时,就作为一个元素节点 39 | processElement(n1,n2,container,parentComponent,anchor) 40 | // isObject(vnode.type) 同样进行替换 41 | }else if(shapeFlag & ShapeFlags.STATEFUL_COMPONENT){ 42 | processComponent(n1,n2,container,parentComponent,anchor) 43 | } 44 | break; 45 | } 46 | 47 | // 去处理组件 48 | 49 | // 判断 是不是 element类型 50 | // 是element类型就处理element 51 | // processElement() 52 | // 是component就处理component 53 | // console.log(vnode.type,vnode) 54 | // shapeflags 55 | } 56 | function processText(n1,n2:any,container:any){ 57 | const {children} = n2 58 | const textNode = (n2.el = document.createTextNode(children)) 59 | container.append(textNode) 60 | } 61 | function processFragment(n1,n2,container,parentComponent,anchor){ 62 | // implement 63 | // mountChildren的功能实际上就是遍历了我们的children 并再次进行patch 64 | mountChildren(n2.children,container,parentComponent,anchor) 65 | } 66 | // 作为元素的处理方式 67 | function processElement(n1,n2:any,container:any,parentComponent,anchor){ 68 | // console.log('processElement') 69 | if(!n1){ 70 | // element 主要有初始化init和更新update 71 | mountElement(n2,container,parentComponent,anchor) 72 | }else{ 73 | patchElement(n1,n2,container,parentComponent,anchor) 74 | } 75 | } 76 | 77 | function patchElement(n1,n2:any,container,parentComponent,anchor){ 78 | console.log('patchElement') 79 | console.log('n1',n1) 80 | console.log('n2',n2) 81 | console.log('container',container) 82 | 83 | 84 | const oldProps = n1.props || {} 85 | const newProps = n2.props || {} 86 | 87 | const el = (n2.el = n1.el) 88 | 89 | patchChildren(n1,n2,el,parentComponent,anchor) 90 | patchProps(el,oldProps,newProps) 91 | } 92 | 93 | function patchChildren(n1,n2,container,parentComponent,anchor){ 94 | const prevShapeFlag = n1.shapeFlag 95 | const {shapeFlag} = n2 96 | const c1 = n1.children 97 | const c2 = n2.children 98 | if(shapeFlag & ShapeFlags.TEXT_CHILDREN){ 99 | if(prevShapeFlag & ShapeFlags.ARRAY_CHILDREN){ 100 | // 1.把n1的元素(children)清空 101 | unmountChildren(n1.children) 102 | } 103 | if(c1 !== c2){ 104 | // 2.设置text 105 | hostSetElementText(container,c2) 106 | } 107 | }else{ 108 | if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN){ 109 | // 清空原来的文本 110 | hostSetElementText(container,'') 111 | // 直接将children进行mount 112 | mountChildren(c2,container,parentComponent,anchor) 113 | }else{ 114 | // diff array with array 115 | patchKeyedChildren(c1,c2,container,parentComponent,anchor) 116 | } 117 | } 118 | 119 | } 120 | 121 | function patchKeyedChildren(c1,c2,container,parentComponent,parentAnchor){ 122 | const l2 = c2.length 123 | let i = 0 124 | let e1 = c1.length -1 125 | let e2 = l2 -1 126 | 127 | function isSomeVNodeType(n1,n2){ 128 | // type 129 | // key 130 | return n1.type === n2.type && n1.key === n2.key 131 | } 132 | // 左侧 133 | while (i<= e1 && i<= e2) { 134 | const n1 = c1[i] 135 | const n2 = c2[i] 136 | if(isSomeVNodeType(n1,n2)){ 137 | patch(n1,n2,container,parentComponent,parentAnchor) 138 | }else{ 139 | break 140 | } 141 | i++ 142 | } 143 | console.log(i) 144 | // 右侧 145 | while (i<= e1 && i<= e2) { 146 | const n1 = c1[e1] 147 | const n2 = c2[e2] 148 | if(isSomeVNodeType(n1,n2)){ 149 | patch(n1,n2,container,parentComponent,parentAnchor) 150 | }else{ 151 | break 152 | } 153 | e1-- 154 | e2-- 155 | } 156 | // 新的比老的多 需要进行创建 157 | if(i>e1){ 158 | if(i<=e2){ 159 | const nextPos = e2 + 1 160 | const anchor = e2 + 1e2){ // 老的比新的多 需要删除 167 | while(i<=e1){ 168 | hostRemove(c1[i].el) 169 | i++ 170 | } 171 | }else{ 172 | // 中间乱序对比的部分 173 | let s1 = i // e1 174 | let s2 = i // e2 175 | 176 | const toBePatched = e2-s2 + 1 // e2中乱序的数量 177 | let patched = 0 // 记录当前处理的数量 178 | const keyToNewIndexMap = new Map() 179 | 180 | const newIndexToOldIndexMap = new Array(toBePatched) 181 | 182 | // 判断是否需要进行移动 逻辑优化 183 | let moved = false 184 | let maxNewIndexSoFar = 0 185 | // 重置新节点数组的索引值 186 | for (let i = 0; i < toBePatched; i++) { 187 | newIndexToOldIndexMap[i] = 0 188 | } 189 | for (let i = s2; i <= e2; i++) { 190 | const nextChild = c2[i] 191 | keyToNewIndexMap.set(nextChild.key,i) 192 | } 193 | for (let i = s1; i <= e1; i++) { 194 | const prevChild = c1[i] 195 | 196 | if(patched >= toBePatched){ 197 | hostRemove(prevChild.el) 198 | continue 199 | } 200 | // 有key直接找映射表 201 | let newIndex 202 | if(prevChild.key !== null){ 203 | newIndex = keyToNewIndexMap.get(prevChild.key) 204 | }else{ // 没有key继续遍历 205 | for (let j = s2; j <= e2; j++) { 206 | // 借助已经封装好的方法 207 | if(isSomeVNodeType(prevChild,c2[j])){ 208 | newIndex = j 209 | 210 | break 211 | } 212 | } 213 | } 214 | // 新值中没有老值,进行删除 215 | if(newIndex === undefined){ 216 | hostRemove(prevChild.el) 217 | }else{ 218 | // 新值大于记录的值 重置最大的值 219 | if(newIndex>= maxNewIndexSoFar){ 220 | maxNewIndexSoFar = newIndex 221 | }else{ 222 | // 新值小于记录的值说明进行位置的移动 223 | moved = true 224 | } 225 | 226 | // 证明新节点是存在的 在此处将老节点进行遍历对新节点进行重新赋值 227 | // 因为此处我们的索引计算包含了前面的部分所以需要减去前面的部分也就是s2 228 | // 由于新节点可能在老节点中是不存在的 所以需要考虑到为0的情况 可以将我们的i加1处理 229 | newIndexToOldIndexMap[newIndex-s2] = i + 1 230 | // 存在继续进行深度对比 231 | patch(prevChild,c2[newIndex],container,parentComponent,null) 232 | patched++ 233 | } 234 | 235 | } 236 | // 给最长递增子序列算法准备进行处理的数组 237 | const increasingNewIndexSequence:any = moved? getSequence(newIndexToOldIndexMap) :[] // 需要进行位置的移动时才调用算法,减少不必要的逻辑代码 238 | let j = increasingNewIndexSequence.length-1 239 | // 获取到我们的最长递增子序列这是一个数组,需要将我们的老值进行遍历 然后 240 | // 利用两个指针分别指向我们的最长递增子序列和我们的老值 如果老值没有匹配 则说明需要进行位置移动 241 | // toBePatched就是我们的新值的中间乱序的长度 242 | for (let i = toBePatched -1; i >= 0; i--) { 243 | const nextIndex = i +s2 244 | const nextChild = c2[nextIndex] 245 | const anchor = nextIndex + 1 0){ 284 | 285 | for (const key in oldProps) { 286 | if(!(key in newProps)){ 287 | hostPatchProp(el,key,oldProps[key],null) 288 | } 289 | } 290 | } 291 | } 292 | } 293 | function mountElement(vnode:any,container:any,parentComponent,anchor){ 294 | // canvas 295 | // new Element() 296 | 297 | // 作为元素的处理 基于vnode来创建元素 298 | // 我们所谓的虚拟节点中的内容主要由 type props children 这里的type一般有string和array 299 | // 还是按照我们正常创建元素的方式来创建虚拟DOM 300 | // 这里的el是属于我们的element类型也就是div的,并不是我们认为的初始化的虚拟节点 301 | const el = (vnode.el = hostCreateElement(vnode.type)) 302 | // string array 303 | const {children,shapeFlag} = vnode 304 | // 字符串类型的处理方式 305 | // children 306 | if(shapeFlag & ShapeFlags.TEXT_CHILDREN){ 307 | // if(typeof children === 'string'){ 308 | // textchildren 309 | el.textContent = children; 310 | // arraychildren 311 | // Array.isArray(children) 312 | }else if(shapeFlag & ShapeFlags.ARRAY_CHILDREN){ 313 | // 逻辑抽离 函数封装 314 | mountChildren(vnode.children,el,parentComponent,anchor) 315 | // children.forEach(v=>{ 316 | // patch(v,el) 317 | // }) 318 | } 319 | // props 320 | const {props} = vnode 321 | for (let key in props) { 322 | const val = props[key] 323 | // const isOn = key=> /^on[A-Z]/.test(key) 324 | // console.log(key) 325 | // // 如果我们的key是我们的onclick我们就可以给他添加一个点击事件 326 | // if(isOn(key)){ 327 | // el.addEventListener(key.slice(2).toLowerCase(),val) 328 | // }else{ 329 | // el.setAttribute(key,val) 330 | // } 331 | hostPatchProp(el,key,null,val) 332 | } 333 | // canvas 334 | // el.x = 10 335 | // addChild() 336 | // container.append(el) 337 | hostInsert(el,container,anchor) 338 | } 339 | function mountChildren(children,container,parentComponent,anchor){ 340 | children.forEach(v=>{ 341 | patch(null,v,container,parentComponent,anchor) 342 | }) 343 | } 344 | function processComponent(n1,n2:any,container:any,parentComponent,anchor){ 345 | // 当n1没有值时才进行创建 346 | if(!n1){ 347 | mountComponent(n2,container,parentComponent,anchor) 348 | }else{ 349 | // 有值就进行更新逻辑 350 | updateComponent(n1,n2) 351 | } 352 | } 353 | 354 | function updateComponent(n1,n2){ 355 | const instance = n2.component = n1.component 356 | // 判断组件实例是否应该更新 357 | if(shouldUpdateComponent(n1,n2)){ 358 | instance.next = n2 359 | instance.update() 360 | }else{ 361 | // 不需要更新时也需要将组件的状态进行更新 362 | n2.el = n1.el 363 | instance.vnode = n2 364 | } 365 | } 366 | 367 | function mountComponent(initialvnode:any,container,parentComponent,anchor){ 368 | // throw new Error('Function not implementd') 369 | const instance = initialvnode.component = creatComponentInstance(initialvnode,parentComponent) 370 | 371 | setupComponent(instance) 372 | setupRenderEffect(instance,initialvnode,container,anchor) 373 | } 374 | 375 | 376 | function setupRenderEffect(instance:any,initialvnode,container,anchor){ 377 | 378 | instance.update = effect(()=>{ 379 | if(!instance.isMounted){ 380 | console.log('init') 381 | const {proxy} = instance 382 | const subTree = instance.subTree = instance.render.call(proxy); 383 | // console.log(subTree) 384 | // vndoeTree => patch 385 | // vnode => element =>mountElement 386 | patch(null,subTree,container,instance,anchor) 387 | 388 | // 我们这里的subTree就是我们的根节点,我们所要赋值的el可以在subTree上找到 389 | // 传入我们的虚拟节点 390 | initialvnode.el = subTree.el 391 | instance.isMounted = true 392 | }else{ 393 | console.log('update') 394 | // 需要一个更新之后的vnode 395 | const {next,vnode} = instance 396 | if(next){ 397 | next.el = vnode.el 398 | 399 | updateComponentPreRender(instance,next) 400 | } 401 | const {proxy} = instance 402 | const subTree = instance.render.call(proxy); 403 | const prevSubTree = instance.subTree 404 | 405 | instance.subTree = subTree 406 | // console.log('current',subTree) 407 | // console.log('pre',prevSubTree) 408 | patch(prevSubTree,subTree,container,instance,anchor) 409 | } 410 | },{ 411 | scheduler(){ 412 | console.log('update -- scheduler') 413 | queueJobs(instance.update) 414 | } 415 | }) 416 | } 417 | 418 | return { 419 | createApp:createAppAPI(render) 420 | } 421 | } 422 | 423 | function updateComponentPreRender(instance,nextVnode){ 424 | instance.vnode = nextVnode 425 | instance.next = null 426 | 427 | instance.props = nextVnode.props 428 | } 429 | 430 | // 最长递增子序列算法 431 | 432 | function getSequence(arr){ 433 | const p = arr.slice() 434 | const result = [0] // 存储长度为i的递增子序列的索引 435 | let i,j,u,v,c 436 | const len = arr.length 437 | for (let i = 0; i < len; i++) { 438 | const arrI = arr[i] 439 | if(arrI !== 0){ 440 | // 把j赋值为数组最后一项 441 | j = result[result.length-1] 442 | // result存储的最后一个值小于当前值 443 | if(arr[j] < arrI){ 444 | // 存储在result更新前的最后一个索引的值 445 | p[i] = j 446 | result.push(i) 447 | continue 448 | } 449 | u = 0 450 | v = result.length -1 451 | // 二分搜索 查找比arrI小的节点 更新result的值 452 | while (u> 1 454 | if(arr[result[c]] 0){ 462 | p[i] = result[u-1] 463 | } 464 | result[u] = i 465 | } 466 | } 467 | } 468 | u = result.length 469 | v = result[u-1] 470 | // 回溯数组 找到最终的索引 471 | while (u-- > 0) { 472 | result[u] = v 473 | v = p[v] 474 | } 475 | return result 476 | } -------------------------------------------------------------------------------- /src/runtime-core/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue:any[] = [] 2 | // 引入一个开关 3 | let isFlushPending = false 4 | let p = Promise.resolve() 5 | export function nextTick(fn){ 6 | 7 | return fn? p.then(fn) : p 8 | } 9 | 10 | export function queueJobs(job){ 11 | 12 | if(queue.includes(job)){ 13 | 14 | }else{ 15 | queue.push(job) 16 | } 17 | queueFlush() 18 | } 19 | 20 | function queueFlush(){ 21 | if(isFlushPending) return 22 | isFlushPending = true 23 | 24 | nextTick(flushJobs) 25 | // Promise.resolve().then(()=>{ 26 | // 这段代码可以利用上面的nextTick来执行 将下面的代码进行抽离 27 | // }) 28 | } 29 | 30 | function flushJobs(){ 31 | isFlushPending = false // 重置开关 32 | let job 33 | while (job = queue.shift()) { 34 | job && job() 35 | } 36 | } 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/Shapeflags" 2 | 3 | export const Fragment = Symbol('Fragment') 4 | export const Text = Symbol('Text') 5 | 6 | export function createVNode(type,props?,children?){ 7 | 8 | const vnode = { 9 | type, 10 | props, 11 | children, 12 | component:null, 13 | key:props && props.key, 14 | shapeFlag:getShapeFlag(type), 15 | el:null 16 | } 17 | // children 18 | if(typeof children === 'string'){ 19 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN 20 | }else if(Array.isArray(children)){ 21 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN 22 | } 23 | 24 | // 如何判定给定的参数是一个slot参数 25 | // 必须是一个组件节点 并且它的children必须是一个Object 26 | if(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT){ 27 | if(typeof children === 'object'){ 28 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN 29 | } 30 | } 31 | return vnode 32 | 33 | } 34 | 35 | function getShapeFlag(type){ 36 | return typeof type === 'string' ? ShapeFlags.ELEMENT:ShapeFlags.STATEFUL_COMPONENT 37 | } 38 | 39 | export function createTextVNode(text:string){ 40 | return createVNode(Text,{},text) 41 | } -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../runtime-core/renderer"; 2 | 3 | function createElement(type){ 4 | // console.log('createElement------------') 5 | return document.createElement(type) 6 | 7 | } 8 | 9 | function patchProp(el,key,prevVal,nextVal){ 10 | // console.log('patchProp---------------') 11 | const isOn = key=> /^on[A-Z]/.test(key) 12 | // console.log(key) 13 | // 如果我们的key是我们的onclick我们就可以给他添加一个点击事件 14 | if(isOn(key)){ 15 | el.addEventListener(key.slice(2).toLowerCase(),nextVal) 16 | }else{ 17 | if(nextVal === undefined || nextVal === null){ 18 | el.removeAttribute(key) 19 | }else{ 20 | el.setAttribute(key,nextVal) 21 | } 22 | } 23 | } 24 | 25 | 26 | function insert(child,parent,anchor){ 27 | // console.log('insert',el,parent) 28 | // parent.append(el) 29 | parent.insertBefore(child,anchor || null) 30 | } 31 | 32 | function remove(children){ 33 | const parent = children.parentNode 34 | if(parent){ 35 | parent.removeChild(children) 36 | } 37 | } 38 | function setElementText(el,text){ 39 | el.textContent = text 40 | } 41 | 42 | const renderer:any = createRenderer({ 43 | createElement, 44 | patchProp, 45 | insert, 46 | remove, 47 | setElementText 48 | }) 49 | 50 | 51 | export function createApp(...args){ 52 | return renderer.createApp(...args) 53 | } 54 | 55 | export * from '../runtime-core' 56 | -------------------------------------------------------------------------------- /src/shared/Shapeflags.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | export const enum ShapeFlags{ 7 | // 利用位运算进行查和改 修改就使用我们的或.查找就使用我们的与 8 | ELEMENT = 1, // 0001 9 | STATEFUL_COMPONENT = 1 << 1, // 0010 10 | TEXT_CHILDREN = 1 << 2, // 0100 11 | ARRAY_CHILDREN = 1 << 3, // 1000 12 | SLOT_CHILDREN = 1 << 4 13 | } -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const extend = Object.assign; 2 | 3 | // 87定义并导出isObject函数 4 | export const isObject = (val)=>{ 5 | return val !== null && typeof val === 'object' 6 | } 7 | 8 | // 108 定义并导出 hasChange函数 9 | export const hasChanged= (value,newValue)=>{ 10 | return !Object.is(value,newValue) 11 | } 12 | 13 | // 将我们的hasOwn函数提取导出 14 | export const hasOwn = (val,key)=>Object.prototype.hasOwnProperty.call(val,key) 15 | 16 | // 对emit字符串绑定事件的处理函数 17 | export const cameLize = (str:string)=>{ 18 | return str.replace(/-(\w)/g,(_,c:string)=>{ 19 | return c?c.toUpperCase():'' 20 | } 21 | ) 22 | } 23 | 24 | 25 | export const capitalize = (str:string)=>{ 26 | return str.charAt(0).toUpperCase() + str.slice(1) 27 | } 28 | export const toHandlerKey = (str:string)=>{ 29 | return str? 'on' + capitalize(str):"" 30 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | "lib": ["DOM","ES6","ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "esnext", /* Specify what module code is generated. */ 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | } 101 | } 102 | --------------------------------------------------------------------------------