对比理解 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 代码。

复制代码
相关推荐
JS_GGbond2 小时前
【性能优化】给Vue应用“瘦身”:让你的网页快如闪电的烹饪秘籍
前端·vue.js
T___T2 小时前
一个定时器,理清 JavaScript 里的 this
前端·javascript·面试
代码小学僧2 小时前
从 Arco Table 迁移到 VTable:VTable使用经验分享
前端·react.js·开源
微笑的曙光2 小时前
Vue3 环境搭建 5 步走(零基础友好)
前端
不知名用户来了2 小时前
基于vue3 封装的antdv/element-Plus 快速生成增删改查页面
前端
明川2 小时前
Android Gradle - ASM + AsmClassVisitorFactory插桩使用
android·前端·gradle
布列瑟农的星空2 小时前
webpack迁移rsbuild——配置深度对比
前端
前端小黑屋2 小时前
查看项目中无引用到的文件、函数
前端
前端小黑屋2 小时前
小程序直播挂件Pendant问题
前端·微信小程序·直播