Vue 响应式数据与方法

响应式是 Vue 的灵魂。你把数据"包装"成响应式对象,之后修改它,Vue 就会自动更新视图——你永远不用手动去碰 DOM。本章讲清两个最重要的 API:refreactive,以及它们最容易踩的坑。

1. 为什么需要响应式?

回顾原生 JS 写法:数据一变,你就要手动找到对应的 DOM 元素,改它的 textContent / value / className。一旦页面复杂、同一个数据出现在多处,漏改就是 bug。

Vue 的思路是:把数据"代理"给框架。你只管改数据,框架追踪它的变化,自动去更新所有用到这个数据的 DOM 节点。要做到这一点,数据必须用 refreactive 包装——这就是"声明响应式数据"。

2. ref:基本类型首选

ref 可以包装任何类型:数字、字符串、布尔、对象、数组都行。但官方推荐它主要用来包装基本类型。

<script setup>
import { ref } from "vue";

// ref 可以包装任何类型:基本类型、对象、数组都行
const count = ref(0);              // 数字
const message = ref("Hello");      // 字符串
const isVisible = ref(false);      // 布尔
const user = ref({ name: "小明" }); // 对象也行

// ⚠️ 在 JS/TS 里访问 ref 的值,必须加 .value
function increment() {
  count.value++;                    // 不是 count++
  user.value.name = "大明";         // 改对象内部字段也要先 .value
}

// ⚠️ 重新赋值整个 ref,也要 .value
function reset() {
  count.value = 0;
  user.value = { name: "新用户" };  // 不是 user = {...}
}
</script>

<template>
  <!-- 模板里 Vue 自动解包,不需要 .value -->
  <p>{{ count }} / {{ message }}</p>
  <button @click="increment">+1</button>
</template>

新手最常踩的坑:在 <script setup> 里访问 ref 的值必须加 .value!这是因为 Vue 内部把 ref 实现成一个 { value: ... } 对象,直接 count++ 改的是包装对象而非内部的值。模板里则不需要 .value——Vue 会自动"解包"。

记住口诀:"JS 里加 .value,模板里直接用"。VS Code 的 Vue 扩展会在你漏写 .value 时给警告,所以不用太担心。

3. reactive:对象/数组首选

reactive 用 Proxy 包装对象,返回一个代理对象。访问/修改它的字段就和普通对象一模一样,不需要 .value

<script setup>
import { reactive } from "vue";

// reactive 只能包装对象/数组,不能包装基本类型(string/number/boolean)
const state = reactive({
  user: { name: "小明", age: 20 },
  list: ["苹果", "香蕉"],
  isLoading: false,
});

// ✅ 直接像普通对象用,不需要 .value
function growUp() {
  state.user.age++;
  state.list.push("橘子");
  state.isLoading = !state.isLoading;
}

// ❌ 错误:不能整体重新赋值(会丢失响应式)
// state = { user: {...}, list: [] };

// ✅ 正确:逐字段改,或用 Object.assign
Object.assign(state, { isLoading: false });
</script>

<template>
  <p>{{ state.user.name }} 今年 {{ state.user.age }} 岁</p>
  <button @click="growUp">长大一岁</button>
</template>

reactive 有两个重要限制:

4. 解构 reactive 会丢响应式!

这是另一个高频坑。直接对 reactive 对象做解构,解构出来的变量就变成普通值,不再是响应式:

<script setup>
import { reactive, toRefs, toRef } from "vue";

const state = reactive({
  name: "小明",
  age: 20,
});

// ❌ 错误:直接解构会丢失响应式
// const { name, age } = state;

// ✅ 正确:用 toRefs 把每个字段变成 ref
const { name, age } = toRefs(state);
// 现在 name.value / age.value 是响应式的

// 或者只解构一个字段用 toRef
const age2 = toRef(state, "age");
</script>

<template>
  <p>{{ name }} - {{ age }}</p>
</template>

解决方法是 toRefs:它把 reactive 对象的每个字段都变成一个 ref,解构出来仍是响应式的。或用 toRef 只转一个字段。

5. ref vs reactive:怎么选?

一个简单的选择规则:

实际项目中,后端返回的数据列表、表单数据,用 ref 装更方便整体替换(因为 ref 可以直接 data.value = newArr):

<script setup>
import { ref, reactive } from "vue";

// 场景:从后端拿到一份新数据,要替换整个对象

// ✅ 方案一:用 ref 包装对象(推荐)
const user = ref({ name: "", age: 0 });
function loadUser(data) {
  user.value = data;   // 直接整体替换,响应式正常
}

// ✅ 方案二:reactive 用 Object.assign 整体合并
const profile = reactive({ name: "", age: 0 });
function loadProfile(data) {
  Object.assign(profile, data);   // 不丢响应式
}

// ❌ 反面教材:profile = data; ← 这会彻底丢掉响应式
</script>

6. 方法的声明

<script setup> 里,函数就是普通声明——function 关键字或箭头函数都行,模板里直接用名字调用。不需要像 Vue 2 那样写在 methods 选项里。

<script setup>
import { ref } from "vue";

const count = ref(0);

// 在 <script setup> 里,函数就是普通声明,不需要写在 methods 里
function increment() {
  count.value++;
}

// 箭头函数也行
const reset = () => {
  count.value = 0;
};

// 可以接收参数
function add(n) {
  count.value += n;
}

// 可以互相调用
function double() {
  increment();
  increment();
}
</script>

<template>
  <button @click="increment">+1</button>
  <button @click="add(5)">+5</button>
  <button @click="double">+2</button>
  <button @click="reset">归零</button>
  <p>{{ count }}</p>
</template>

这种"扁平化"写法是 Composition API 的核心优势:数据和操作它的函数放在一起,逻辑内聚,而不是按"data 里 / methods 里 / computed 里"机械分类。

7. 响应式原理(简化版)

不必深究,但了解大致原理能避免踩坑。Vue 3 用 Proxy 实现 reactive:当你读取对象的某个属性,Vue 记下"谁依赖这个属性"(依赖收集);当你修改属性,Vue 通知所有依赖它的地方更新(派发更新)。整个过程对开发者透明。

ref 则更简单:内部就是 { value: xxx },通过 getter/setter 拦截对 .value 的访问。这就是为什么必须 .value——否则 Vue 无法感知读写。

小结

这一章你掌握了 Vue 的核心机制——响应式数据。记三个要点:ref.valuereactive 不能整体替换、解构 reactive 用 toRefs。下一篇讲计算属性和侦听器——它们是响应式的"延伸能力"。

← 上一篇 Vue 模板语法

下一篇 Vue 计算属性与侦听器

✈️💬