第一部分:Vue 3 基础入门

第8集:响应式数据初体验

响应式系统是Vue的核心特性之一,它允许我们在数据变化时自动更新视图。Vue 3的响应式系统基于Proxy API实现,提供了更强大、更灵活的响应式能力。在本集中,我们将学习Vue 3响应式系统的基础知识,包括refreactive的使用方法和区别。

8.1 响应式系统的基本概念

什么是响应式?

响应式是指当数据发生变化时,视图会自动更新,无需手动操作DOM。这种机制极大地提高了开发效率,让开发者可以专注于数据逻辑,而不是DOM操作。

Vue 3响应式系统的优势

  • 基于Proxy API,支持更多的数据类型
  • 支持监听数组索引和长度变化
  • 支持监听对象的新增和删除属性
  • 更好的性能和内存使用
  • 提供了更灵活的响应式API

8.2 使用ref创建响应式数据

ref是Vue 3中最常用的响应式API之一,它可以将基本类型或对象转换为响应式数据。

8.2.1 基本使用

语法

import { ref } from 'vue'

const count = ref(0)

示例

<template>
  <div>
    <h1>响应式数据初体验</h1>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <button @click="decrement">减少</button>
    <p>消息: {{ message }}</p>
    <input v-model="message" type="text" placeholder="输入消息">
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 创建响应式数据
const count = ref(0)
const message = ref('Hello Vue 3!')

// 方法
function increment() {
  // 访问和修改ref的值需要使用.value
  count.value++
}

function decrement() {
  count.value--
}
</script>

8.2.2 注意事项

  • 访问和修改值:在&lt;script setup&gt;中,需要使用.value访问和修改ref的值;在模板中,Vue会自动解包,不需要.value
  • 类型支持ref支持所有JavaScript数据类型,包括基本类型和对象
  • 响应式转换:对于对象类型,ref会递归地将其转换为响应式数据
  • 解构问题:直接解构ref会丢失响应式,需要使用toRefstoRef

8.3 使用reactive创建响应式数据

reactive用于创建响应式对象,它只能用于对象类型,不能用于基本类型。

8.3.1 基本使用

语法

import { reactive } from 'vue'

const state = reactive({ count: 0, message: 'Hello' })

示例

<template>
  <div>
    <h1>使用reactive创建响应式对象</h1>
    <p>计数: {{ state.count }}</p>
    <button @click="increment">增加</button>
    <p>用户信息</p>
    <p>姓名: {{ user.name }}</p>
    <p>年龄: {{ user.age }}</p>
    <input v-model="user.name" type="text" placeholder="输入姓名">
    <input v-model.number="user.age" type="number" placeholder="输入年龄">
  </div>
</template>

<script setup>
import { reactive } from 'vue'

// 创建响应式对象
const state = reactive({
  count: 0
})

const user = reactive({
  name: '张三',
  age: 25
})

// 方法
function increment() {
  // 直接修改属性,不需要.value
  state.count++
}
</script>

8.3.2 注意事项

  • 只能用于对象reactive只能用于对象类型(对象、数组、Map、Set等),不能用于基本类型
  • 直接修改属性:访问和修改reactive对象的属性时,不需要使用.value
  • 响应式转换reactive会递归地将对象的所有属性转换为响应式
  • 解构问题:直接解构reactive对象会丢失响应式,需要使用toRefs
  • 引用问题:不能直接替换reactive对象,否则会丢失响应式

8.4 ref vs reactive

特性 ref reactive
支持的数据类型 所有类型 仅对象类型
访问方式 需要.value(脚本中) 直接访问属性
解构行为 直接解构丢失响应式 直接解构丢失响应式
替换整个对象 支持(count.value = 10) 不支持(会丢失响应式)
数组支持 支持 支持
类型推断 良好 良好
使用场景 基本类型或需要频繁替换的对象 复杂对象或状态管理

8.5 响应式数据的解构

直接解构响应式对象会丢失响应式,Vue 3提供了toRefstoRef来解决这个问题。

8.5.1 使用toRefs

toRefs可以将reactive对象转换为包含ref的普通对象,这样解构后的数据仍然保持响应式。

示例

<template>
  <div>
    <h1>响应式数据解构</h1>
    <p>计数: {{ count }}</p>
    <p>消息: {{ message }}</p>
    <button @click="increment">增加</button>
    <input v-model="message" type="text">
  </div>
</template>

<script setup>
import { reactive, toRefs } from 'vue'

// 创建响应式对象
const state = reactive({
  count: 0,
  message: 'Hello Vue 3!'
})

// 使用toRefs解构,保持响应式
const { count, message } = toRefs(state)

function increment() {
  // 解构后的ref仍然需要.value
  count.value++
}
</script>

8.5.2 使用toRef

toRef用于创建单个属性的ref,它可以从reactive对象中提取单个属性并保持响应式。

示例

<template>
  <div>
    <h1>使用toRef提取单个属性</h1>
    <p>姓名: {{ name }}</p>
    <input v-model="name" type="text">
  </div>
</template>

<script setup>
import { reactive, toRef } from 'vue'

const user = reactive({
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
})

// 提取单个属性,保持响应式
const name = toRef(user, 'name')
</script>

8.6 响应式数据的计算

Vue 3提供了computed函数来创建计算属性,计算属性是基于响应式数据的派生值,它会自动缓存计算结果,只有当依赖的数据变化时才会重新计算。

示例

<template>
  <div>
    <h1>计算属性</h1>
    <p>原始价格: {{ price }}元</p>
    <p>折扣: {{ discount }}%</p>
    <input v-model.number="discount" type="number" placeholder="输入折扣">
    <p>折后价格: {{ discountedPrice }}元</p>
    <p>是否特价: {{ isSpecialPrice ? '是' : '否' }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// 原始数据
const price = ref(100)
const discount = ref(20)

// 计算属性
const discountedPrice = computed(() => {
  return price.value * (1 - discount.value / 100)
})

// 计算属性的getter和setter
const isSpecialPrice = computed({
  get() {
    return discountedPrice.value < 80
  },
  set(value) {
    if (value) {
      discount.value = 30 // 如果设置为特价,折扣改为30%
    } else {
      discount.value = 20 // 否则恢复为20%
    }
  }
})
</script>

计算属性的优势

  • 缓存:只有依赖的数据变化时才会重新计算
  • 简洁:将复杂的计算逻辑封装起来,使模板更简洁
  • 响应式:计算结果会自动响应依赖数据的变化

8.7 响应式数据的侦听

Vue 3提供了watchwatchEffect函数来侦听响应式数据的变化。

8.7.1 使用watch

watch可以监听一个或多个响应式数据的变化,并在变化时执行回调函数。

示例

<template>
  <div>
    <h1>侦听响应式数据</h1>
    <p>计数: {{ count }}</p>
    <button @click="count++">增加</button>
    <p>消息: {{ message }}</p>
    <input v-model="message" type="text">
    <p>用户: {{ user.name }} - {{ user.age }}</p>
    <input v-model="user.name" type="text">
    <input v-model.number="user.age" type="number">
  </div>
</template>

<script setup>
import { ref, reactive, watch } from 'vue'

const count = ref(0)
const message = ref('Hello')
const user = reactive({ name: '张三', age: 25 })

// 监听单个ref
watch(count, (newValue, oldValue) => {
  console.log(`count变化了: ${oldValue} -> ${newValue}`)
})

// 监听多个值
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
  console.log('count或message变化了')
})

// 监听reactive对象的属性
watch(() => user.name, (newName, oldName) => {
  console.log(`用户名变化了: ${oldName} -> ${newName}`)
})

// 监听整个reactive对象
watch(user, (newUser, oldUser) => {
  console.log('用户对象变化了')
}, {
  deep: true, // 深度监听,监听对象的所有属性
  immediate: true // 立即执行一次
})
</script>

8.7.2 使用watchEffect

watchEffect会自动跟踪依赖,当依赖的数据变化时,会重新执行回调函数。

示例

<template>
  <div>
    <h1>使用watchEffect</h1>
    <p>计数: {{ count }}</p>
    <button @click="count++">增加</button>
    <p>消息: {{ message }}</p>
    <input v-model="message" type="text">
  </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue'

const count = ref(0)
const message = ref('Hello')

// watchEffect会自动跟踪依赖
watchEffect(() => {
  console.log(`count: ${count.value}, message: ${message.value}`)
})
</script>

**watch vs watchEffect**:

特性 watch watchEffect
依赖跟踪 手动指定依赖 自动跟踪依赖
回调时机 可以获取新旧值 只能获取新值
立即执行 需要设置immediate: true 默认立即执行
适用场景 需要获取新旧值,或只需要监听特定依赖 自动跟踪所有依赖,不需要手动指定

8.8 响应式数据的最佳实践

  1. 选择合适的API

    • 基本类型使用ref
    • 复杂对象使用reactive
    • 需要频繁替换的对象使用ref
  2. 避免直接解构响应式对象

    • 使用toRefstoRef进行解构
    • 或者直接使用.value访问
  3. 合理使用计算属性

    • 将复杂的计算逻辑封装为计算属性
    • 避免在计算属性中修改数据
    • 合理使用getter和setter
  4. 谨慎使用深度监听

    • 深度监听会带来性能开销
    • 尽量只监听需要的属性
    • 考虑使用watchEffect代替深度监听
  5. 注意响应式数据的边界

    • 不要在响应式数据中存储非响应式的复杂对象
    • 避免在响应式数据中存储DOM元素
    • 合理使用shallowRefshallowReactive(用于浅层响应式)

8.9 响应式系统的工作原理

Vue 3的响应式系统基于Proxy API实现,它通过以下步骤工作:

  1. 创建Proxy:当我们使用refreactive创建响应式数据时,Vue会为数据创建一个Proxy对象
  2. 收集依赖:当响应式数据被访问时,Vue会收集当前的依赖(通常是组件的渲染函数或计算属性)
  3. 触发更新:当响应式数据被修改时,Vue会通知所有收集到的依赖,触发它们的重新执行

Proxy的优势

  • 支持监听对象的新增和删除属性
  • 支持监听数组索引和长度变化
  • 支持Map、Set、WeakMap、WeakSet等数据结构
  • 更好的性能和内存使用

本集小结

在本集中,我们学习了Vue 3响应式系统的基础知识:

  • 响应式的概念:当数据变化时,视图自动更新
  • ref的使用:用于创建响应式数据,支持所有数据类型
  • reactive的使用:用于创建响应式对象,仅支持对象类型
  • **ref vs reactive**:它们的区别和适用场景
  • 响应式数据的解构:使用toRefstoRef保持响应式
  • 计算属性:使用computed创建基于响应式数据的派生值
  • 侦听器:使用watchwatchEffect侦听响应式数据的变化
  • 最佳实践:如何合理使用响应式API
  • 工作原理:基于Proxy API的响应式系统

响应式系统是Vue的核心,掌握好响应式API的使用对于开发Vue应用至关重要。在下一集中,我们将学习Vue 3的事件处理与表单绑定,进一步提升我们的Vue开发能力。

« 上一篇 模板语法基础:插值与指令 下一篇 » 事件处理与表单绑定基础