Vue 响应式数据与方法
响应式是 Vue 的灵魂。你把数据"包装"成响应式对象,之后修改它,Vue 就会自动更新视图——你永远不用手动去碰 DOM。本章讲清两个最重要的 API:ref 和 reactive,以及它们最容易踩的坑。
1. 为什么需要响应式?
回顾原生 JS 写法:数据一变,你就要手动找到对应的 DOM 元素,改它的 textContent / value / className。一旦页面复杂、同一个数据出现在多处,漏改就是 bug。
Vue 的思路是:把数据"代理"给框架。你只管改数据,框架追踪它的变化,自动去更新所有用到这个数据的 DOM 节点。要做到这一点,数据必须用 ref 或 reactive 包装——这就是"声明响应式数据"。
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 有两个重要限制:
- 只能包装对象/数组(包括 Map、Set),不能包装基本类型(string/number/boolean)。
reactive(0)是错的。 - 不能整体重新赋值。
state = newObj会丢失响应式——因为新对象不是代理对象。要替换内容,用Object.assign(state, newObj)逐字段合并。
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:怎么选?
一个简单的选择规则:
- 基本类型(number、string、boolean):必须用
ref。 - 对象/数组:两种都行。
reactive写起来更顺手(无需 .value),但整体替换场景多时用ref更省心。 - 拿不准就用
ref:它是更通用的方案,新版的 ref 解包体验也越来越顺。
实际项目中,后端返回的数据列表、表单数据,用 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 加 .value、reactive 不能整体替换、解构 reactive 用 toRefs。下一篇讲计算属性和侦听器——它们是响应式的"延伸能力"。
← 上一篇 Vue 模板语法
下一篇 Vue 计算属性与侦听器 →