对比理解 Vue 响应式 API:data(), ref、reactive、computed 与 watch 详解

在 Vue 开发中,理解响应式数据的概念以及如何正确使用不同的响应式 API 是非常重要的。本文将详细介绍 Vue 2 中的 data 选项、Vue 3 Composition API 中的 ref、reactive、toRefs 和 toRef 函数之间的区别和使用场景,同时也会介绍 computed 和 watch 的区别与使用场景。

基本概念

响应式数据(Reactive Data)指的是当数据发生变化时,使用该数据的界面会自动更新,而无需手动操作DOM。 响应式数据具有以下特征:

  1. 自动追踪变化:框架会自动监测数据的变化
  2. 依赖关系建立:框架知道哪些视图依赖哪些数据
  3. 自动更新视图:当数据变化时,相关视图会自动重新渲染

1. Vue 2 中的 data 选项

在 Vue 2 中,我们通常在组件的 data 选项中定义响应式数据。

1.1 基本用法

javascript 复制代码
export default {
  data() {
    return {
      message: 'Hello Vue!',
      count: 0,
      user: {
        name: 'Alice',
        age: 25
      }
    }
  }
}

1.2 特点

  • data 必须是一个函数,返回一个包含响应式数据的对象
  • Vue 会递归地将 data 中的所有属性转换为 getter/setter
  • 访问和修改数据时不需要使用 .value
  • 适用于 Options API

2. Vue 3 中的 ref

ref 是 Vue 3 Composition API 中的一个函数,用于创建一个响应式的引用。

2.1 基本用法

javascript 复制代码
import { ref } from 'vue'

// 基本类型
const count = ref(0)
const message = ref('Hello Vue 3!')

// 对象类型
const user = ref({
  name: 'Alice',
  age: 25
})

// 访问值
console.log(count.value) // 0
console.log(user.value.name) // 'Alice'

// 修改值
count.value++
user.value.name = 'Bob'

2.2 特点

  • 可以包装任何类型的值(基本类型、对象、数组等)
  • 返回一个带有 .value 属性的响应式引用对象
  • 在模板中使用时,Vue 会自动解包,无需使用 .value
  • 适用于需要将基本类型变为响应式的场景

3. Vue 3 中的 reactive

reactive 是 Vue 3 Composition API 中的另一个重要函数,用于创建响应式对象。

3.1 基本用法

javascript 复制代码
import { reactive } from 'vue'

const state = reactive({
  count: 0,
  message: 'Hello Vue 3!',
  user: {
    name: 'Alice',
    age: 25
  }
})

// 访问值
console.log(state.count) // 0
console.log(state.user.name) // 'Alice'

// 修改值
state.count++
state.user.name = 'Bob'

3.2 特点

  • 只能用于对象和数组(不能用于基本类型)
  • 返回原始对象的代理(Proxy)
  • 不需要使用 .value 访问属性
  • 对对象进行深层响应式转换

4. 四者之间的详细对比

特性 data (Vue 2) ref reactive toRefs toRef
Vue 版本 Vue 2 Vue 3 Vue 3 Vue 3 Vue 3
API 类型 Options API Composition API Composition API Composition API Composition API
数据类型支持 任意类型 任意类型 仅对象/数组 对象属性 单个对象属性
访问方式 直接访问 .value 访问(模板中除外) 直接访问 .value 访问 .value 访问
解构支持 不支持 支持(但失去响应性) 支持(但失去响应性) 支持(保持响应性) 支持(保持响应性)
自动解包 不适用 模板中自动解包 不适用 不适用 不适用

5. 使用场景推荐

5.1 何时使用 data

  • 使用 Vue 2 项目时
  • 使用 Options API 时
  • 需要在组件选项中定义响应式数据时

5.2 何时使用 ref

  • 需要将基本类型(字符串、数字、布尔值)变为响应式时
  • 需要重新分配整个对象时
  • 在模板中需要直接使用变量名(而非对象属性)时
javascript 复制代码
// 适合使用 ref 的场景
const isVisible = ref(true)
const userName = ref('Alice')

// 切换整个对象
let user = ref({
  name: 'Alice',
  age: 25
})

user.value = {
  name: 'Bob',
  age: 30
}

5.3 何时使用 reactive

  • 需要创建包含多个相关属性的响应式状态对象时
  • 处理复杂的数据结构时
  • 不需要重新分配整个对象,只需要修改对象属性时
javascript 复制代码
// 适合使用 reactive 的场景
const state = reactive({
  user: {
    name: 'Alice',
    age: 25
  },
  permissions: ['read', 'write'],
  isLoggedIn: true
})

5.4 何时使用 toRefs 和 toRef

  • 需要将 reactive 对象的属性解构为独立的响应式引用时
  • 需要将 reactive 对象的部分属性传递给其他函数时
  • 需要保持解构后属性的响应性时
javascript 复制代码
// 适合使用 toRefs 的场景
const state = reactive({
  count: 0,
  name: 'Alice',
  permissions: []
})

// 解构但仍保持响应性
const { count, name } = toRefs(state)

// 适合使用 toRef 的场景
const countRef = toRef(state, 'count')
const nameRef = toRef(state, 'name')

6. 注意事项与最佳实践

6.1 ref 的注意事项

  1. 在 JavaScript 中访问 ref 的值时必须使用 .value
  2. 解构 ref 会失去响应性
javascript 复制代码
const count = ref(0)
const { value } = count // 错误:失去了响应性

6.2 reactive 的注意事项

  1. 不能用于基本类型
  2. 替换 reactive 对象本身不会保持响应性
javascript 复制代码
let state = reactive({ count: 0 })

// 错误:这将失去响应性
state = reactive({ count: 1 })
  1. 解构 reactive 对象会失去响应性
javascript 复制代码
const state = reactive({ count: 0, name: 'Alice' })
const { count } = state // 错误:失去了响应性

6.3 最佳实践

  1. 基本类型用 ref,对象类型优先考虑 reactive
javascript 复制代码
// 推荐
const count = ref(0)
const state = reactive({
  users: [],
  loading: false
})
  1. 组合使用 ref 和 reactive
javascript 复制代码
import { ref, reactive } from 'vue'

// 复杂状态使用 reactive
const state = reactive({
  users: [],
  pagination: {
    page: 1,
    size: 10
  }
})

// 简单状态使用 ref
const loading = ref(false)
const errorMessage = ref('')
  1. 使用 toRefs 解构 reactive 对象
javascript 复制代码
import { reactive, toRefs } from 'vue'

const state = reactive({
  count: 0,
  name: 'Alice'
})

// 正确的解构方式
const { count, name } = toRefs(state)
  1. 使用 toRef 创建单个响应式引用
javascript 复制代码
import { reactive, toRef } from 'vue'

const state = reactive({
  count: 0,
  name: 'Alice'
})

// 创建单个响应式引用
const countRef = toRef(state, 'count')
const nameRef = toRef(state, 'name')

// 修改原始属性会影响 toRef 创建的引用
state.count++
console.log(countRef.value) // 1

// 修改 toRef 创建的引用也会影响原始属性
countRef.value++
console.log(state.count) // 2

7. 实际项目中的应用示例

以下是在实际项目中使用这些 API 的示例:

7.1 使用 ref 处理表单状态

javascript 复制代码
import { ref } from 'vue'

export default {
  setup() {
    const formData = ref({
      username: '',
      email: '',
      password: ''
    })
    
    const isSubmitting = ref(false)
    const errorMessage = ref('')
    
    const handleSubmit = async () => {
      isSubmitting.value = true
      try {
        // 提交表单逻辑
      } catch (error) {
        errorMessage.value = error.message
      } finally {
        isSubmitting.value = false
      }
    }
    
    return {
      formData,
      isSubmitting,
      errorMessage,
      handleSubmit
    }
  }
}

7.2 使用 reactive 管理复杂状态

javascript 复制代码
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      users: [],
      filters: {
        name: '',
        status: 'active'
      },
      pagination: {
        page: 1,
        size: 10,
        total: 0
      },
      loading: false,
      error: null
    })
    
    // 计算属性
    const activeUsers = computed(() => {
      return state.users.filter(user => user.status === 'active')
    })
    
    return {
      state,
      activeUsers
    }
  }
}

8. computed 与 watch 的区别

在 Vue 开发中,除了响应式数据外,我们还需要处理派生状态和副作用。这就需要用到 computed 和 watch。

8.1 computed(计算属性)

computed 用于声明式地描述依赖响应式数据的复杂逻辑,创建一个响应式的只读值。

基本用法

javascript 复制代码
import { ref, computed } from 'vue'

const count = ref(0)

// 只读计算属性
const doubled = computed(() => count.value * 2)

// 可写的计算属性
const writableComputed = computed({
  get: () => count.value * 2,
  set: (val) => {
    count.value = val / 2
  }
})

console.log(doubled.value) // 0

count.value++
console.log(doubled.value) // 2

特点

  • 基于响应式依赖进行缓存,只有依赖发生改变时才会重新计算
  • 默认是只读的,但也可以创建可写的计算属性
  • 适用于复杂的逻辑计算,避免在模板中放入太多逻辑
  • 在模板中使用时会被自动解包,无需使用 .value

8.2 watch(侦听器)

watch 用于监听响应式数据的变化并在变化时执行副作用。

基本用法

javascript 复制代码
import { ref, watch } from 'vue'

const count = ref(0)
const user = ref({
  name: 'Alice',
  age: 25
})

// 侦听单个 ref
watch(count, (newVal, oldVal) => {
  console.log(`count changed from ${oldVal} to ${newVal}`)
})

// 侦听 getter 函数
watch(
  () => user.value.name,
  (newName, oldName) => {
    console.log(`name changed from ${oldName} to ${newName}`)
  }
)

// 侦听多个数据源
watch(
  [count, () => user.value.name],
  ([newCount, newName], [oldCount, oldName]) => {
    console.log(`count: ${oldCount} -> ${newCount}`)
    console.log(`name: ${oldName} -> ${newName}`)
  }
)

// 深度侦听
watch(
  user,
  (newUser, oldUser) => {
    console.log('user changed', newUser, oldUser)
  },
  { deep: true }
)

特点

  • 用于执行副作用,如 API 请求、手动 DOM 操作等
  • 不会缓存,只要依赖发生变化就会执行
  • 可以监听单个或多个数据源
  • 支持深度监听、立即执行等选项

8.3 computed 与 watch 的区别对比

特性 computed watch
主要用途 创建响应式只读值 执行副作用
缓存机制 有缓存,依赖不变时不重新计算 无缓存,每次变化都执行
返回值 返回一个不可变的响应式对象 无返回值(执行副作用)
使用场景 复杂逻辑计算、数据格式化 API 请求、异步操作、DOM 操作
执行时机 模板渲染时按需计算 数据变化时立即执行

10. 总结

选择使用哪种响应式 API 主要取决于以下因素:

  1. Vue 版本:Vue 2 使用 data,Vue 3 可以使用 ref、reactive、toRefs 和 toRef
  2. 数据类型:基本类型推荐使用 ref,对象类型可以使用 reactive
  3. 使用场景:需要频繁替换整个值时使用 ref,处理复杂嵌套对象时使用 reactive
  4. API 风格:Options API 使用 data,Composition API 使用 ref、reactive、toRefs 和 toRef
  5. 派生状态处理:需要缓存的派生值用 computed,需要执行副作用用 watch
  6. 解构需求:需要解构 reactive 对象属性并保持响应性时使用 toRefs 或 toRef

理解这些差异有助于我们在实际开发中做出更好的选择,写出更高效、更易维护的 Vue 代码。

复制代码
相关推荐
deli00737 分钟前
32K 上下文塞满后,AI 最先忘掉什么?120 轮对话实测
前端
deli0072 小时前
手机拍灯为什么有一条条黑纹?PWM 240Hz 实测
前端
wangruofeng6 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng6 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈10 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技10 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户479492835691510 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马12 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng13 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding14 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js