vue3源码之reactiveApi实现

请添加图片描述

1. Proxy和Reflect的用法

  • 要实现vue3的响应式之前,先要知道Proxy和Reflect的基础用法
1.1 Proxy的定义
  • Proxy对象对于创建一个对象的代理,也可以理解成为在对象前面设了一层拦截,可以实现基本操作的拦截和一些自定义操作(比如一些赋值,属性查找,函数调用等)
  • 用法: let proxy = new Proxy(target,handler)
  • target:目标对象(即将进行改造的对象)
  • handler:一些自定义操作(比如vue中的gettersetter

1.2 Reflect

  • Reflect是es6为操作对象而提供的新API,设计它的目的有:
    1. 把Object对象上一些明显属于语言内部的方法放到Reflect对象身上,比如Object.defineProperty
    2. 修改了某些object方法返回的结果;
    3. 让Object操作都变成函数行为;
    4. Reflect对象上的方法和Proxy对象上的方法一一对应,这样就可以让Proxy对象方便地调用对应的Reflect方法;
  • Reflect.get(target, propertyKey, receiver):等价于target[propertyKey], Reflect.get方法查找并返回target对象的propertyKey属性,如果没有该属性,则返回undefined。
  • Reflect.set(target, propertyKey, value, receiver)等价于target[propertyKey] = value,Reflect.set方法设置target对象的propertyKey属性等于value

1.3 Proxy和Reflect的使用

const obj = {
  name: 'win'
}
const handler = {
  get: function(target, key){
    console.log('get--', key)
    return Reflect.get(...arguments)  
  },
  set: function(target, key, value){
    console.log('set--', key, '=', value)
    return Reflect.set(...arguments)
  }
}

const data = new Proxy(obj, handler)
data.name = 'ten'
console.log(data.name,'data.name22')

2.为什么要用Proxy来重构

Proxy 之前,JavaScript 中就提供过 Object.defineProperty,允许对对象的 getter/setter 进行拦截

Vue3.0之前的双向绑定是由 defineProperty 实现, 在3.0重构为 Proxy,那么两者的区别究竟在哪里呢?

首先我们再来回顾一下它的定义

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

上面给两个词划了重点,对象上属性,我们可以理解为是针对对象上的某一个属性做处理的

语法

  • obj 要定义属性的对象
  • prop 要定义或修改的属性的名称或 Symbol
  • descriptor 要定义或修改的属性描述符
Object.defineProperty(obj, prop, descriptor)

举个例子

const obj = {}
Object.defineProperty(obj, "name", {
  value : '张三',
  writable : false, // 是否可写 
  configurable : false, // 是否可配置
  enumerable : false // 是否可枚举
})

// 上面给了三个false, 下面的相关操作就很容易理解了
obj.name = '李四' // 无效
delete obj.name // 无效
for(key in obj){
  console.log(key) // 无效 
}

2.1 Vue中的defineProperty

Vue3之前的双向绑定都是通过 defineProperty 的 getter,setter 来实现的,我们先来体验一下 getter,setter

const obj = {};
Object.defineProperty(obj, 'name', {
  set(val) {
    console.log(`开始设置新值: ${val}`)
  },
  get() { 
    console.log(`开始读取属性`)
    return '张三'; 
  },
  writable : true
})

obj.name = '李四' // 开始设置新值: 2
obj.name // 开始获取属性 

看到这里,我相信有些同学已经想到了实现双向绑定背后的流程了,其实很简单嘛,只要我们观察到对象属性的变更,再去通知更新视图就好了

2.2 对象新增属性为什么不更新

data  () {
  return  {
    obj: {
      name: '张三'
    }
  }
}

methods: {
  update () {
    this.obj.age = 18
  }
}

这个其实很好理解,我们在初始化data的时候,是在created之前,会对data绑定的一个观察者Observer,之后 data 中的字段更新都会通知依赖收集器Dep触发视图更新

然后我们回到 defineProperty 本身,是对对象上的属性做操作,而非对象本身

简单来说:就是我们Observer data时,新增的属性根本不存在,也就不会有gettersetter了,所以就解释了为什么会有$set方法来新增对象属性了

3. reactive的方法简单实现

3.1 reactive()

返回一个对象的响应式代理。

  • 我们新建一个reactive()函数
const reactiveMap = new WeakMap(); // 会自动垃圾回收,不会造成内存泄漏, 存储的key只能是对象

export function reactive(target,baseHandlers){
    // 如果目标不是对象 没法拦截了,reactive这个api只能拦截对象类型
    if( !isObject(target)){
        return target; 
    }
    // 如果某个对象已经被代理过了 就不要再次代理了  可能一个对象 被代理是深度 又被仅读代理了
    const proxyMap = reactiveMap
    const existProxy = proxyMap.get(target);
    if(existProxy){
        return existProxy; // 如果已经被代理了 直接返回即可
    }
    const proxy = new Proxy(target,mutableHandlers);
    proxyMap.set(target,proxy); // 将要代理的对象 和对应代理结果缓存起来

    return proxy;
}

  • 我们将get和set函数的方法抽离到一个对象中,mutableHandlers
    export const mutableHandlers = {
        get(target, key, receiver) { // let proxy = reactive({obj:{}})
            // proxy + reflect
            // Reflect 方法具备返回值
            const res = Reflect.get(target, key, receiver); // target[key];
           
            // 收集依赖,等会数据变化后更新对应的视图
            console.log('执行effect时会取值','收集effect')
            track()
            if(isObject(res)){ // vue2 是一上来就递归,vue3 是当取值时会进行代理 。 vue3的代理模式是懒代理
                return reactive(res)
            }
            return res;
        },
        set(target, key, value, receiver) {
         const oldValue = target[key]; // 获取老的值
         const result = Reflect.set(target, key, value, receiver); // target[key] = value
          // 当数据更新时 通知对应属性的effect重新执行  
         trigger()

        return result;
       }
  }   
  • track()trigger()是依赖收集和触发的方法
  • 可以看到vue3的代理模式是一个懒代理的方式,是在取值的时候,才进行代理,相比于vue2中data数据初始化就进行了深度递归代理,性能上会好一些

4.拓展思考

  • 对reactive我们可以看到是通过proxy来进行带来的,那shallowReactive,readonly,shallowReadonly等几个API是如何实现的了?我们可以如何在上面的基础上面来改造抽取公共的代码部分?

4.1 shallowReactive

定义:reactive() 的浅层作用形式

reactive() 不同,这里没有深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了

简单理解就是只会代理对象的第一层级,嵌套的对象就不会再被代理,那我们开始在上面的地方处理我们的逻辑,在get中处理我们的逻辑

   get(target, key, receiver) { // let proxy = reactive({obj:{}})
    const res = Reflect.get(target, key, receiver); // target[key];
    track()
    
    //我们加上这么一个判断,就表示如果是浅层的代理,那我们直接返回第一层代理结果,
    // 后面就不在深度递归遍历,那不是不是就实现了这个api了
     if(shallow){
        return res;
    }
    
    if(isObject(res)){ 
        return reactive(res)
    }
    return res;
},

4.2 readonly

定义:接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理

表示那我们的对象就不是响应式的,那我们只要传入一个参数isReadonly判断是否是只读的就可以实现,那我们还是先改造get

      get(target, key, receiver) { // let proxy = reactive({obj:{}})
        const res = Reflect.get(target, key, receiver); // target[key];
        //表示如果是不是只读的,我们才收集其依赖,如果是只读的那我们就不收集依赖
         if(!isReadonly){
            track(target,TrackOpTypes.GET,key)
        }
        
        //我们加上这么一个判断,就表示如果是浅层的代理,那我们直接返回第一层代理结果,
        // 后面就不在深度递归遍历,那不是不是就实现了这个api了
         if(shallow){
            return res;
        }
        
        if(isObject(res)){ 
            return reactive(res)
        }
        return res;
    },

对readonly的set单独提出来
set: (target, key) => {
console.warn(set on key ${key} falied)
}

 set: (target, key) => {
      console.warn(`set on key ${key} falied`)
  }

4.3 总结

其实说了这么多,发现我们对其API里面的很多逻辑都是通用的,都是都只是进行了传参的处理,那接下来贴出来完整代理,利用了函数柯里化的思想,来进行对传参的处理

reactive.ts

let mutableHandlers = {}
let shallowReactiveHandlers ={}
let readonlyHandlers = {}
let shallowReadonlyHandlers = {}

export function reactive(target){
    return createReactiveObject(target,false,mutableHandlers)
}

export function shallowReactive(target){
   return createReactiveObject(target,false,shallowReactiveHandlers)
}

export function readonly(target){
   return createReactiveObject(target,true,readonlyHandlers)
}

export function shallowReadonly(target){
   return createReactiveObject(target,true,shallowReadonlyHandlers)
}

// 1.三个参数 第一个是代理的数据 第二个判断是否只读,第三个判断不同的代理
const reactiveMap = new WeakMap() //会自动回收,不会造成内存泄露,存储的key只能是对象
const readonlyMap = new WeakMap()
export function createReactiveObject(target,isReadonly,baseHandlers){
    //如果目标不是对象 没法拦截,reactive这个api只能拦截对象
    if(!isObject(target)){
      return target
    }
    //优化点 如果某个对象已经被代理过了,就不要再次代理了 所以需要缓存下来
    //思考点 可能一个对象 被代理时深度 又被仅读代理了
    const proxyMap = isReadonly ? readonlyMap : reactiveMap
    const existProxy = proxyMap.get(target)
    //如果已经被代理过了  直接返回即可
    if(existProxy){
      return existProxy
    }
    const proxy = new Proxy(target,baseHandlers)
    proxyMap.set(target,proxy)
    return proxy
 }

baseHandler.ts

  • 新建baseHanlders.ts文件,编写代理方法
export mutableHandlers ={
  get:()=>{},
  set:()=>{}
}
export shallowReactiveHandlers = {
  get:()=>{},
  set:()=>{}
}
export readonlyHandlers = {
  get:()=>{},
  set:()=>{}
}
export shallowReadonlyHandlers = {
  get:()=>{},
  set:()=>{}
}
  • 想到都是get和set方法,想到创建一个公共createGetter函数,用传参的方式的来区分
//是不是仅读的,仅读的属性set时会包异常
//是不是深度的
function  createGetter(isReadonly=false,shallow=false){
   return function get(target,key,recevier){
     // 使用 proxy 和 reflect
     const res = Reflect.get(target,key,recevier) // 等同于 target[key]

     if(!isReadonly){
       //如果不是只读的,那么就会收集依赖,等数据变化后更新视图
       console.log("执行effect时会取值","收集effect")
     }
     if(shallow){
       return res
     }
     if(isObject){  //vue2是上来就递归,vue3是当取值的会进行代理。vue3是懒代理
       return isReadonly ? readonly(res):reactive(res)
     }

     return res
   }
}
// 暂不处理set逻辑
function createSetter(shallow = false){
  return function set(target,key,value,receiver){
      const result = Reflect.set(target, key, value, receiver); // target[key] = value
      return result
  }
}
  • 再来定义不同handlers中的不同的set方法
const get = createGetter()
const shallowGet = createGetter(false,true)
const readonlyGet = createGetter(true,false)
const shallowReadonlyGetter = createGetter(true,true)
const set = createSetter();
const shallowSet = createSetter(true);

export const mutableHandlers = {
    get,
    set
}
export const shallowReactiveHandlers = {
    get: shallowGet,
    set: shallowSet
}

//如果是只读的,那么set会报警告
export const readonlyHandlers = {
	get:readonlyGet,
  set: (target, key) => {
      console.warn(`set on key ${key} falied`)
  }
}

export const shallowReadonlyHandlers = {
	get:showllowReadonlyGet,
  set: (target, key) => {
      console.warn(`set on key ${key} falied`)
  }
}

结语

看到这里我们大概清楚 reactive 是做为整个响应式的入口,负责处理目标对象是否可观察以及是否已被观察的逻辑,最后使用 Proxy 进行目标对象的代理,对 es6 Proxy 概念清楚的同学应该 Proxy 重点的逻辑处理在 Handlers

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值