• Vue3 中有场景是 reactive 能做而 ref 做不了的吗?


    前言

    如果你使用过 Vue3,你知道的,在 Vue3 中有两个非常常用的响应式 API:reactive 和 ref。它们会把我们想要追踪的数据变成响应式。

    而且我们在使用时一直被告知 ref 用于创建基础类型的响应式,也可以创建引用类型的响应式。而对于引用类型,底层也是转换为 reactive 来进行响应式处理。那既然这样为撒还需要 reactive ,全部使用 ref 不就行了吗?

    虽然 ref 创建的响应式数据在脚本中需要通过 .value 才能访问到呀!但是这里肯定影响不大。并且在模板中会自动添加上 .value,所以模板中不需要通过 .value 访问。

    既然这二者基本没差别,但是还是暴露了 reactive 这个 API,难道有什么场景是 reactive 能做而 ref 做不了的?

    image.png

    简单了解 ref & reactive

    我们先简单了解一下这两个 API。

    reactive

    返回对象的响应式副本,响应式转换是“深层”的——它影响所有嵌套 property。我们一般这样写。

    const obj = reactive({ count0 })
    

    并且可以直接使用。

    const count = obj.count
    

    ref

    接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个  .value property,指向该内部值。我们一般是这样写。

    const data = ref(xxx)
    

    引用的时候,一般会通过data.value的方式引用。

    const dataValue = data.value
    

    通过跟踪 Vue3 的源代码可以证明,当我们调用 ref 方法来定义响应式数据时,当参数为对象类型时,其实里面用的是 reactive 方法。也就是说上面的 data.value ,事实上是 reactive 方法创造出来的。

    reactive 能做的 ref 也能做,并且还是用 reactive 做的

    我们通过源码来看看 ref 的源码实现。

    源码分析版本:3.2.36

    1. function ref(value) {
    2.     return createRef(value, false);
    3. }

    ref 函数跳转到 createRef 函数。

    1. function createRef(rawValue, shallow) {
    2.     ...
    3.     return new RefImpl(rawValue, shallow);
    4. }

    createRef 函数返回的是 RefImpl 类的实例,换句话说,ref 创建出来的响应式就是 RefImpl 实例对象。

    1. const count = ref(1);
    2. console.log(count);

    image.png

    我们重点来看看这个 RefImpl 类。

    1. class RefImpl {
    2.     constructor(value, __v_isShallow) {
    3.         ...
    4.         this._value = __v_isShallow ? value : toReactive(value);
    5.     }
    6.     get value() {
    7.         trackRefValue(this);
    8.         return this._value;
    9.     }
    10.     set value(newVal) {
    11.         newVal = this.__v_isShallow ? newVal : toRaw(newVal);
    12.         if (hasChanged(newVal, this._rawValue)) {
    13.             this._rawValue = newVal;
    14.             this._value = this.__v_isShallow ? newVal : toReactive(newVal);
    15.             triggerRefValue(this, newVal);
    16.         }
    17.     }
    18. }

    __v_isShallow 参数在这里默认是 false,这里也顺带讲一嘴,当我们在使用 shallowRef 时,这个参数为 true。

    1. function shallowRef(value) {
    2.     return createRef(value, true);
    3. }

    Ref 与 Reactive 创建的都是递归响应的,将每一层的 json 数据解析成一个 proxy 对象,shallowRef 与 shallowReactive 创建的是非递归的响应对象,shallowReactive 创建的数据第一层数据改变会重新渲染 dom。

    1.  var state = shallowReactive({
    2.     a:'a',
    3.     gf:{
    4.        b:'b',
    5.        f:{
    6.           c:'c',
    7.           s:{d:'d'}
    8.        }
    9.     }
    10.  });
    11. // 改变第一层的数据会导致页面重新渲染
    12. state.a = '1'
    13. // 如果不改变第一层,只改变其他的数据页面不会重新渲染
    14. state.gf.b = 2

    通过 shallowRef 创建的响应式对象,需要修改整个 value 才能重新渲染 dom。

    1. var state = shallowRef({
    2.    a:'a',
    3.     gf:{
    4.        b:'b',
    5.        f:{
    6.           c:'c',
    7.           s:{d:'d'}
    8.        }
    9.     }
    10. });
    11. // 不会重新渲染
    12. state.value.a = 1
    13. // 要修改整个 value 才能重新渲染
    14. state.value = {
    15.     a:'1',
    16.     gf:{
    17.        b:'2',
    18.        f:{
    19.           c:'3',
    20.           s:{d:'d'}
    21.        }
    22.     }
    23. }

    如果想更新 shallowRef 的某一层数据,并且想触发渲染,可以使用 triggerRef。

    1. var state = shallowRef({
    2.    a:'a',
    3.     gf:{
    4.        b:'b',
    5.        f:{
    6.           c:'c',
    7.           s:{d:'d'}
    8.        }
    9.     }
    10. })
    11. state.value.gf.f.s.d = 4
    12. triggerRef(state)

    所以这里会走到 toReactive(value) 函数。

    1. const isObject = (val) => val !== null && typeof val === 'object';
    2. const toReactive = (value) => isObject(value) ? reactive(value) : value;

    可以看到,如果传入的参数是一个对象的话,返回值将会继续调用 reactive 方法来进行包裹,reactive 最终会通过 Proxy 来进行实现响应拦截,返回的也是一个 Proxy 对象,但在这里不重要,我们只需要知道当 ref 的参数为对象时,用的就是 reactive 方法。

    1. const data = reactive({
    2.   count: 1,
    3. });
    4. console.log(data);
    5. const data_ref = ref({
    6.   count: 1,
    7. });
    8. console.log(data_ref);

    结果显然,让对 ref 传入对象作为参数时和传入基本类型作为参数返回结果情况是不一样的。

    基本类型返回值value就是具体的值,对象类型返回值value是 reactive 方法创建的 proxy 对象。

    通过源码来看,其实也证明了,在 Vue3 中,如果是把对象类型的数据弄成响应式,reactive 和 ref 都可以,且 ref 内部是通过 r eactive 来支持的。

    也就是说,你 reactive 能做的,我 ref 也能做。

    ref 能做,但是 reactive 不能做

    其实通过上面的例子就能知道有什么是 reactive 不能做的呢?很明显,reactive 不支持对基本类型数据响应式,也就是说基本类型数据不能直接作为 reactive 的参数来使用。

    简单看看源码。

    1. function reactive(target) {
    2.     ...
    3.     return createReactiveObject(...);
    4. }

    reactive 函数跳转到 createReactiveObject 函数。

    1. const isObject = (val) => val !== null && typeof val === 'object';
    2. function createReactiveObject(...) {
    3.     if (!isObject(target)) {
    4.         {
    5.             console.warn(`value cannot be made reactive: ${String(target)}`);
    6.         }
    7.         return target;
    8.     }
    9.     ...
    10.     const proxy = new Proxy(...);
    11.     proxyMap.set(target, proxy);
    12.     return proxy;
    13. }

    createReactiveObject 一开始就会判断 target 是否是对象,如果不是对象就会直接 ⚠️ 提示返回。如果是对象就会把 target 用 Proxy 变成响应式对象。

    const data = reactive(10);
    

    image.png

    总结

    我们通过源码来分析了两个响应式 API,发现 Vue3 中有没有 reactive 能做而 ref 做不了的场景?

    结论是:没有

    简单来说 ref 是在 reactive 上在进行了封装进行了增强,所以在 Vue3 中 reactive 能做的,ref 也能做,reactive 不能做的,ref 也能做。

  • 相关阅读:
    Vue3学习(二十二)- 保存文档内容
    分布式系分发展概览
    B站视频“多模态大模型,科大讯飞前NLP专家串讲”记录
    (02)Cartographer源码无死角解析-(10) 配置文件加载
    Leetcode 2336. Smallest Number in Infinite Set [Python]
    完美解决k8s master节点无法ping node节点中的IP或Service NodePort的IP
    NLP工具集:【doccano】——标注平台doccano使用手册
    MySQL 自动根据年份动态创建范围分区
    TCP 滑动窗口
    什么是 vuejs 加载器?
  • 原文地址:https://blog.csdn.net/hebiwen95/article/details/126268658