自在学
分类课程AI导师价格
分类课程AI导师价格
模板语法
3 / 15
条件渲染与列表渲染
自在学

© 2025 - 2026 自在学,保留所有权利。

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1

编程Vue指南响应式数据

响应式数据

在前面两堂课的学习中,我们已经多次使用了响应式数据,但可能还没有完全理解"响应式"的含义。因此这一堂课,我们将深入学习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 { message: '初始消息' }; }, methods: { changeMessage() { this.message = '新消息'; // 页面会自动更新,不需要手动操作DOM! } } }); app.mount('#app'); </script>

这就是响应式的好处。当我们点击按钮调用changeMessage方法时,message的值被修改,页面上的显示会自动更新,不需要我们手动操作DOM。


选项式API中的响应式数据

在前面的学习里,我们一直使用的是选项式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', count: 0 }; } }); app.mount('#app'); </script>

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() { return { user: { name: '张三', age: 25 }, hobbies: ['读书', '编程', '运动'] }; } }); app.mount('#app'); </script>

在这个例子中,user是一个对象,hobbies是一个数组。它们都是响应式的,当我们修改它们的属性或元素时,视图会自动更新。


组合式API中的响应式数据

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 message = ref('Hello Vue'); const count = ref(0); // 返回的数据可以在模板中使用 return { message, count }; } }); app.mount('#app'); </script>

在组合式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() { const message = ref('Hello Vue'); const changeMessage = () => { // 在JavaScript中需要使用.value message.value = '新消息'; }; return { message, changeMessage }; } }); app.mount('#app'); </script>

在这个例子中,我们在changeMessage函数中使用message.value来修改值。注意,在模板中我们直接使用{{ message }},不需要.value。

在模板中使用ref时,Vue会自动解包,所以不需要写.value。但在setup函数的JavaScript代码中,必须使用.value来访问和修改ref的值。


使用reactive创建响应式对象

前面我们在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 = createApp({ setup() { // 使用reactive创建响应式对象 const user = reactive({ name: '张三', age: 25 }); const updateUser = () => { // 直接修改属性,不需要.value user.name = '李四'; user.age = 30; }; return { user, updateUser }; } }); app.mount('#app'); </script>

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 } = Vue; const app = createApp({ setup() { const items = reactive(['项目1', '项目2', '项目3']); const addItem = () => { items.push('新项目'); }; return { items, addItem }; } }); app.mount('#app'); </script>

在这个例子中,我们使用reactive创建了一个响应式数组。当我们调用addItem方法向数组添加元素时,视图会自动更新。


ref和reactive的选择

你可能会问:ref 和 reactive 到底什么时候用?它们各自适合什么场景?

  1. 数据类型范围

    • ref 可以包裹任何类型的数据,包括基本类型(如数字、字符串、布尔值)、对象、数组等。它创建了一个响应式的“盒子”,通过 .value 属性访问和修改它的内容。
    • reactive 只能用于对象或者数组,不能直接用于基本类型(如果你直接 reactive(1),会收到警告)。用 reactive 包裹的对象或数组,会自动变成响应式,不需要 .value。
  2. 使用方式

    • 基本类型(如 let count = 0),只能用 ref,因为 reactive 不支持。
    • 对象和数组,推荐用 reactive,因为它在模板和代码中都可以直接用属性访问,写法简洁。
    • 但如果你习惯 .value 的写法,或者想让数据始终“以 ref 的方式存在”,用 ref 其实也可以。比如 const state = ref({ name: 'Tom' })。注意,这时如果你要修改属性,需要写成 state.value.name = 'Jerry'。
  3. 解构的区别

    • 用 ref 创建的数据,如果解构出来会失去响应性。例如 const { value } = count,value 不是响应的。如果用在setup返回的数据被解构时要注意。
    • 用 reactive 包裹的对象,被解构出来的属性还是响应的。
  4. 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); return { user }; } }); </script>

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, reactive } = Vue; const app = createApp({ setup() { const items = reactive(['项目1', '项目2', '项目3']); const updateItem = () => { // 这种方式可以工作 items[0] = '新项目1'; // 但更好的方式是使用数组方法 // items.splice(0, 1, '新项目1'); }; return { items, updateItem }; } }); app.mount('#app'); </script>

实际上,在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(user, { name: '李四', age: 30 }); return { user }; } }); </script>

如果我们使用ref,就可以替换整个对象:

<script> const { createApp, ref } = Vue; const app = createApp({ setup() { const user = ref({ name: '张三', age: 25 }); // 使用ref时可以替换整个对象 user.value = { name: '李四', age: 30 }; return { user }; } }); </script>

响应式原理

在实际开发中,我们通常可以直接使用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 true; } }); }

综合示例

下面,我们通过一个详细的综合示例,加深对 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)">删除</button> </li> </ul> <p>共 {{ todos.length }} 项待办</p> </div> <script> const { createApp, ref } = Vue; const app = createApp({ setup() { // 使用ref创建响应式数据 const newTodo = ref(''); const todos = ref([]); const addTodo = () => { if (newTodo.value.trim()) { todos.value.push(newTodo.value); newTodo.value = ''; } }; const removeTodo = (index) => { todos.value.splice(index, 1); }; return { newTodo, todos, addTodo, removeTodo }; } }); app.mount('#app'); </script>

在这个例子中,我们使用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"></script> <style> body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; } input { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; } ul { list-style: none; padding: 0; } li { padding: 10px; margin: 5px 0; background-color: #f5f5f5; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; } button { padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer; } </style> </head> <body> <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)">删除</button> </li> </ul> <p>共 {{ todos.length }} 项待办</p> </div> <script> const { createApp, ref } = Vue; const app = createApp({ setup() { const newTodo = ref(''); const todos = ref([]); const addTodo = () => { if (newTodo.value.trim()) { todos.value.push(newTodo.value); newTodo.value = ''; } }; const removeTodo = (index) => { todos.value.splice(index, 1); }; return { newTodo, todos, addTodo, removeTodo }; } }); app.mount('#app'); </script> </body> </html>

下一步

在这一部分中,我们了解了什么是响应式数据,如何在选项式API和组合式API中创建响应式数据,以及ref和reactive的区别和使用场景。响应式是Vue的核心特性,它让我们可以专注于数据的状态,而不需要关心DOM的更新。当我们修改数据时,视图会自动更新,这大大简化了开发工作。

在下一个部分中,我们将学习条件渲染和列表渲染,了解如何使用v-if、v-show和v-for指令来控制元素的显示和渲染列表。这些指令是构建动态界面的重要工具。

  • 什么是响应式数据
  • 选项式API中的响应式数据
  • 组合式API中的响应式数据
  • 使用reactive创建响应式对象
  • ref和reactive的选择
  • 响应式数据的限制
  • 响应式原理
  • 综合示例
  • 下一步

目录

  • 什么是响应式数据
  • 选项式API中的响应式数据
  • 组合式API中的响应式数据
  • 使用reactive创建响应式对象
  • ref和reactive的选择
  • 响应式数据的限制
  • 响应式原理
  • 综合示例
  • 下一步