├── .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('')){
43 | for (let i = ancestors.length - 1; i >= 0; i--) {
44 | const tag = ancestors[i].tag
45 | if(startsWithEndTagOpen(s,tag)){
46 | return true
47 | }
48 | }
49 | }
50 | // if(ancestors && s.startsWith(`${ancestors}>`)){
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('') && source.slice(2,2+tag.length).toLowerCase() === tag.toLowerCase()
111 | }
112 |
113 | function parseTag(context:any,type:TagType){
114 | const match:any = /^<\/?([a-z]*)/i.exec(context.source)
115 | console.log(match)
116 | const tag = match[1]
117 | // 2 删除处理完成的代码
118 | advanceBy(context,match[0].length)
119 | advanceBy(context,1)
120 | console.log(context.source)
121 | if(type === TagType.End) return
122 | return {
123 | type:NodeTypes.ELEMENT,
124 | tag,
125 | }
126 | }
127 |
128 | function parseInterpolation(context){
129 | // {{message}}
130 | const openDelimiter = '{{'
131 | const closeDelimiter = '}}'
132 | const closeIndex = context.source.indexOf(closeDelimiter,openDelimiter.length)
133 | advanceBy(context,openDelimiter.length)
134 |
135 | const rawContentLength = closeIndex -openDelimiter.length
136 | // 未处理的
137 | // const rawcontent = context.source.slice(0,rawContentLength)
138 | const rawContent = parseTextData(context,rawContentLength)
139 | const content = rawContent.trim()
140 | advanceBy(context,closeDelimiter.length)
141 | // context.source = context.source.slice(rawContentLength + closeDelimiter.length)
142 |
143 | return {
144 | type:NodeTypes.INTERPOLATION,
145 | content:{
146 | type:NodeTypes.SIMPLE_EXPRESSION,
147 | content:content
148 | }
149 | }
150 | }
151 | function advanceBy(context:any,length:number){
152 | context.source = context.source.slice(length)
153 | }
154 |
155 | function createRoot(children){
156 | return {
157 | children
158 | }
159 | }
160 |
161 | function createParseContext(content:string):any{
162 | return{
163 | source:content
164 | }
165 | }
--------------------------------------------------------------------------------
/src/compiler-core/src/transform.ts:
--------------------------------------------------------------------------------
1 | import { NodeTypes } from "./ast"
2 |
3 | export function transform(root,options={}){
4 | // 存储我们的初始值
5 | const context = createTransformContext(root,options)
6 | // 1 遍历 深度优先搜索
7 | traverseNode(root,context)
8 |
9 | createRootCodegen(root)
10 | root.helpers = [...context.helpers.keys()]
11 | }
12 |
13 | function createRootCodegen(root:any){
14 | root.codegenNode = root.children[0]
15 | }
16 |
17 | function createTransformContext(root:any,options:any):any{
18 | const context = {
19 | root,
20 | nodeTransforms:options.nodeTransforms || [],
21 | helpers: new Map(),
22 | helper(key){
23 | context.helpers.set(key,1)
24 | }
25 | }
26 |
27 | return context
28 | }
29 |
30 |
31 | function traverseNode(node:any,context){
32 | console.log(node)
33 | // if(node.type === NodeTypes.TEXT){
34 | // node.content = node.content + ' mini-vue'
35 | // }
36 | const nodeTransforms = context.nodeTransforms
37 | for (let i = 0; i < nodeTransforms.length; i++) {
38 | const transform = nodeTransforms[i]
39 | transform(node)
40 | }
41 |
42 | switch (node.type) {
43 | case NodeTypes.INTERPOLATION:
44 | context.helper('toDisplayString')
45 | break;
46 |
47 | default:
48 | break;
49 | }
50 |
51 |
52 | tranversChildren(node,context)
53 | // const children = node.children
54 |
55 | // if(children){
56 | // for (let i = 0; i < children.length; i++) {
57 | // const node = children[i]
58 | // traverseNode(node,context)
59 | // }
60 | // }
61 | }
62 |
63 | function tranversChildren(node,context){
64 | const children = node.children
65 |
66 | if(children){
67 | for (let i = 0; i < children.length; i++) {
68 | const node = children[i]
69 | traverseNode(node,context)
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/src/compiler-core/tests/__snapshots__/codegen.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`codegen interpolation 1`] = `
4 | "const { toDisplayString:_toDisplayString } = Vue
5 | return function render(_ctx, _cache){return'[object Object]'}"
6 | `;
7 |
8 | exports[`codegen string 1`] = `
9 | "const { } = Vue
10 | return function render(_ctx, _cache){return'hi'}"
11 | `;
12 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/codegen.spec.ts:
--------------------------------------------------------------------------------
1 | import { generate } from "../src/codegen"
2 | import { baseParse } from "../src/parse"
3 | import { transform } from "../src/transform"
4 |
5 |
6 | describe('codegen',()=>{
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('')
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 |
--------------------------------------------------------------------------------
/src/drawio/reg.drawio.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/drawio/transform.drawio.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------