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


👉点击进入 我的网站

相关推荐
全栈老石2 小时前
TypeScript 中 Type 和 Interface 傻傻分不清?看完这篇就不纠结了
前端·typescript
沈千秋.2 小时前
xss.pwnfunction.com闯关(1~6)
java·前端·xss
于谦2 小时前
✨ feat(app1,pkg1): monorepo生成规范化提交信息的最优解 - committier
javascript·github·代码规范
浪浪山_大橙子2 小时前
吃透 CSS 常用函数:从布局到美化,18 个高频函数让样式写得又快又优雅
前端
我是ed.2 小时前
Vue3 图片标注插件 AILabel
前端·vue3·标注·ailabel
晚星star2 小时前
《深入浅出 Node.js》第四章:异步编程 详细总结
前端·node.js
无心使然2 小时前
vant实现自定义日期时间选择器(年月日时分秒)
前端·vue.js