Skip to content

响应式处理函数

约 1482 字大约 5 分钟

前端开发响应式函数

2025-06-23

Vue 3 引入了全新的响应式系统,提供了 ref()reactive()computed()watch()watchEffect() 等 API。这些 API 在开发中各有其适用场景,但很多开发者对它们之间的区别和使用时机存在困惑。本文将从原理、用法和最佳实践角度深入分析这些响应式 API。

1. ref() - 基础响应式容器

核心特性

ref() 是 Vue 3 中最基础的响应式 API,它的核心作用是创建一个包含响应式值的引用对象

import { ref } from 'vue'

// 创建 ref
const count = ref(0)
const message = ref('Hello')
const isActive = ref(false)

// 访问和修改值
console.log(count.value) // 0
count.value++ // 修改值

实现原理

ref() 内部使用 JavaScript 的访问器属性(getter/setter)实现响应式:

// 简化的 ref 实现原理
function myRef(value) {
  let _value = value
  
  return {
    get value() {
      // 依赖收集
      track(this, 'value')
      return _value
    },
    set value(newValue) {
      if (newValue !== _value) {
        _value = newValue
        // 触发更新
        trigger(this, 'value')
      }
    }
  }
}

模板中的自动解包

在模板中使用时,Vue 会自动解包 ref,无需使用 .value

<template>
  <div>
    <p>{{ count }}</p> <!-- 自动解包,无需 count.value -->
    <button @click="count++">Increment</button>
  </div>
</template>

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

const count = ref(0)
</script>

引用类型值的处理

ref() 接收对象或数组时,内部会使用 reactive() 进行转换:

const user = ref({
  name: 'John',
  age: 30
})

// 实际上相当于
const user = {
  value: reactive({
    name: 'John',
    age: 30
  })
}

2. reactive() - 对象响应式代理

核心特性

reactive() 专门用于创建响应式的对象或数组,它返回原始对象的 Proxy 代理。

import { reactive } from 'vue'

// 创建响应式对象
const state = reactive({
  count: 0,
  user: {
    name: 'John',
    address: {
      city: 'New York'
    }
  },
  items: ['apple', 'banana']
})

// 直接访问属性,无需 .value
console.log(state.count) // 0
state.count++ // 修改属性
state.user.name = 'Jane' // 深层属性也是响应式的
state.items.push('orange') // 数组操作也是响应式的

实现原理

reactive() 使用 ES6 Proxy 实现深度响应式:

// 简化的 reactive 实现原理
function myReactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // 依赖收集
      track(target, key)
      const result = Reflect.get(target, key, receiver)
      // 如果结果是对象,递归代理
      if (result && typeof result === 'object') {
        return myReactive(result)
      }
      return result
    },
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      if (result && oldValue !== value) {
        // 触发更新
        trigger(target, key)
      }
      return result
    }
  })
}

局限性

  1. 仅限于对象类型:不能用于基本类型
  2. 解构丢失响应性:直接解构会失去响应式连接
  3. 整体替换丢失响应性:直接替换整个对象会破坏响应式
// 错误示例:解构会失去响应性
const state = reactive({ count: 0, name: 'John' })
const { count, name } = state // count 和 name 不再是响应式的

// 正确做法:使用 toRefs 保持响应性
import { toRefs } from 'vue'
const { count, name } = toRefs(state) // count 和 name 现在是 ref

// 错误示例:整体替换会破坏响应式
state = { count: 1, name: 'Jane' } // 错误!

// 正确做法:逐个修改属性
state.count = 1
state.name = 'Jane'

3. ref() 与 reactive() 的深度对比

相同点

  1. 都是响应式的:都能触发视图更新
  2. 都支持深度响应:嵌套对象都是响应式的
  3. 都基于 Vue 响应式系统:使用相同的依赖收集和触发机制

不同点

特性ref()reactive()
适用类型任何类型仅对象和数组
访问方式需要 .value直接访问属性
模板使用自动解包直接使用
解构行为保持响应式失去响应式
类型推导需要泛型参数自动推断
整体替换支持(修改 .value不支持

使用场景建议

使用 ref() 当:

  • 处理基本类型值(string、number、boolean)
  • 需要明确的 .value 访问语义
  • 可能需要在未来重新赋值整个对象
  • 在组合式函数中返回值

使用 reactive() 当:

  • 处理一组逻辑相关的状态
  • 需要管理复杂对象或嵌套数据
  • 希望直接访问属性而不需要 .value
  • 状态结构相对稳定,不需要整体替换

4. computed() - 计算属性

核心特性

computed() 创建基于依赖缓存的响应式计算值。

import { ref, computed } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

console.log(doubleCount.value) // 0
count.value = 5
console.log(doubleCount.value) // 10

可写计算属性

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(newValue) {
    const [first, last] = newValue.split(' ')
    firstName.value = first
    lastName.value = last || ''
  }
})

fullName.value = 'Jane Smith' // 会自动更新 firstName 和 lastName

5. watch() 与 watchEffect() - 副作用监听

watch() - 精确监听

import { ref, watch } from 'vue'

const count = ref(0)
const user = reactive({ name: 'John', age: 30 })

// 监听单个 ref
watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`)
})

// 监听 reactive 的属性
watch(() => user.name, (newName, oldName) => {
  console.log(`Name changed from ${oldName} to ${newName}`)
})

// 监听多个源
watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => {
  // 处理变化
})

watchEffect() - 自动依赖收集

import { ref, watchEffect } from 'vue'

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

// 自动收集依赖
watchEffect(() => {
  console.log(`Count is ${count.value}, message is ${message.value}`)
  // 当 count 或 message 变化时都会执行
})

对比总结

特性watch()watchEffect()
依赖收集显式指定自动收集
立即执行可配置总是立即执行
旧值获取支持不支持
使用场景需要精确控制自动响应依赖变化

总结

Vue 3 的响应式 API 提供了强大而灵活的状态管理能力:

  1. ref():适用于基本类型和需要明确访问语义的场景,总是使用 .value 访问
  2. reactive():适用于逻辑相关的对象状态,直接访问属性但注意解构问题
  3. computed():用于派生状态,自动缓存计算结果
  4. watch():精确监听特定数据源的变化,可获取旧值
  5. watchEffect():自动追踪依赖,立即执行副作用