提升Vue3应用效率的秘诀:深入比较ref与reactive!

news2024/12/27 16:20:19

refreactive 是 Vue3 中实现响应式数据的核心 API。ref 用于包装基本数据类型,而 reactive 用于处理对象和数组。尽管 reactive 似乎更适合处理对象,但 Vue3 官方文档更推荐使用 ref

在这里插入图片描述

我的想法,ref就是比reactive好用,官方也是这么说的,不服来踩!下面我们从源码的角度详细讨论这两个 API,以及 Vue3 为什么推荐使用ref而不是reactive

ref 的内部工作原理

ref 是一个函数,它接受一个内部值并返回一个响应式且可变的引用对象。这个引用对象有一个 .value 属性,该属性指向内部值。

// 深响应式
export function ref(value?: unknown) {
  return createRef(value, false)
}

// 浅响应式
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

function createRef(rawValue: unknown, shallow: boolean) {
  // 如果传入的值已经是一个 ref,则直接返回它
  if (isRef(rawValue)) {
    return rawValue
  }
  // 否则,创建一个新的 RefImpl 实例
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  // 存储响应式的值。我们追踪和更新的就是_value。(这个是重点)
  private _value: T
  // 用于存储原始值,即未经任何响应式处理的值。(用于对比的,这块的内容可以不看)
  private _rawValue: T 

  // 用于依赖跟踪的 Dep 类实例
  public dep?: Dep = undefined
  // 一个标记,表示这是一个 ref 实例
  public readonly __v_isRef = true

  constructor(
    value: T,
    public readonly __v_isShallow: boolean,
  ) {
    // 如果是浅响应式,直接使用原始值,否则转换为非响应式原始值
    this._rawValue = __v_isShallow ? value : toRaw(value)
    // 如果是浅响应式,直接使用原始值,否则转换为响应式值
    this._value = __v_isShallow ? value : toReactive(value)
    
    // toRaw 用于将响应式引用转换回原始值
    // toReactive 函数用于将传入的值转换为响应式对象。对于基本数据类型,toReactive 直接返回原始值。
    // 对于对象和数组,toReactive 内部会调用 reactive 来创建一个响应式代理。
    // 因此,对于 ref 来说,基本数据类型的值会被 RefImpl 直接包装,而对象和数组
    // 会被 reactive 转换为响应式代理,最后也会被 RefImpl 包装。
    // 这样,无论是哪种类型的数据,ref 都可以提供响应式的 value 属性,
    // 使得数据变化可以被 Vue 正确追踪和更新。
    // export const toReactive = (value) => isObject(value) ? reactive(value) : value
  }

  get value() {
    // 追踪依赖,这样当 ref 的值发生变化时,依赖这个 ref 的组件或副作用函数可以重新运行。
    trackRefValue(this)
    // 返回存储的响应式值
    return this._value
  }

  set value(newVal) {
    // 判断是否应该使用新值的直接形式(浅响应式或只读)
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    // 如果需要,将新值转换为非响应式原始值
    newVal = useDirectValue ? newVal : toRaw(newVal)
    // 如果新值与旧值不同,更新 _rawValue 和 _value
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      // 触发依赖更新
      triggerRefValue(this, DirtyLevels.Dirty, newVal)
    }
  }
}

在上述代码中,ref 函数通过 new RefImpl(value) 创建了一个新的 RefImpl 实例。这个实例包含 getter 和 setter,分别用于追踪依赖和触发更新。使用 ref 可以声明任何数据类型的响应式状态,包括对象和数组。

import { ref } from 'vue' 

let state = ref({ count: 0 })
state.value.count++

注意,ref核心是返回响应式且可变的引用对象,而reactive核心是返回的是响应式代理,这是两者本质上的核心区别,也就导致了ref优于reactive,我们接着看下reactive源码实现。

reactive 的内部工作原理

reactive 是一个函数,它接受一个对象并返回该对象的响应式代理,也就是 Proxy

function reactive(target) {
  if (target && target.__v_isReactive) {
    return target
  }

  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

function createReactiveObject(
  target,
  isReadonly,
  baseHandlers,
  collectionHandlers,
  proxyMap
) {
  if (!isObject(target)) {
    return target
  }
  
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  const proxy = new Proxy(target, baseHandlers)
  proxyMap.set(target, proxy)
  return proxy
}

reactive的源码相对就简单多了,reactive 通过 new Proxy(target, baseHandlers) 创建了一个代理。这个代理会拦截对目标对象的操作,从而实现响应式。

import { reactive } from 'vue' 

let state = reactive({ count: 0 })
state.count++

到这里我们可以看出 refreactive 在声明数据的响应式状态上,底层原理是不一样的。ref 采用 RefImpl对象实例,reactive采用Proxy代理对象。

ref 更深入的理解

当你使用 new RefImpl(value) 创建一个 RefImpl 实例时,这个实例大致上会包含以下几部分:

  1. 内部值:实例存储了传递给构造函数的初始值。
  2. 依赖收集:实例需要跟踪所有依赖于它的效果(effect),例如计算属性或者副作用函数。这通常通过一个依赖列表或者集合来实现。
  3. 触发更新:当实例的值发生变化时,它需要通知所有依赖于它的效果,以便它们可以重新计算或执行。

RefImpl 类似于发布-订阅模式的设计,以下是一个简化的 RefImpl 类的伪代码实现,展示这个实现过程:

class Dep {
  constructor() {
    this.subscribers = new Set();
  }

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach(effect => effect());
  }
}

let activeEffect = null;

function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}

class RefImpl {
  constructor(value) {
    this._value = value;
    this.dep = new Dep();
  }

  get value() {
    // 当获取值时,进行依赖收集
    this.dep.depend();
    return this._value;
  }

  set value(newValue) {
    if (newValue !== this._value) {
      this._value = newValue;
      // 值改变时,触发更新
      this.dep.notify();
    }
  }
}

// 使用示例
let count = new RefImpl(0);

watchEffect(() => {
  console.log(`The count is: ${count.value}`); // 订阅变化
});

count.value++; // 修改值,触发通知,重新执行watchEffect中的函数

Dep 类负责管理一个依赖列表,并提供依赖收集和通知更新的功能。RefImpl 类包含一个内部值 _value 和一个 Dep 实例。当 value 被访问时,通过 get 方法进行依赖收集;当 value 被赋予新值时,通过 set 方法触发更新。

refreactive 尽管两者在内部实现上有所不同,但它们都能满足我们对于声明响应式变量的要求,但是 reactive 却存在一定的局限性。

reactive 的局限性

在 Vue3 中,reactive API 通过 Proxy 实现了一种响应式数据的方法,尽管这种方法在性能上比 Vue2 有所提升,但 Proxy 的局限性也导致了 reactive 的局限性,这些局限性可能会影响开发者的使用体验。

仅对引用数据类型有效

reactive 主要适用于对象,包括数组和一些集合类型(如 MapSet)。对于基础数据类型(如 stringnumberboolean),reactive 是无效的。这意味着如果你尝试使用 reactive 来处理这些基础数据类型,将会得到一个非响应式的对象。

import { reactive } from 'vue';
const state = reactive({ count: 0 });

使用不当会失去响应

  1. 直接赋值对象:如果直接将一个响应式对象赋值给另一个变量,将会失去响应性。这是因为 reactive 返回的是对象本身,而不仅仅是代理。

    import { reactive } from 'vue';
    
    let state = reactive({ count: 0 });
    state = { count: 1 }; // 失去响应性
    
  2. 直接替换响应式对象:同样,直接替换一个响应式对象也会导致失去响应性。

    import { reactive } from 'vue';
    
    let state = reactive({ count: 0 });
    state = reactive({ count: 1 }); // 失去响应性
    
  3. 直接解构对象:在解构响应式对象时,如果直接解构对象属性,将会得到一个非响应式的变量。

    const state = reactive({ count: 0 });
    
    let { count } = state;
    count++; // count 仍然是 0
    

    好家伙!常用的解构赋值不能用。为了解决这个问题,需要使用 toRefs 函数来将响应式对象转换为 ref 对象。

    import { toRefs } from 'vue';
    
    const state = reactive({ count: 0 });
    let { count } = toRefs(state);
    count++; // count 现在是 1
    

    首先来说,太不方便了!而且使用toRefs(),将响应式变量换成 ref 的形式,那我还不如直接使用ref()了,大家说是不是?

  4. 将响应式对象的属性赋值给变量:如果将响应式对象的属性赋值给一个变量,这个变量的值将不会是响应式的。

    let state = reactive({ count: 0 })
    
    let count = state.count
    count++  // count 仍然是 0
    

使用 reactive 声明响应式变量的确存在一些不便之处,尤其是对于喜欢使用解构赋值的开发者而言。这些局限性可能会导致意外的行为,因此在使用 reactive 时需要格外注意。相比之下,ref API 提供了一种更灵活和统一的方式来处理响应式数据。

为什么推荐使用 ref ?

ref()它为响应式编程提供了一种统一的解决方案,适用于所有类型的数据,包括基本数据类型和复杂对象。以下是推荐使用 ref 的几个关键原因:

统一性

ref 的核心优势之一是它的统一性。它提供了一种简单、一致的方式来处理所有类型的数据,无论是数字、字符串、对象还是数组。这种统一性极大地简化了开发者的代码,减少了在不同数据类型之间切换时的复杂性。

import { ref } from 'vue';

let num = ref(0);
let str = ref('Hello');
let obj = ref({ count: 0 });

// 修改基本数据类型
num.value++;
str.value += ' World';

// 修改对象
obj.value.count++;

深层响应性

ref 支持深层响应性,这意味着它可以追踪和更新嵌套对象和数组中的变化。这种特性使得 ref 非常适合处理复杂的数据结构,如对象和数组。

import { ref } from 'vue';

let obj = ref({
  user: {
    name: 'xiaoming',
    details: {
      age: 18
    }
  }
});

// 修改嵌套对象
obj.value.user.details.age++;

当然,为了减少大型不可变数据的响应式开销,也可以通过使用shallowRef来放弃深层响应性。

let shallowObj = shallowRef({ 
    details: { age: 18, }, 
});

灵活性

ref 提供了高度的灵活性,尤其在处理普通赋值解构赋值方面。这种灵活性使得 ref 在开发中的使用更加方便,特别是在进行复杂的数据操作时。

import { ref } from 'vue';

let state = ref({
  count: 0,
  name: 'Vue'
});

// 解构赋值
let { count, name } = state.value;

// 直接修改解构后的变量
count++;
name = 'Vue3';

// 替换整个对象
state.value = {
  count: 10,
  name: 'Vue4'
};

总结

ref 在 Vue3 中提供了一种更统一、灵活的响应式解决方案,还能避免了 reactive 的某些局限性。

源码资料:点此下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1472143.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

猫头虎分享已解决Bug || AttributeError: ‘Sequential‘ object has no attribute ‘session‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

matlab采用不同数值方法求解微分方程

1、内容简介 略 60-可以交流、咨询、答疑 欧拉方法、改进欧拉、RK4、米尔斯坦方法求解微分方程 2、内容说明 略 lc; close all; clear all; % 参数赋值 global a global b global h a 1; b 2; Ni 1000; % 总步数 h 0.001; % 步长Xt1(1:Ni) 0; Xt2(…

外包干了3个月,技术倒退1年。。。

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…

【数据结构与算法】常用算法 前缀和

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

C++:STL(标准模板库)

STL&#xff1a;主要是一些“容器”的集合&#xff1b;“容器”有&#xff1a;vector(数组)、list(双向链表)、deque(双向队列)、set(集合)、map(图&#xff1a;内部结构红黑树) STL也是算法和其他一些组件的集合&#xff0c;是泛型编程的一个经典范例。 STL的目的是标准化组…

RabbitMQ开启MQTT协议支持

1&#xff09;RabbitMQ启用MQTT插件 rootmq:/# rabbitmq-plugins enable rabbitmq_mqtt Enabling plugins on node rabbitmq: rabbitmq_mqtt The following plugins have been configured:rabbitmq_managementrabbitmq_management_agentrabbitmq_mqttrabbitmq_web_dispatch Ap…

leet hot 100-6 三数之和

三数之和 原题链接思路代码 原题链接 leet hot 100-5 15. 三数之和 思路 从前往后定义第一个数字 first 开始遍历整个数组 然后要求 frist和上一个数字不重复否则就是重复组合 从frist往后遍历第二个数字 同样要求第二个数字不能重复 再定义第三个数字从后往前面数 三个数字…

【MySQL】表的约束 -- 详解

表中一定要有各种约束&#xff0c;通过约束让我们在未来插入数据库表中的数据是符合预期的。约束本质是通过技术手段倒逼程序员插入正确的数据&#xff0c;反过来站在 MySQL 的角度&#xff0c;凡是插入进来的数据都是符合数据约束的。约束的最终目标&#xff1a;保证数据的完整…

试一下newb,还是有错误呀

解题&#xff1a;原式&#xff1d; 2. 在递增的等比数列 ( a n ) (a_n) (an​)中&#xff0c;若 ( a 3 − a 1 5 2 ) (a_3 - a_1 \frac{5}{2}) (a3​−a1​25​), ( a 2 3 ) (a_2 3) (a2​3), 则公比 (q) A. ( 4 3 ) ( \frac{4}{3} ) (34​) B. ( 3 2 ) ( \frac{3}{2} …

蛇形矩阵1

题目描述 把数1&#xff0c;2&#xff0c;3&#xff0c;…&#xff0c;N*N按照“蛇形1”放入N*N的矩形中&#xff0c;输出结果。 下面是N10的蛇形1的图示 输入格式 第一行1个正整数&#xff1a;N&#xff0c;范围在[1,100]。 输出格式 N行&#xff0c;每行N个整数。 输入/…

GTSAM 中的鲁棒噪声模型与 M-估计 (GTSAM Robust Noise Model and M-Estimator)

Title: GTSAM 中的鲁棒噪声模型与 M-估计 (GTSAM Robust Noise Model and M-Estimator) 文章目录 前言I. 噪声模型II. 鲁棒回归 —— M 估计1. 线性回归2. 损失函数3. 加权最小二乘 III. 鲁棒噪声模型1. 鲁棒目标函数2. 非线性加权最小二乘3. 迭代重加权最小二乘法 V. 总结参考…

第 2 章 微信小程序的构成 (代码导读)断更,后续继续更新

2.1 小程序项目的基本结构 Hello World&#xff01;程序.mp4 文泉云盘 -- 图书二维码资源管理系统兆泰源二维码管理系统https://www.wqyunpan.com/resourceDetail.html?id284928&openIdoUgl9wdyNYHu9EcAe-GEwbQdZilY&qrcodeId242916&signc2lnbm1PUmNxSndPWGFOck…

rhel8配置bond遇到的问题汇总

操作环境信息 配置bond mode=4 两块网卡的配置信息 bond卡的配置信息(标红是后来加上的) 遇到的问题 rhel8 使用NetworkManage 管理网络,而不使用network,经常用到的命令是nmcli 拉起bond0后,因为没有获取到ip地址,每隔一段时间就会将网卡down掉,出现nic link is down …

代码随想录算法训练营第四三天 | 最后一块石头的重量 II、目标和、一和零

目录 最后一块石头的重量 II目标和一和零 LeetCode 1049. 最后一块石头的重量 II LeetCode 494. 目标和 LeetCode 474.一和零 最后一块石头的重量 II class Solution {// dp[j] 容量为j 的背包&#xff0c;最多可以背最大重量为dp[j]。// dp[j] Math.max(dp[j], dp[j - sto…

zabbix3.4.6 源码安装

Step1&#xff1a; 下载 https://www.zabbix.com/download 选中一下。download Zabbix Sources PackageReleaseDateRelease NotesZabbix ManualDownloadZabbix 3.4Server, Proxy, Agent, GUI3.4.615 January, 2018 Download step2 &#xff1a;拷贝在redhat 6.3_X86_86(192…

基于huggingface加载openai/clip-vit-large-patch14-336视觉模型demo

文章目录 引言一、模型加载二、huggingface梯度更新使用三、图像处理四、模型推理五、整体代码总结 引言 本文介绍如何使用huggingface加载视觉模型openai/clip-vit-large-patch14-336&#xff0c;我之所以记录此方法源于现有大模型基本采用huggingface库来加载视觉模型和大语…

【生成式AI】ChatGPT 原理解析(2/3)- 预训练 Pre-train

Hung-yi Lee 课件整理 预训练得到的模型我们叫自监督学习模型&#xff08;Self-supervised Learning&#xff09;&#xff0c;也叫基石模型&#xff08;foundation modle&#xff09;。 文章目录 机器是怎么学习的ChatGPT里面的监督学习GPT-2GPT-3和GPT-3.5GPTChatGPT支持多语言…

nginx(二)

nginx的验证模块 输入用户名和密码 第一步先下载httpd 这个安装包 第二步编辑子配置文件 然后去网页访问192.168.68.3/admin/ 连接之后&#xff0c;会出现404&#xff0c;404出现是因为没给网页写页面 如果要写页面&#xff0c;则在/opt/html&#xff0c;建立一个admin&#x…

算法沉淀——动态规划之路径问题(leetcode真题剖析)

算法沉淀——动态规划之路径问题 01.不同路径02.不同路径 II03.珠宝的最高价值04.下降路径最小和05.最小路径和06.地下城游戏 01.不同路径 题目链接&#xff1a;https://leetcode.cn/problems/unique-paths/ 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图…

探索C语言位段的秘密

位段 1. 什么是位段2. 位段的内存分配3. 位段的跨平台问题4. 位段的应用4. 使用位段的注意事项 1. 什么是位段 我们使用结构体实现位段&#xff0c;位段的声明和结构体是类似的&#xff0c;有两个不同&#xff1a; 位段的成员必须是int&#xff0c;unsigned int&#xff0c;或…