Vue3中ref和reactive区别

Vue 3 中 ref 和 reactive 区别完全指南

本文档专门讲解 Vue 3 里 refreactive区别:各自是什么、怎么用、何时用谁、常见坑与替代写法,配有大量对比示例,适合新手搞清“到底选哪个”。


一、为什么会有 ref 和 reactive 两种?

1.1 共同目标:让数据“响应式”

在 Vue 里,只有响应式数据变了,视图才会自动更新。
refreactive 都是用来把数据变成响应式的,但用法和适用场景不同。

1.2 设计上的分工

  • ref:来自 “reference”(引用),可以包任意类型(数字、字符串、布尔、对象、数组等)。在 script 里要通过 .value 读写。
  • reactive:来自 “reactive”(响应式的),只接受对象类型(普通对象、数组等)。在 script 里直接改属性,不需要 .value。

下面从“怎么用”开始,再对比“区别”和“怎么选”。


二、ref 的用法回顾

2.1 基本用法

ref(初始值) 返回一个响应式引用
里:xxx.valuexxx.value = 新值
里:自动解包,直接用 xxx,不写 .value。

<template>
  <p>{{ count }}</p>
  <button @click="count++">+1</button>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
function add() {
  count.value++
}
</script>
  • 模板里写 count 即可(Vue 自动解包 ref)。
  • script 里必须写 count.value

2.2 ref 可以包任何类型

const n = ref(0)
const s = ref('hello')
const ok = ref(true)
const obj = ref({ name: 'a' })
const arr = ref([1, 2, 3])
  • obj.value 才是对象,改 obj.value.name = ‘b’ 会触发更新。
  • arr.value 才是数组,arr.value.push(4) 会触发更新。

2.3 整体替换:ref 可以“换一整份”

ref 的 .value 可以整体替换,替换后仍然是响应式的:

const user = ref({ name: '小明', age: 18 })
user.value = { name: '小红', age: 20 }

这样 user 仍然是一个 ref,只是里面包的对象换了,视图会正常更新。


三、reactive 的用法回顾

3.1 基本用法

reactive(对象) 会把整个对象变成响应式的。
在 script 里直接改属性,不需要 .value;在模板里也是直接写属性名

<template>
  <p>{{ state.count }}</p>
  <button @click="state.count++">+1</button>
</template>

<script setup>
import { reactive } from 'vue'
const state = reactive({
  count: 0,
  name: ''
})
</script>
  • state 本身不是“值”,而是一个响应式对象,所以没有 .value
  • 只能写 state.countstate.name,不能写 state = xxx(见下文“坑”)。

3.2 只接受对象类型

reactive 只接受对象(包括数组)。传数字、字符串会报警告,应改用 ref

reactive(0)        // 不推荐,可能报警告
reactive('hello')  // 不推荐
reactive({})       // 可以
reactive([])       // 可以

3.3 不能整体替换

reactive 变量重新赋值断掉响应式,视图不会再更新:

const state = reactive({ count: 0 })
state = reactive({ count: 1 })  // 错误!state 变成了新的引用,原来的响应式丢了
state.count = 1                 // 正确:只改属性

若业务上经常要“换一整份对象”,用 ref 包对象更合适。


四、核心区别对比表

对比项 ref reactive
可接受类型 任意(数字、字符串、对象、数组等) 对象(含数组)
script 里读写 必须用 .value 直接属性,无 .value
template 里 自动解包,写 xxx state.xxx(属性名)
整体替换 可以:xxx.value = 新值 不可以,会丢响应式
解构 解构出来仍是 ref,保留响应式 解构会丢响应式,需 toRefs
传给别人/函数 传的是 ref,对方要 .value 传的是对象,对方直接改属性

下面逐条用示例说明。


五、区别一:类型限制

ref:任意类型

const a = ref(0)
const b = ref('')
const c = ref(true)
const d = ref({ x: 1 })
const e = ref([1, 2, 3])

reactive:仅对象

const state = reactive({ count: 0 })
const list = reactive([1, 2, 3])

若需要一个“单独的”数字或字符串,用 ref;若是一组相关字段组成的对象,可以用 reactiveref 包对象


六、区别二:script 里是否要 .value

ref:必须要 .value

const count = ref(0)
count.value++
console.log(count.value)

const user = ref({ name: 'a' })
user.value.name = 'b'

reactive:直接属性,没有 .value

const state = reactive({ count: 0, name: '' })
state.count++
state.name = 'hello'
console.log(state.count)

state 没有 .value,state 本身就是那个响应式对象。


七、区别三:整体替换(能否重新赋值)

ref:可以整体换

const user = ref({ name: '小明', age: 18 })
user.value = { name: '小红', age: 20 }

user 还是同一个 ref,只是里面包的对象换了,响应式不丢。

reactive:不能整体换

let state = reactive({ count: 0 })
state = reactive({ count: 1 })

这里 state 被赋成了新的 reactive 对象,和模板里用的原来的 state 已经不是同一个引用了,所以原来的 state 不再和视图关联,响应式就断了。
正确做法是只改属性

state.count = 1

若业务上经常要“整份替换”(例如接口返回一整份用户信息要覆盖),用 ref 包对象更合适:

const state = ref({ count: 0 })
state.value = { count: 1 }

八、区别四:解构后是否还响应式

ref:解构后仍是 ref

const count = ref(0)
const { value: countValue } = count

一般不这样解构 ref,通常直接 count.value。若把 count 传给子组件或 composable,传的是 ref 本身,对方改 count.value 仍然响应式。

reactive:解构会丢响应式(重要)

reactive解构出来的变量是普通变量,和响应式“脱钩”了:

const state = reactive({ count: 0, name: '' })
const { count, name } = state
count++        // 改的是普通变量,界面不会更新!
name = 'hello' // 同样不会更新

要让“解构出来的”仍然响应式,要用 toRefs 转成 ref 再解构:

import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0, name: '' })
const { count, name } = toRefs(state)
count.value++
name.value = 'hello'

这样 countnameref,改 .value 会同步回 state,视图会更新。
模板里可以继续写 countname(自动解包)。


九、区别五:在模板里的写法

ref:自动解包,写变量名

<template>
  <p>{{ count }}</p>
</template>

<script setup>
const count = ref(0)
</script>

模板里写 count,不写 count.value

reactive:写对象 + 属性名

<template>
  <p>{{ state.count }}</p>
  <p>{{ state.name }}</p>
</template>

<script setup>
const state = reactive({ count: 0, name: '' })
</script>

若用 toRefs 解构出 ref,模板里也可以写 countname

const { count, name } = toRefs(state)
<template>
  <p>{{ count }} {{ name }}</p>
</template>

十、区别六:传给函数或 composable

传 ref:函数里要 .value

const count = ref(0)
function add(n) {
  n.value++
}
add(count)

传 reactive:函数里直接改属性

const state = reactive({ count: 0 })
function add(obj) {
  obj.count++
}
add(state)

组合式函数里,若希望“返回一组可写的响应式数据”,通常返回 ref(或 toRefs 出来的 ref),这样对方用 .value 或自动解包即可,不会误把整个 reactive 替换掉。


十一、用 ref 包对象 vs 用 reactive 包对象

相同点

两种方式都能让对象“响应式”,改属性都会更新视图:

const userRef = ref({ name: 'a', age: 18 })
const userReactive = reactive({ name: 'a', age: 18 })

userRef.value.name = 'b'
userReactive.name = 'b'

不同点

  • ref 包对象

    • script 里要 userRef.value.xxx
    • 可以 userRef.value = { … } 整体替换。
    • 解构一般不解构“整个对象”,而是需要时用 toRefs(userRef.value) 或自己包 ref。
  • reactive 包对象

    • script 里 userReactive.xxx,没有 .value。
    • 不能 userReactive = { … } 整体替换。
    • 解构会丢响应式,需 toRefs(userReactive) 再解构。

若你经常要整体替换这个对象(例如每次请求用户信息都整份覆盖),用 ref 更合适;若只是一直改属性、不替换,用 reactiveref 都可以,看团队习惯。


十二、toRefs:reactive 解构的“救星”

toRefs(reactive 对象) 会把对象里每个属性转成 ref,这样解构出来的每个属性仍是响应式的:

import { reactive, toRefs } from 'vue'
const state = reactive({
  count: 0,
  name: ''
})
const { count, name } = toRefs(state)
count.value++
name.value = 'hi'
  • 模板里可以写 countname
  • count.valuestate.count 是同一份数据,改谁都会更新。

适合:想用 reactive 管理“一组字段”,又想在模板或函数里按名字用(而不是一直写 state.xxx)时。


十三、常见坑与注意点

13.1 reactive 整体替换

const state = reactive({ count: 0 })
state = reactive({ count: 1 })  // 错误,响应式断了

应只改属性,或改用 ref 包对象再整体替换。

13.2 reactive 解构不包 toRefs

const { count } = reactive({ count: 0 })
count++  // 不生效

toRefs 后再解构,或不用解构,一直写 state.count

13.3 ref 在 script 里忘记 .value

const count = ref(0)
count++  // 错误:count 是 ref 对象,这样改的不是数值
count.value++

13.4 把 reactive 赋给 ref 再整体替换

若你有一个 reactive,后来想“整份换掉”,不能直接 state = 新对象。可以一开始就用 ref 包对象,或把新对象的属性逐个赋给 state(Object.assign(state, 新对象)),保留同一引用。


十四、怎么选?简单决策

  • 单值(一个数字、字符串、布尔、一个“我要整体替换”的对象或数组)→ 用 ref
  • 一组强相关的字段(表单 state、页面 state),且不会整份替换、希望少写 .value → 用 reactive;若需要解构或传出去用,配合 toRefs
  • 既要“一组字段”又要“有时整份替换”(例如先 reactive 一坨,后来接口返回整份覆盖)→ 用 ref 包对象,或一开始就 ref({ … })

十五、速查表

需求 用谁 说明
一个数字/字符串/布尔 ref script 里 .value
一个对象,可能要整体替换 ref xxx.value = 新对象
一个对象,只改属性、不替换 reactive 或 ref 都可 reactive 无 .value
想解构出一组字段仍响应式 reactive + toRefs 解构出来是 ref
模板里少写 state. 前缀 reactive + toRefs 解构 模板写 count、name
组合式函数返回值 一般返回 ref(或 toRefs) 便于对方 .value 或解包

十六、学习建议

  1. 先记牢:ref 要 .value(script),reactive 不能整体替换reactive 解构要用 toRefs
  2. 不确定时,单值或“可能整份换”的对象/数组”用 ref,一般不会错。
  3. 多写几个对比示例(ref 包对象 vs reactive,解构 vs toRefs),在项目里试一遍,体会会更深。

把本文档里的示例在项目里敲一遍、故意写错几次(如 reactive 整体赋值、解构不加 toRefs),再按表格纠正,会掌握得更牢。祝你学习顺利。

发表评论