第二章 手写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方法,而是直接读取设置属性,这样就可以解决循环读取的问题。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中的代码演示了使用Vue的reactive函数来创建响应式数据。在代码中,使用reactive函数创建了两个响应式对象:account和emp。account是一个普通的原始类型变量,而emp是一个包含name和salary属性的对象。通过reactive函数的包装,这些变量和属性都具有了响应性,可以在模板中进行双向数据绑定和响应式更新。 引用展示了另一个示例,使用reactive函数将一个对象包装成响应式数据。在这个示例中,使用reactive函数创建了一个job对象,包含type和salary属性。通过改变job对象的属性值,可以实现对应模板中的数据更新。 总结来说,reactive函数用于将对象或数组包装成具有响应性的代理对象。这样,在模板中使用这些变量时,可以自动感知其变化并进行更新。通过reactive函数,可以简化开发者对数据的管理和更新操作,提高开发效率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [reactive() 函数](https://blog.csdn.net/www61621/article/details/128976430)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【vue3.2】reactive函数](https://blog.csdn.net/sakenc/article/details/126192715)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值