第二章 手写reactive函数

目录

1.创建文件

2.实现reactive

3.实现createReactiveObject

4.实现get set方法中的返回值


1.创建文件

在reactivity文件夹中创建reactive.ts文件,作为reactive的入口

在index.ts中将其统一导出

2.实现reactive

在reactive.ts中导出一个函数reactive,该函数接收一个对象作为参数,返回一个proxy代理对象

具体代码如下

 export function reactive(target) {

    return  createReactiveObject(target) // 实现响应式函数

  }

3.实现createReactiveObject

1. 判断参数是否为对象,如果不是则直接返回

function createReactiveObject(target) { 
// 1.判断传入的值是否是对象,如果是对象正经行代理,如果不是直接返回

  // isObject函数时当时在shared创建的一个判断是否是对象的函数,当时用于多个依赖包之间共享打包,在这里直接可以用于判断是否是对象

      if(!isObject(target)) {

          return target

      }
}

2. 创建一个代理对象并返回

function createReactiveObject(target) { 
// 1.判断传入的值是否是对象,如果是对象正经行代理,如果不是直接返回

  // isObject函数时当时在shared创建的一个判断是否是对象的函数,当时用于多个依赖包之间共享打包,在这里直接可以用于判断是否是对象

      if(!isObject(target)) {

          return target

      }

    let proxy =  new Proxy(target,mutableHandlers)
    return proxy;
}

// 这里的代理对象的操作配置属性对象我们使用一个变量来存储
// 代理操作
const mutableHandlers:  ProxyHandler<any> = {
    get(target,key,recevier) { },
    set(target,key,value,recevier) {
        return true
    }
}

这样一个基本的响应式变量就完成了。但是我们发现当两次同时传入同一个对象进行代理时,我们完全没有必要每次都在重新创建一个proxy,所以我们维护一个WeakMap,来存放每一个对象对应的proxy。

当两次需要代理的对象相同时,我们直接从WeakMap中获取proxy即可。

3. 实现WeakMap缓存

  // 记录已经代理过的对象,避免重复代理

  // 这里为了防止循环引用,所以用弱引用

  const reactiveMap = new WeakMap()

在实现代理的函数中判断是否已经存在当前代理过的对象,如果有,直接将其返回。

function createReactiveObject(target) {

    ...
      // 判断当前是否已经代理过,如果已经代理过直接返回
    if(reactiveMap.has(target)) {
        return reactiveMap.get(target)
    }
    ...
}

在这里我们发现,当一个对象被代理之后,又重新将这个已经是响应式对象的对象再次传入,我们就不需要对其做任何操作,直接返回即可。

4. 判断target是否已经是响应式对象

这里用到了一个很巧妙的操作,我们在每次代理对象之前,从对象上读取一个标记属性,然后在每个代理对象的get方法中,专门判断这个标记属性

如果get方法判断到了这个属性,就说明这个对象在之前已经代理过,因此我们设置的get方法才能生效,否则就说明这个对象还没有被代理过,我们就可以进行代理操作。

  enum  ReactiveFlags {

    IS_REACTIVE = '__v_isReactive', // 已经代理过

  }
function createReactiveObject(target) {

    ...
  // 判断当前传入的值是否是一个响应式对象,如果是则直接返回

  // 这里的判断方式是通过获取响应式对象的属性,然后再get方法中特定的返回,如果有返回,则代表其已经被代理了get方法

  if(target[ReactiveFlags.IS_REACTIVE]) {

    return target

  }
    ....
}

const mutableHandlers:  ProxyHandler<any> = {
    // proxy get方法

  get(target,key,recevier) {

        // 判断当前是否已经是响应式对象

        if(key === ReactiveFlags.IS_REACTIVE) {

            return true

        }

    },
    set(target,key,value,recevier) {
        return true
    }
}

4.实现get set方法中的返回值

- 这里我们现将上面所写的枚举ReactiveFlags,对象mutableHandlers移入一个新文件中,命名为baseHandler.ts

在将其导出,在reactive.ts中导入,实现分模块

- 这里我们需要在get和set方法中返回读取的数据(主要是在get方法中),但是我们却不能直接使用target[key]来读取数据,这主要涉及到一个问题,我们来看下面一个例子:

  const user = {

    name: "zhangsan",

    get desc() {

      return "我叫" + this.name

    }

  }
  // 这里我们读取user.desc时,就会间接的读取this.name

  let proxy = new Proxy(user,{

    get(target,key,recevier) {

      return target[key]

    },

    set(target,key,value) {}

  })

我们来看上面的这个代码 在读取proxy.desc时,就会触发get方法,而desc间接依赖于name,在desc的逻辑当中,此时还是会读取this.name,而这个this还是user

这样一来就会出现一个问题,既然desc依赖于name,那么读取desc时就必须要读取name,这样才能将desc收集到name的effect,从而在未来name发生变化时,desc也会跟着变化

因此这样的方法可能会导致某些依赖我们无法收集到。那么使用recevier[key]行不行呢?我们知道recevier就是代理后的proxy对象,我们直接从代理后的对象上读取属性不是就可以触发name的get方法从而收集到依赖了

但是这里也有一个致命的问题,就是当我们在get内部去再去读取proxy的属性是,就会再次触发get方法,这样就会导致get一直被循环触发,最终导致请程序崩溃。

为了解决这一个问题,我们需要引入另一个API,Reflect :这个api就是专门用来解决循环读取属性的,它不会触发get方法,而是直接从proxy对象中读取属性。

    get(target,key,recevier) {

        // 判断当前是否已经是响应式对象

        if(key === ReactiveFlags.IS_REACTIVE) {

            return true

        }

        // 取值时,需要将响应式属性与effect进行绑定

        return Reflect.get(target,key,recevier)

      },

      set(target,key,value,recevier) {

          // 找到属性,然对应的effect触发执行

          return Reflect.set(target,key,value,recevier)

      },

这个api不会循环触发get,set方法,而是直接读取设置属性,这样就可以解决循环读取的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值