Vue3 响应式 ref 和 reactive 原理详解及选择建议

Vue3 响应式 ref 和 reactive 原理详解及选择建议

核心概念理解

参考 响应式基础 | Vue.js

什么是响应式?

响应式就是当数据发生变化时,使用这个数据的地方会自动更新。

javascript 复制代码
// 普通 JavaScript - 数据变化不会自动更新页面
let count = 0;
count = 1; // 页面不会自动更新

// Vue 响应式 - 数据变化会自动更新页面
const count = ref(0);
count.value = 1; // 页面会自动更新

ref 和 reactive 的本质区别

ref - 包装盒子原理

javascript 复制代码
// ref 就像是给数据套了一个"包装盒子"
const count = ref(0);
// 实际结构:{ value: 0 }

const name = ref('Alice');
// 实际结构:{ value: 'Alice' }

reactive - 对象代理原理

javascript 复制代码
// reactive 直接对对象进行"魔法改造"
const user = reactive({
  name: 'Alice',
  age: 25
});
// 直接修改属性:user.name = 'Bob' - 页面自动更新

详细用法对比

1. 基本数据类型

vue 复制代码
<script setup>
import { ref } from 'vue'

// ref 可以包装任何类型的数据
const count = ref(0)        // 数字
const name = ref('Alice')   // 字符串
const isShow = ref(true)    // 布尔值
const empty = ref(null)     // null

// 修改值时必须通过 .value
console.log(count.value)    // 读取:0
count.value = 10            // 修改:10
</script>

2. 对象类型

vue 复制代码
<script setup>
import { ref, reactive } from 'vue'

// 用 ref 包装对象
const userRef = ref({
  name: 'Alice',
  age: 25
})

// 用 reactive 包装对象
const userReactive = reactive({
  name: 'Bob',
  age: 30
})

// 修改方式不同:
userRef.value.name = 'Charlie'     // 需要 .value
userReactive.name = 'David'        // 直接修改属性
</script>

完整示例对比

vue 复制代码
<template>
  <div>
    <h2>ref 示例</h2>
    <p>计数器: {{ count }}</p>
    <p>用户名: {{ userInfo.name }}</p>   <!-- 这里不可写作 userInfo.value.name  -->
    <button @click="increment">增加</button>
    <button @click="changeUser">改变用户</button>
    
    <h2>reactive 示例</h2>
    <p>计数器: {{ state.count }}</p>
    <p>用户名: {{ state.user.name }}</p>
    <button @click="incrementReactive">增加</button>
    <button @click="changeUserReactive">改变用户</button>
  </div>
</template>

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

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

// 对象类型也可以用 ref
const userInfo = ref({
  name: 'Alice',
  age: 25
})

const increment = () => {
  count.value++  // 注意:需要 .value
}

const changeUser = () => {
  userInfo.value.name = 'Bob'  // 修改对象属性也需要 .value
}

// ===== reactive 方式 =====
// reactive 只能用于对象
const state = reactive({
  count: 0,
  user: {
    name: 'Charlie',
    age: 30
  }
})

const incrementReactive = () => {
  state.count++  // 直接修改,不需要 .value
}

const changeUserReactive = () => {
  state.user.name = 'David'  // 直接修改属性
}
</script>

原理深入理解

ref 的实现原理(简化版)

javascript 复制代码
// ref 的核心思想
function myRef(value) {
  return {
    _isRef: true,  // 标识这是 ref
    get value() {
      track()      // 依赖收集
      return value
    },
    set value(newVal) {
      value = newVal
      trigger()    // 触发更新
    }
  }
}

reactive 的实现原理(简化版)

javascript 复制代码
// reactive 使用 Proxy 实现
function myReactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track()  // 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      trigger()  // 触发更新
      return result
    }
  })
}

使用场景推荐

什么时候用 ref?

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

// ✅ 推荐:基本数据类型
const count = ref(0)
const name = ref('Alice')
const isLoading = ref(false)

// ✅ 推荐:需要整体替换的对象
const formData = ref({
  username: '',
  password: ''
})

// 整体替换时 ref 更方便
const resetForm = () => {
  formData.value = {  // 直接替换整个对象
    username: '',
    password: ''
  }
}
</script>

什么时候用 reactive?

vue 复制代码
<script setup>
import { reactive } from 'vue'

// ✅ 推荐:复杂的嵌套对象
const state = reactive({
  user: {
    profile: {
      name: 'Alice',
      age: 25,
      address: {
        city: 'Beijing',
        street: 'Main St'
      }
    }
  },
  ui: {
    loading: false,
    dialogVisible: false
  }
})

// 修改嵌套属性更简洁
const updateUserCity = () => {
  state.user.profile.address.city = 'Shanghai'
}
</script>

常见陷阱和注意事项

1. ref 的 .value 陷阱

vue 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)

// ❌ 错误:模板中不需要 .value
// <p>{{ count.value }}</p>

// ✅ 正确:模板中直接使用
// <p>{{ count }}</p>

// ✅ 正确:JavaScript 中需要 .value
const doubleCount = computed(() => count.value * 2)
const increment = () => count.value++
</script>

2. reactive 的替换陷阱

vue 复制代码
<script setup>
import { reactive } from 'vue'

const state = reactive({ count: 0 })

// ❌ 错误:这样会破坏响应式
const resetState = () => {
  state = { count: 0 }  // 这样做会失去响应式
}

// ✅ 正确:修改属性
const resetState = () => {
  state.count = 0
}

// ✅ 或者使用 Object.assign
const resetState = () => {
  Object.assign(state, { count: 0 })
}
</script>

3. 解构的陷阱

vue 复制代码
<script setup>
import { ref, reactive } from 'vue'

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

// ❌ 错误:解构会丢失响应式
const { name } = state  // name 不再是响应式的

// ✅ 正确:使用 toRefs
import { toRefs } from 'vue'
const { name } = toRefs(state)  // name 仍然是响应式的

// ❌ 错误:ref 解构也需要小心
const { value } = count  // value 不再是响应式的
</script>

总结

特性 ref reactive
数据类型 任何类型 只能是对象/数组
访问方式 .value 直接访问
模板使用 自动解包 直接使用
嵌套对象 需要多层 .value 直接访问属性
替换对象 简单 需要特殊处理

简单记忆法则:

  • 基本类型 → 用 ref
  • 复杂对象 → 用 reactive
  • 不确定 → 用 ref(更安全)
相关推荐
gCode Teacher 格码致知1 分钟前
前端教学8:提高 CSS 选择器的优先级,有哪些方法?
前端·css·html5
u0111026756 分钟前
Canvas 导出 PNG 时 quality 参数为什么不起作用
前端·javascript·vue.js
众链网络12 分钟前
年卡跨淡旺季的有效期模型:不可用日期、次数与顺延规则
java·前端·数据库
2601_9498477516 分钟前
jose 跨端三方库仓颉适配全流程
前端·javascript·flutter
CappuccinoRose27 分钟前
滚动交互体系
前端·javascript·交互·html5·1024程序员节
时空节拍AI数字人30 分钟前
博物馆数字人讲解系统设计:知识库 schema、展项联动协议与时序控制
java·服务器·前端·人工智能·ai·语音识别
吴建旭 智宅焕31 分钟前
智能家居全国交付中前端工具与后端基础设施的系统集成架构:报价入口与交付兜底的闭环设计
前端·架构·智能家居
夏天要喝冰可乐1 小时前
Codex 写 Three.js 大屏实录:800 行代码,和一条画反的弧线
前端·aigc
欣欣之王来了1 小时前
v-bind语法糖::属性名的简写方式
前端·学习·架构·项目·vue3教程
2601_954005621 小时前
# ricon SVG 编辑器:工业图元的“军火工厂“
前端·物联网·数学建模·架构·编辑器·制造