reactive对传入的类型是有限制的,必须是对象或者数组。对一些基础类型,例如string, numberboolean等不支持,如果要使用reactiveAPI必须将这些基础类型封装成对象,这样显然是不太科学的。因此Vue 3.0提供了refAPI

Ref是一个接口, 它最主要的是有一个value属性可以获取值和赋值。

export interface Ref<T = any> {
  value: T
  _shallow?: boolean
}

ref

使用场景

将数据变为响应式数据

ref使用

代码解释:

  1. 通过ref将字符串变为了一个响应式对象person
  2. 通过person.valueperson进行新值的设置,也是通过person.value获取响应式对象person的值。
实现原理
  • createRef传入的参数如果已经是ref对象,就直接返回;如果不是就利用RefImpl进行封装。

createRef

  • RefImpl有两个私有变量_value_rawValue, _rawValue是原始值,_value是操作的值。
    • 如果value值是原始数据,_value_rawValue都等于value
    • 如果value值是数组或者对象,_value被转换成了reactive响应式对象,_rawValue就是响应式对象的原始对象;
    • get函数先收集依赖,然后返回_value是操作的值;
    • set函数先比对原始值有没有变化,如果变化了就设置_value_rawValue,然后分发依赖。

RefImpl

refreactive相关的一些疑问?

问题1:基础类型数据变为响应式对象refAPI对象或者数组变为响应式对象用reactiveAPI

答案1:一般是这样使用的,但是ref也是可以将对象或者数组变为响应式对象的,因为其内部实现机制也是基于reactive

问题2:既然ref包含了reactive的功能,为什么不只提供refAPI就一切都搞定了。

答案2:ref的一个特点是提供了set方法, 可以将整个原值数据value完全替换掉,类似于let,而reactive是不能这样操作的,只能对原值数据value的属性进行修改, 类似于const的限制。

ref 类似于 let, reactive 类似于 const,他们的作用场景不一样。

shallowRef

使用场景

只需要监测对象的替换,不需要监测对象的属性修改。原始数据类型shallowRefref的效果没有差别。

const p1 = {name: "hehe"};
const p2 = {name: "xixi"};

// 响应式数据
const person = shallowRef(p1);

person.value.name = "haha"; // 不会监测到数据变化
person.value = p2;          // 会监测到数据变化

reactive不存在替换对象的情况,所以shallowReactive是能监测到外部属性的变化,不能监测到内部属性的变化。

实现原理
  • shallowRef不会将对象转换成reactive对象,只有value值变化后才会分发依赖。

shallowRef

你可能会好奇,person.value.name = "haha"设置新值后hasChanged(newVal, this._rawValue)不是应该true分发依赖吗?

其实person.value.name = "haha"这里调用的是get方法,调用的是get方法,调用的是get方法。和set方法没有关系哦~~~

什么时候调用set方法?当然是person.value = p2;这个方法啦。 希望没有被绕晕啊~~~

isRef

使用场景

判断一个对象是否是ref对象

实现原理
  • isRef很简单,就是判断__v_isRef是否为true。因为RefImpl__v_isRef就是true

isRef

unref

使用场景

获取ref对象的_value值,有可能是reactive对象(因为不是获取_rawValue的值)。

实现原理

unref

toRef

使用场景

reactive响应式对象的某个属性创建一个ref对象,方便赋值和取值。

const zhangshanfeng = reactive({
  name: '张三丰',
  age: 100,
  child: {
    name: '张翠山',
    age: 40,
    child: {
      name: '张无忌',
      age: 20
    }
  }
})

const wuji = toRef(zhangshanfeng.child, 'child'); // 获得张无忌的ref对象

wuji.value.age += 10;  // 修改张无忌的年龄

这个API的主要功能是当只需要操作响应式数据的部分数据时,将部分数据提取成为一个ref对象,然后方便操作。例子中如果要操作张无忌的年龄得使用zhangshanfeng.child.child.age += 10,比较繁琐。

这个接口也比较适合网络请求的返回值的处理,可能在某些请求中只有一部分数据是需要展示的,这部分提取出来处理就行了。

实现原理
  • ObjectRefImpl处理objectkey;

ObjectRefImpl

  • get就是取objectkey属性的值, set就是设置objectkey属性的值。由于object响应式对象,所以其实调用的就是响应式对象getset方法。

ObjectRefImpl

toRefs

使用场景

reactive响应式对象的每个属性创建一个ref对象,方便赋值和取值。

const zhangshanfeng = reactive({
  name: '张三丰',
  age: 100,
  child: {
    name: '张翠山',
    age: 40,
    child: {
      name: '张无忌',
      age: 20
    }
  }
})

const refs = toRefs(zhangshanfeng);  // 获得张三丰的所以属性的refs。

// 结果
{
    name: <ObjectRefImpl>{_object: zhangsanfeng, key: "name"},
    age: <ObjectRefImpl>{_object: zhangsanfeng, key: "age"},
    child: <ObjectRefImpl{_object: zhangsanfeng, key: "child"},
}
实现原理
  • 就是对每个属性分别执行toRef调用

toRefs

customRef

自定义一个ref对象,实现自己的功能。

下面官方给的一个防抖的例子:get方法就是返回值,set方法是延迟200毫秒才设置值,在这200毫秒如果设置了新值,就重新计时200毫秒再赋值。

function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}
实现原理
  • customRef的参数tracktrigger分别是() => trackRefValue(this)() => triggerRefValue(this),可以收集依赖和分发依赖,customRef持有返回的对象的getset方法,这两个方法就是真正执行的赋值和取值的方法。

CustomRefImpl

Logo

前往低代码交流专区

更多推荐