vue3 源码解析(手写min-vue)

在使用了一段vue3之后,手动实现下代码,也是为了理解里面的原理,vue3和vue2最大的区别是在响应式处理上的方法发生了变化,之前使用Objest.defineProperty现在使用Proxy处理的。
下面围绕着Proxy来实现min-vue

首先我们先定义一个需求,响应式变化,假设我定义了一个a,和一个b,b是依据a得到的

/* 需求1 */
let a = 30
let b = a+1
如何实现a变化后让b也变化呢

那就是每次我跟新a之后再手动改变b

/* 需求2 实现变化 */
let a = 30
let b = a+1
a = 40
b = a + 1 // b:41
a = 50
b = a + 1 // b:20

优化b的变化方式

let a = 30;
let b;
setB () {
  b= a+1
}
a = 40;
setB(); // b:40
a = 50;
setB(); // b:51

我们如何做到不手动触发这个setB呢, 让它自己触发对b进行赋值操作呢
那我们就对a 初始化的时候做一些操作。

我们先看下vue3中的是怎么用api的, 看过vue3的都知道是原始数据类型用ref ,对象是reactive, 通过这两个方法可以实现数据的响应式

用法

ref和reactive和用法和区别我就不细说了,需要注意的是在vue3中ref和reactive是拆分出来了,所以需要单独引入
需要注意的是在vue3中ref和reactive是拆分出来了,所以需要单独引入
ref建议使用原始数据初始化,猜测是为了性能考虑的,如果你在ref初始化是传入了对象也是可以响应化的,但是建议不是原始类属性的响应化用reactive

使用 ref 实现响应式 这里用的ts

import { ref, watchEffect } from 'vue'
let a = ref<number>(30)
let b:number;
watchEffect(() => {
  b = a.value + 1
  console.log('change', a.value, b)
})

a.value = 40
/*
log:
change 30 31
change 40 41
*/

使用 reactive 实现响应式 这里用的ts

let a = reactive({
  value:30
})
let b:number;
watchEffect(() => {
  b = a.value + 1
  console.log('change', a.value, b)
})
a.value = 40
/*
log:
change 30 31
change 40 41
*/

实现 reactive

使用es6 的class生命, reactive的核心就是收集依赖,然后监听变化触发依赖的这个过程。

1.创建 dep类,收集依赖的一些api

 class Dep {
  // 收集依赖
  effects:Set<{[key:string]:()=>void}>
  constructor () {
    this.effects = new Set();
  }
  depend () {}
  // 触发依赖
  notice () {}
 }
 // 收集依赖
 function watchEffect (effect: () => void) {}

问题: 我们在watchEffect中做的一些操作如何在dep类中收集到依赖呢,

dep 如何收集依赖

我们可以定义一个局部变量储存这个依赖等到用的时候在这个局部变量中使用就可以了

 let currentEffect:()=>void | undefined; 
 class Dep {
  // 收集依赖
  effects:Set<()=>void>
  constructor () {
    this.effects = new Set();
  }
  // 收集依赖
  depend () {
    if(currentEffect) {
      this.effects.add(currentEffect)
    }
  }
  // 触发依赖
  notice () { }
 }
// 收集依赖
const dep = new Dep()
function watchEffect (effect:()=>void) {
  currentEffect = effect
  dep.depend()
  currentEffect = undefined
 }

问题: 我们如何初识化的时候给它赋值呢

初始化赋值

let currentEffect:()=>void | undefined; 
 
class Dep {
  // 收集依赖
  effects:Set<()=>void>
  _val:any
  constructor (val:any) {
    this._val = val
    this.effects = new Set();
  }
  get value () {
    return this._val
  }
  set value(v : any) {
    this._val = v;
    this.notice()
  }
  
  // 收集依赖
  depend () {
    if(currentEffect) {
      this.effects.add(currentEffect)
    }
  }
  // 触发依赖
  notice () {
    this.effects.forEach((effect:()=>void|undefined) => {
      effect()
    })
  }
}

// 收集依赖
function watchEffect (effect:()=>void | undefined) {
  currentEffect = effect;
  effect();
  dep.depend();
}
const dep = new Dep(30)
let  b:number;
watchEffect(() => {
  b = dep.value + 1
  console.log('change', b)
})
dep.value = 40
/*
log:

*/

问题: 有优空间

优化操作

currentEffect: 避免重复收集和释放
执行顺序可循

let currentEffect:{():void}|null; 
 
class Dep {
  // 收集依赖
  effects:Set<()=>void>
  _val:any
  constructor (val:any) {
    this._val = val
    this.effects = new Set();
  }
  get value () {
    dep.depend();
    return this._val
  }
  set value(v : any) {
    this._val = v;
    this.notice()
  }
  
  // 收集依赖
  depend () {
    if(currentEffect) {
      this.effects.add(currentEffect)
    }
  }
  // 触发依赖
  notice () {
    this.effects.forEach((effect:()=>void) => {
      effect()
    })
  }
}

// 收集依赖
function watchEffect (effect:()=>void) {
  currentEffect = effect;
  effect();
  currentEffect = null
}

const dep = new Dep(30)
let  b:number;
watchEffect(() => {
  b = dep.value + 1
  console.log('change', b)
})
dep.value = 40

以上是ref的实现,reactive的实现个人感觉实在这个上面添加的proxy

reactive实现

首先要知道vue3中的proxy,就是代理对象,就是通过对对象的代理进行响应式操作,在对象的赋值和回去的时候修改值,

function reactive(row:any) {
  return new Proxy(row, {
    get (target, key:string) {
      console.log(`获取${key}的值`)
      return Reflect.get(target,key)
    }
  }) 
}
const obj = reactive({
  name:'gqq',
  children:{
    name:'g_xxx'
  }
})
obj.name;
obj.children
/*
log:
获取name的值
获取children的值
*/

问题: 现在可以实现了proxy,知道它调用了哪些属性,接下来我们就可以给他设置响应式

对proxy代理的属性添加监听的方法

const targetMap = new Map()

function  getDep(target:any,key:any) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Dep()
    depsMap.set(key,dep)
  }
  return dep
}

function reactive(row:any) {
  return new Proxy(row, {
    get (target, key:string) {
      let dep = getDep(target, key)
      dep.depend()
      console.log(target, key)
      return Reflect.get(target,key)
    },
    set (target,key,value) {
      let dep = getDep(target, key)
      Reflect.set(target,key,value)
      dep.notice()
      return true
    }
  }) 
}
const obj = reactive({
  name:'gqq',
  children:{
    name:'g_xxx'
  }
})
let objName:any = null
watchEffect(() => {
  objName = obj.name
  
})
console.log(objName) // gqq
obj.name = 'XXXX'
console.log(objName) // XXXX

问题: 写完了,验证下对不对

测试reactive

使用vue的效果

import { watchEffect, reactive } from 'vue'
let a = reactive({
  age: 22
})
let b:number;
watchEffect(() => {
  b = a.age + 1
  console.log('change', a.age, b)
})
/*
change 22 23
*/

自己写的效果

import { watchEffect, reactive } from './core/reactivity'
let a = reactive({
  age: 22
})
let b:number;
watchEffect(() => {
  b = a.age + 1
  console.log('change', a.age, b)
})
/*
change 22 23
*/

reactivity.ts

let currentEffect:{():void}|null; 
 
class Dep {
  // 收集依赖
  effects:Set<()=>void>
  _val:any
  constructor (val?:any) {
    this._val = val
    this.effects = new Set();
  }
  get value () {
    this.depend();
    return this._val
  }
  set value(v : any) {
    this._val = v;
    this.notice()
  }
  // 收集依赖
  depend () {
    if(currentEffect) {
      this.effects.add(currentEffect)
    }
  }
  // 触发依赖
  notice () {
    this.effects.forEach((effect:()=>void) => {
      effect()
    })
  }
}

// 收集依赖
export function watchEffect (effect:()=>void) {
  currentEffect = effect;
  effect();
  currentEffect = null
}

const targetMap = new Map()

function  getDep(target:any,key:any) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Dep()
    depsMap.set(key,dep)
  }
  return dep
}

export function reactive(row:any) {
  return new Proxy(row, {
    get (target, key:string) {
      let dep = getDep(target, key)
      dep.depend()
      return Reflect.get(target,key)
    },
    set (target,key,value) {
      let dep = getDep(target, key)
      Reflect.set(target,key,value)
      dep.notice()
      return true
    }
  }) 
}

到现在为止我们的响应式实现了,还有的就是细节问题了随后优化,加下来实现下 setup 和 rendee ,来实现下最小的vue
在composition-api中主要的是下面的接口,可能还一一些生命周期的东西,我们先来实现下render和setUp

const App = {
  setup () {
    const state = reactive({
      count: 0
    })
    return { status }
  },
  render (context) {
    // 构建view
  }
}
posted @ 2021-03-29 01:09  GQiangQ  阅读(458)  评论(0)    收藏  举报