Skip to content

响应式原理:ref 和 reactive

所谓响应式,即当数据变化时,依赖该数据的视图或逻辑自动更新,无需手动操作 DOM。

Vue 3 提供了两种创建响应式数据的主要方式:refreactive。它们在用法和适用场景上有显著区别。

响应式的实现:

  • Vue 2:使用 Object.defineProperty 拦截数据,但无法检测新增属性和数组索引的变化。
  • Vue 3:改用 Proxy,支持动态属性增删和数组索引修改,能够实现更细粒度的依赖追踪,性能更高。

核心概念对比

特性refreactive
数据类型适用于基本类型和对象仅适用于对象/数组
访问方式需要通过 .value 访问(JS 中)直接访问属性
模板使用自动解包(无需 .value直接使用
响应性原理内部使用 reactive 包装对象直接使用 Proxy 代理
TS 类型支持更简单需要定义接口
适用场景基本类型、需要显式控制的变量复杂对象、不需要 .value 的场合

ref 深度解析

基本用法

javascript
import { ref } from 'vue'

// 基本类型
const count = ref(0)

// 对象类型(内部会自动用 reactive 转换)
const user = ref({
  name: 'Alice',
  age: 25
})

// 修改值(JS中需要 .value)
count.value++
user.value.name = 'Bob'

模板中的自动解包

html
<template>
  <!-- 基本类型:自动解包 -->
  <div>{{ count }}</div>  <!-- 不需要 .value -->
  
  <!-- 对象类型:属性访问也不需要 .value -->
  <div>{{ user.name }}</div>
</template>

为什么需要 .value?

ref 通过包装对象实现响应性:

javascript
// 伪代码实现
function ref(value) {
  return {
    get value() { track(this, 'value'); return value },
    set value(newVal) { value = newVal; trigger(this, 'value') }
  }
}

适用场景

  • 基本类型数据(string/number/boolean)
  • 需要显式控制响应式引用的场合
  • 需要重新赋值的变量(因为 reactive 不能替换整个对象)

reactive 深度解析

基本用法

javascript
import { reactive } from 'vue'

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

// 直接修改属性
state.count++
state.user.name = 'Bob'

限制与特性

  1. 不能替换整个对象

    javascript
    // ❌ 错误:会失去响应性
    state = { count: 1 }
    
    // ✅ 正确:修改属性
    Object.assign(state, { count: 1 })
  2. 对基本类型无效

    javascript
    // ❌ 不会变成响应式
    const count = reactive(0)

适用场景

  • 复杂的嵌套对象
  • 不需要重新赋值的状态管理
  • 表单对象等结构化数据

转换与工具函数

toRefs:保持响应式解构

javascript
import { reactive, toRefs } from 'vue'

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

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

count.value++ // 仍然响应

toRef:单个属性转换

javascript
const countRef = toRef(state, 'count')

isRef/isReactive 类型检查

javascript
import { isRef, isReactive } from 'vue'

console.log(isRef(count)) // true
console.log(isReactive(state)) // true

性能与实现差异

实现原理

  • ref:使用对象包装 + reactive

    javascript
    class RefImpl {
      constructor(value) {
        this._value = isObject(value) ? reactive(value) : value
      }
      get value() { /* 依赖收集 */ }
      set value(newVal) { /* 触发更新 */ }
    }
  • reactive:直接使用 ES6 Proxy

性能考虑

  • ref 对基本类型有轻微性能优势
  • reactive 更适合复杂对象,减少 .value 使用

最佳实践指南

  1. 基础规则:

    • 基本类型 → ref
    • 对象/数组 → reactive
    • 需要重新赋值 → ref
  2. 项目一致性:

    • 团队统一选择一种风格(特别是对象处理)
    • 推荐组合式 API 优先使用 ref
  3. TypeScript 优化:

    typescript
    // ref 类型推断更简单
    const count = ref<number>(0)
    
    // reactive 需要接口
    interface State {
      count: number
      user: User
    }
    const state = reactive<State>({...})
  4. 组合式函数:

    • 返回响应式数据时优先返回 ref,方便使用者解构

常见问题解答

Q1:为什么有时候需要 .value,有时候不需要?

  • <template>reactive 对象中自动解包
  • 在 JS 逻辑中需要显式使用 .value

Q2:ref 可以替代 reactive 吗?

技术上可以,但会导致代码中大量 .value,降低可读性

Q3:如何选择大型项目的状态结构?

  • 顶层状态用 reactive 组织模块
  • 组件内部简单状态用 ref

总结

refreactive 是 Vue 3 响应式系统的两大支柱:

  • ref 是通用解决方案,适合大多数场景
  • reactive 为复杂对象提供更简洁的语法
  • 理解它们的差异能帮助开发者写出更高效、可维护的代码
编程洪同学服务平台是一个广泛收集编程相关内容和资源,旨在满足编程爱好者和专业开发人员的需求的网站。无论您是初学者还是经验丰富的开发者,都可以在这里找到有用的信息和资料,我们将助您提升编程技能和知识。
专业开发
高端定制
售后无忧
站内资源均为本站制作或收集于互联网等平台,如有侵权,请第一时间联系本站,敬请谅解!本站资源仅限于学习与参考,严禁用于各种非法活动,否则后果自行负责,本站概不承担!