Vue3.0 常用响应式API的使用和原理分析(二)

1,147 阅读5分钟
Vue 3.0 系列文章

Vue 3.0组件的渲染流程

Vue 3.0组件的更新流程和diff算法详解

揭开Vue3.0 setup函数的神秘面纱

Vue 3.0 Props的初始化和更新流程的细节分析

Vue3.0 响应式实现原理分析

Vue 3.0 计算属性的实现原理分析

Vue3.0 常用响应式API的使用和原理分析(一)

Vue3.0 常用响应式API的使用和原理分析(二)

Vue 3.0 Provide和Inject实现共享数据

Vue 3.0 Teleport的使用和原理分析

Vue3侦听器和异步任务调度, 其中有个神秘角色

Vue3.0 指令

Vue3.0 内置指令的底层细节分析

Vue3.0 的事件绑定的实现逻辑是什么

Vue3.0 的双向绑定是如何实现的

Vue3.0的插槽是如何实现的?

探究Vue3.0的keep-alive和动态组件的实现逻辑

Vuex 4.x

Vue Router 4 的使用,一篇文章给你讲透彻

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