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(更安全)
相关推荐
FFF-X2 分钟前
前端无感刷新 Token 的 Axios 封装方案
前端
qq_589568103 分钟前
javaweb开发笔记—— 前端工程化
java·前端
gnip21 分钟前
包管理工具的发展
前端
前端工作日常1 小时前
H5 实时摄像头 + 麦克风:完整可运行 Demo 与深度拆解
前端·javascript
韩沛晓1 小时前
uniapp跨域怎么解决
前端·javascript·uni-app
前端工作日常2 小时前
以 Vue 项目为例串联eslint整个流程
前端·eslint
程序员鱼皮2 小时前
太香了!我连夜给项目加上了这套 Java 监控系统
java·前端·程序员
该用户已不存在2 小时前
这几款Rust工具,开发体验直线上升
前端·后端·rust
前端雾辰2 小时前
Uniapp APP 端实现 TCP Socket 通信(ZPL 打印实战)
前端
无羡仙2 小时前
虚拟列表:怎么显示大量数据不卡
前端·react.js