在前面两堂课的学习中,我们已经多次使用了响应式数据,但可能还没有完全理解"响应式"的含义。因此这一堂课,我们将深入学习Vue的响应式系统,了解数据是如何变成响应式的,以及如何创建和管理响应式数据。
响应式是Vue的核心特性之一。当我们修改数据时,使用这些数据的视图会自动更新,这就是响应式的魅力。Vue 3使用了全新的响应式系统,基于JavaScript的Proxy实现,提供了更好的性能和更强大的功能。

让我们先通过一个简单的例子来理解什么是响应式数据。在普通的JavaScript中,如果我们修改一个变量的值,页面上显示这个变量的地方不会自动更新:
|<div id="app"> <p id="message"></p> </div> <script> // 普通JavaScript let message = '初始消息'; document.getElementById('message').textContent = message; // 修改message的值 message = '新消息'; // 但是页面上显示的内容不会自动更新! </script>
在普通的JavaScript中,每当变量的值发生变化时,我们都需要通过代码手动去查找并更新相应的DOM节点,否则页面上的内容不会随着变量的改变而自动变化。这种方式不仅易出错,而且在数据和界面复杂时维护起来会非常麻烦。
而在Vue中,数据和视图是自动关联在一起的。当你修改了响应式数据,Vue内部会自动追踪哪些地方用到了这份数据,并在数据变更时帮你自动更新对应的界面。你不需要再手动操作DOM,所有的视图同步都由Vue高效地完成,让开发者可以专注于业务逻辑而不用担心界面更新的细节。
|<div id="app"> <p>{{ message }}</p> <button @click="changeMessage">改变消息</button> </div> <script> const { createApp } = Vue; const app = createApp({ data() { return
这就是响应式的好处。当我们点击按钮调用changeMessage方法时,message的值被修改,页面上的显示会自动更新,不需要我们手动操作DOM。
在前面的学习里,我们一直使用的是选项式API(Options API)。在选项式API中,我们通过data函数来定义响应式数据:
|<div id="app"> <p>{{ message }}</p> <p>{{ count }}</p> </div> <script> const { createApp } = Vue; const app = createApp({ data() { return { message: 'Hello Vue'
data函数返回一个对象,这个对象中的所有属性都会变成响应式的。Vue会追踪这些属性的变化,当属性值改变时,会自动更新使用这些属性的视图。
data必须是一个函数,而不是一个对象。这是因为每个组件实例都需要有自己的数据副本。如果data是一个对象,所有组件实例会共享同一个数据对象,这会导致数据混乱。
在data函数中,我们可以定义各种类型的数据:字符串、数字、布尔值、数组、对象等。所有这些类型的数据都会变成响应式的:
|<div id="app"> <p>姓名:{{ user.name }}</p> <p>年龄:{{ user.age }}</p> <p>爱好:{{ hobbies.join(', ') }}</p> </div> <script> const { createApp } = Vue; const app = createApp({ data
在这个例子中,user是一个对象,hobbies是一个数组。它们都是响应式的,当我们修改它们的属性或元素时,视图会自动更新。
Vue 3带来了一个全新的“组合式API”(Composition API),这是Vue 3相比Vue 2非常重要的升级之一。简单来说,组合式API提供了一种全新的方式来编写和组织组件里的逻辑,让代码更加灵活、清晰,尤其是在组件变得越来越复杂的时候。
在组合式API中,我们常用的创建响应式数据的“工具”有两个:ref 和 reactive。它们的作用相当于以前data里定义的数据,但是功能更强大,也更灵活。
我们先来看看ref是怎么用的。ref通常用来创建基本数据类型的响应式数据,比如字符串、数字、布尔值等等。什么意思呢?就是我们可以用ref把一个普通的值变成“响应式”的,这样当它变化时,界面上用到它的地方就会自动更新。
下面我们来举个例子体验一下:
|<div id="app"> <p>{{ message }}</p> <p>{{ count }}</p> </div> <script> const { createApp, ref } = Vue; const app = createApp({ setup() { // 使用ref创建响应式数据 const
在组合式API(Composition API)中,核心就是setup函数。setup是每个组件的“准备区”,它在组件实例被正式创建之前就会执行。说得简单一些,setup就像一个专门用来写代码的小房间,在这里你可以定义所有的响应式数据(比如用ref或reactive),也可以写方法(函数)、计算属性等。
通过setup返回的内容,模板里就能直接使用这些数据和方法。你可以把它理解为把所有和组件业务相关的逻辑都集中在setup里,让代码更清晰。
ref函数接收一个值,返回一个响应式的引用对象。这个对象有一个value属性,存储实际的值。在模板中使用时,Vue会自动解包ref,所以我们不需要写.value。但在JavaScript代码中,我们需要通过.value来访问和修改值:
|<div id="app"> <p>{{ message }}</p> <button @click="changeMessage">改变消息</button> </div> <script> const { createApp, ref } = Vue; const app = createApp({ setup() {
在这个例子中,我们在changeMessage函数中使用message.value来修改值。注意,在模板中我们直接使用{{ message }},不需要.value。
在模板中使用ref时,Vue会自动解包,所以不需要写.value。但在setup函数的JavaScript代码中,必须使用.value来访问和修改ref的值。
前面我们在setup中介绍响应式函数时提到了reactive,是的,对于对象和数组,我们可以使用reactive来创建响应式数据。reactive会深度地将整个对象转换为响应式:
|<div id="app"> <p>姓名:{{ user.name }}</p> <p>年龄:{{ user.age }}</p> <button @click="updateUser">更新用户</button> </div> <script> const { createApp, reactive } = Vue; const app
reactive返回的对象本身就是响应式的,我们可以直接修改它的属性,不需要像ref那样使用.value。这是reactive和ref的一个重要区别。
reactive也可以用于数组:
|<div id="app"> <ul> <li v-for="item in items">{{ item }}</li> </ul> <button @click="addItem">添加项目</button> </div> <script> const { createApp, reactive } =
在这个例子中,我们使用reactive创建了一个响应式数组。当我们调用addItem方法向数组添加元素时,视图会自动更新。
你可能会问:ref 和 reactive 到底什么时候用?它们各自适合什么场景?
数据类型范围
ref 可以包裹任何类型的数据,包括基本类型(如数字、字符串、布尔值)、对象、数组等。它创建了一个响应式的“盒子”,通过 .value 属性访问和修改它的内容。reactive 只能用于对象或者数组,不能直接用于基本类型(如果你直接 reactive(1),会收到警告)。用 reactive 包裹的对象或数组,会自动变成响应式,不需要 .value。使用方式
let count = 0),只能用 ref,因为 reactive 不支持。reactive,因为它在模板和代码中都可以直接用属性访问,写法简洁。.value 的写法,或者想让数据始终“以 ref 的方式存在”,用 ref 其实也可以。比如 const state = ref({ name: 'Tom' })。注意,这时如果你要修改属性,需要写成 state.value.name = 'Jerry'。解构的区别
ref 创建的数据,如果解构出来会失去响应性。例如 const { value } = count,value 不是响应的。如果用在setup返回的数据被解构时要注意。reactive 包裹的对象,被解构出来的属性还是响应的。Vue官方建议
ref。reactive;如果要和 ref 保持一致风格,也可以用 ref。ref 是通用安全的选择。例如:
const count = ref(0) // 适用于计数器这样的基本类型const user = reactive({ name: '张三', age: 18 }) // 想管理一整个对象时其实当你用 ref 包裹对象时,Vue 内部会自动用 reactive 把对象变响应式,所以本质上没什么大问题,只是写法上需要使用 .value。
|<script> const { createApp, ref } = Vue; const app = createApp({ setup() { // ref也可以用于对象 const user = ref({ name: '张三', age: 25 }); // 访问时需要.value console.log(user.value.name);
Vue官方推荐:对于基本类型使用ref,对于对象和数组可以使用reactive或ref。如果你不确定,使用ref总是安全的,因为它可以用于任何类型。
虽然Vue的响应式系统很强大,但它也有一些限制。Vue 3的响应式系统基于Proxy,这意味着它只能追踪对象的属性访问和修改。有一些操作可能无法被正确追踪。
例如,直接通过索引设置数组元素:
|<div id="app"> <ul> <li v-for="(item, index) in items" :key="index">{{ item }}</li> </ul> <button @click="updateItem">更新第一个项目</button> </div> <script> const { createApp,
实际上,在Vue 3中,直接通过索引设置数组元素是可以工作的。但为了代码的可读性和可维护性,建议使用数组的方法(如push、pop、splice等)。
另一个需要注意的点是,我们不能直接替换整个响应式对象:
|<script> const { createApp, reactive } = Vue; const app = createApp({ setup() { const user = reactive({ name: '张三', age: 25 }); // 错误的方式:直接替换整个对象 // user = { name: '李四', age: 30 }; // 正确的方式:修改属性 Object.assign
如果我们使用ref,就可以替换整个对象:
|<script> const { createApp, ref } = Vue; const app = createApp({ setup() { const user = ref({ name: '张三', age: 25 }); // 使用ref时可以替换整个对象 user.value = { name: '李四', age: 30 };
在实际开发中,我们通常可以直接使用Vue的响应式能力而不用关心底层是如何实现的。但如果你希望更加高效、灵活地编写Vue应用,了解响应式原理会让你事半功倍。
Vue 3 的响应式系统是建立在 JavaScript 的 Proxy(代理)机制之上的。Proxy 是 ES6 引入的一个原生 API,它可以用来“代理”一个对象,对这个对象的各种操作(比如读取属性、设置属性、删除属性等)进行拦截和处理。
具体来说,Vue 会用 Proxy 代理我们的响应式数据对象。当你访问一个属性时,Proxy 的 get 方法会被触发;当你修改一个属性时,set 方法会被触发。通过这种方式,Vue 就能够“感知”你对数据的访问和修改行为,实现自动依赖收集和视图更新。
Vue 3的响应式系统相比Vue 2有了很大的改进。Vue 2使用Object.defineProperty,只能拦截对象的属性访问。Vue 3使用Proxy,可以拦截更多操作,包括数组操作和对象属性的添加、删除等。
简而言之,Vue 3 通过 Proxy 技术,实现了数据的“侦听”与“追踪”,让开发者可以专注于数据本身,而不用手动操心依赖更新的问题。
|// 简化的响应式原理示例 function reactive(target) { return new Proxy(target, { get(target, key) { // 当访问属性时,追踪依赖 track(target, key); return target[key]; }, set(target, key, value) { // 当修改属性时,触发更新 target[key] = value; trigger(target, key); return
下面,我们通过一个详细的综合示例,加深对 Vue 3 响应式数据原理的理解。我们会一步步实现一个简单的“待办事项”小应用。这个例子会演示如何用 ref 创建和管理响应式的数据,以及数据变化时界面是如何自动更新的。即使你没有开发经验,也可以跟着理解每一部分的作用。
|<div id="app"> <h2>待办事项</h2> <input v-model="newTodo" @keyup.enter="addTodo" placeholder="输入待办事项"> <ul> <li v-for="(todo, index) in todos" :key="index"> {{ todo }} <button @click="removeTodo(index)"
在这个例子中,我们使用ref创建了两个响应式数据:newTodo用于存储新输入的待办事项,todos用于存储所有待办事项的数组。
addTodo方法向todos数组添加新项目,removeTodo方法从数组中删除指定项目。当我们修改这些数据时,视图会自动更新。
注意,我们在addTodo和removeTodo方法中使用.value来访问ref的值。在模板中,我们直接使用newTodo和todos,Vue会自动解包。
|<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>待办事项</title> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></
在这一部分中,我们了解了什么是响应式数据,如何在选项式API和组合式API中创建响应式数据,以及ref和reactive的区别和使用场景。响应式是Vue的核心特性,它让我们可以专注于数据的状态,而不需要关心DOM的更新。当我们修改数据时,视图会自动更新,这大大简化了开发工作。
在下一个部分中,我们将学习条件渲染和列表渲染,了解如何使用v-if、v-show和v-for指令来控制元素的显示和渲染列表。这些指令是构建动态界面的重要工具。