【mini-vue】- 实现ref

49 阅读2分钟

实现ref

主要思路

ref主要是在reactive的基础上新增加了对基本数据类的响应式,因此对于传入进来的基本数据类型的数据,可以返回一个被RefImpl类型包裹的实例对象出去。 然后对这个对象进行get 和 set 拦截操作,实现响应式。

代码

先实现一个RefImpl的类

对传入的value 进行 get 收集依赖, set触发依赖

import { hasChanged, isObject } from "../shared"
import { trackEffects, triggerEffects } from "./effect"
import { reactive } from "./reactive"

/**
 * ref 接受 1 true “1” 这样的基本数据类的值
 * 但是proxy只能代理对象,于是对于传入的值将其改造成一个RefImpl的实例对象
 * 通过对这个对象进行get 和 set 操作进行拦截,达到的响应的目的
 */
class RefImpl {
  private _value: any
  private dep: any
  private _rawValue: any
  constructor(value) {
    this._rawValue = value // 存储开始的值
    this._value = convert(value) // 如果不是响应式的对象需要转换成响应式的,如果只是普通数据类型的数据那么不管
    this.dep = new Set()
  }

  get value() {
    // 收集依赖
    trackEffects(this.dep)
    return this._value
  }

  set value(newValue) {
    // 触发依赖
    // 这里比较的是数据是 一个是proxy 一个是原始值 ( 受限于 Object.is() ),所以需要转换
    if (hasChanged(newValue, this._rawValue)) { // 判断当前值和原来的值是否相等 如果相等则不触发更新
      this._rawValue = newValue // 原始值也需要更新,因为下次新旧值对比的时候,拿的是_rawValue进行对比的
      this._value = convert(newValue)  // 注意需要修改value之后再去通知effect执行 
      triggerEffects(this.dep)
    }  
    
  }
}
export function convert(value) {
  return isObject(value) ? reactive(value) : value
}
export function ref(value) {
  return new RefImpl(value)
}

一些细节

ref 对于 传入的两次相同的值来说,最后一次是不会触发依赖的 所以需要比较新传入的值和当前的值是否相同,如果不相同再去触发依赖, 但由于Object.is 函数只能比较两个普通对象是否相等,所以在一开始我们需要设置一个变量_rawValue 去保存 初始值。

单元测试

import { effect } from '../effect'
import { ref } from '../ref'
describe("ref", () => {
  it("happy path", () => {
    const a = ref(1)
    expect(a.value).toBe(1)
  })

  it("should be reactive", () => {
    const a = ref(1)
    let dummy
    let calls = 0
    effect(() => {
      calls++
      dummy = a.value
    })
    expect(calls).toBe(1)
    expect(dummy).toBe(1)
    // 触发 a 的 set 函数,
    a.value = 2
    expect(calls).toBe(2)
    expect(dummy).toBe(2)

    // same value should not trigger 
    a.value = 2
    expect(calls).toBe(2)
    expect(dummy).toBe(2)
  })

  it("should make nested properties reactive", () => {
    const a = ref({
      count: 1,
    })
    let dummy
    effect(() => {
      dummy = a.value.count
    })
    expect(dummy).toBe(1)
    // ref 复杂数据类型 触发 set 操作
    a.value.count = 2 
    expect(dummy).toBe(2)
  })
})