vue3 - 响应式数ref与reactive的深度解析

文章目录

    • [1. 基础概念](#1. 基础概念)
    • [2. 核心区别](#2. 核心区别)
      • [1. 数据类型支持](#1. 数据类型支持)
      • [2. 访问方式](#2. 访问方式)
      • [3. 重新赋值](#3. 重新赋值)
    • [3. 底层实现原理](#3. 底层实现原理)
      • [`reactive` 的实现](#reactive 的实现)
      • [`ref` 的实现](#ref 的实现)
    • [4. reactive 的局限性 ​](#4. reactive 的局限性)

在 Vue3 的 Composition API 中,响应式系统得到了彻底的重构,这不仅提升了性能,还增强了代码的可读性和灵活性。在新的响应式 API 中, refreactive 是最常用的两种创建响应式数据的方式。下面将深入探讨这两个 API 的差异及其底层实现。

1. 基础概念

reactive

reactive 用于创建 对象类型 的响应式数据。它会将对象的所有属性变成响应式,确保当数据变化时,视图自动更新。

示例:

javascript 复制代码
import { reactive } from "vue";

const state = reactive({
  count: 0,
  user: {
    name: "张三",
    age: 25
  }
});

state.count++; // 响应式更新

state.count 是响应式的,修改它的值会自动触发视图更新。

ref

ref 可以用于 任何类型 的响应式数据,包括 基本数据类型stringnumberboolean 等)和 对象 。它通过 .value 属性来存储和访问数据。

示例:

javascript 复制代码
import { ref } from "vue";

const count = ref(0);
const user = ref({
  name: "李四",
  age: 30
});

count.value++; // 通过 .value 访问
user.value.name = "王五";

在这里,count 是一个基本数据类型,user 是一个对象。我们通过 .value 来访问和修改它们的值。

2. 核心区别

1. 数据类型支持

  • reactive

    • 适用于 对象类型ObjectArrayMapSet 等)。
    • 不支持 基本类型stringnumberboolean 等)。
  • ref

    • 适用于 所有数据类型 ,包括 基本类型对象类型
    • 当包裹对象时,ref 只会让对象的引用变成响应式,而不会递归地将对象的属性转化为响应式。

2. 访问方式

  • reactive:直接访问数据的属性。

    javascript 复制代码
    state.count; // 直接访问
  • ref :需要通过 .value 来访问。

    javascript 复制代码
    count.value; // 通过 .value 访问

    在 Vue 模板中,ref 会自动解包,无需使用 .value

    vue 复制代码
    <template>
      <div>{{ count }}</div>
      <!-- 自动解包,无需 .value -->
    </template>

3. 重新赋值

  • reactive:不能直接重新赋值给响应式对象,否则会失去响应性。

    javascript 复制代码
    let state = reactive({ count: 0 });
    state = { count: 1 }; // ❌ 失去响应性
  • ref:可以安全地重新赋值,并且会保持响应性。

    javascript 复制代码
    const count = ref(0);
    count.value = 1; // ✅ 保持响应性

3. 底层实现原理

Vue3 的响应式系统基于 Proxy API ,这是 Vue3 相对于 Vue2 的重要优化。通过 Proxy,Vue3 可以拦截对数据的读取、修改等操作,进行依赖追踪和视图更新。

reactive 的实现

reactive 会通过 Proxy 对传入的对象进行深度代理。它会为对象的每个属性创建代理,使得对象的每一个嵌套属性都变得响应式。

示例代码:

javascript 复制代码
function reactive(target) {
  return new Proxy(target, {
    get(obj, key) {
      track(obj, key); // 依赖收集
      return obj[key];
    },
    set(obj, key, value) {
      obj[key] = value;
      trigger(obj, key); // 触发视图更新
      return true;
    }
  });
}

getset 方法中,tracktrigger 分别用于收集依赖和触发视图更新。

ref 的实现

ref 的实现与 reactive 类似,但它专门用于包装 基本类型对象类型 。当包装的是对象时,ref 内部会使用 reactive 来确保对象的响应性。

示例代码:

javascript 复制代码
function ref(value) {
  if (isObject(value)) {
    value = reactive(value); // 对对象使用 reactive 包裹
  }

  return {
    get value() {
      track(this, "value"); // 依赖收集
      return value;
    },
    set value(newVal) {
      value = newVal;
      trigger(this, "value"); // 触发视图更新
    }
  };
}

ref 会使用 Proxy 拦截 .value 的读取和修改操作,并在其中收集依赖和触发视图更新。

4. reactive 的局限性 ​

(1) 有限的值类型

它只能用于对象类型 (对象、数组和如 Map、Set 这样的集合类型)。它不能持有如 stringnumberboolean 这样的原始类型。

(2) 不能替换整个对象(直接用=号来赋值)

由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地"替换"响应式对象,因为这样的话与第一个引用的响应性连接将丢失:

对于这个问题,解决方案有三个:见 reactive 定义响应式数据进行列表赋值时,视图没有更新的解决方案

js 复制代码
let state = reactive({ count: 0 });
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 });

(3) 对解构操作不友好

当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:

js 复制代码
const state = reactive({ count: 0 });

// 当解构时,count 已经与 state.count 断开连接
let { count } = state;
// 不会影响原始的 state
count++;

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count);

由于这些限制,vue 官网 建议使用 ref() 作为声明响应式状态的主要 API。

解决解构响应式对象的问题见: toRefs


👉点击进入 我的网站

相关推荐
LaiYoung_1 分钟前
🛡️ 代码质量的“埃癸斯”:为什么你的项目需要这面更懂业务的 ESLint 神盾?
前端·代码规范·eslint
AAA阿giao14 分钟前
qoder-cli:下一代命令行 AI 编程代理——全面解析与深度实践指南
开发语言·前端·人工智能·ai编程·mcp·context7·qoder-cli
我有一棵树17 分钟前
Vite 7 中 dev 没样式、build 却正常:一次由 CSS import 位置引发的工程化问题
前端·javascript·vue.js
@Autowire18 分钟前
CSS 中 px、%、vh、vw 这四种常用单位的区别
前端·css
怕浪猫19 分钟前
React从入门到出门第七章 管理你的CSS 模块化样式控制方案
javascript·css·react.js
@Autowire21 分钟前
CSS 中「继承属性」的核心知识,包括哪些属性可继承、继承的规则、如何控制继承(继承/取消继承)
前端·css
万行26 分钟前
机器人系统ros2&期末速通2
前端·人工智能·python·算法·机器学习
天天向上102426 分钟前
css Grid常用布局
前端·css
syty202032 分钟前
RedisTemplate方法汇总
前端·bootstrap·html