ref与reactive #
一、ref详解 #
1.1 基本使用 #
vue
<template>
<div>
<p>计数: {{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
1.2 ref的特点 #
vue
<script setup>
import { ref, isRef } from 'vue'
// 1. 可以包装任何类型的值
const number = ref(0)
const string = ref('hello')
const boolean = ref(true)
const object = ref({ name: '张三' })
const array = ref([1, 2, 3])
const nullValue = ref(null)
const undefinedValue = ref(undefined)
// 2. 通过.value访问和修改
console.log(number.value) // 0
number.value = 10
// 3. 检查是否为ref
console.log(isRef(number)) // true
// 4. 模板中自动解包,不需要.value
</script>
1.3 对象类型的ref #
vue
<script setup>
import { ref, isReactive } from 'vue'
const user = ref({ name: '张三', age: 25 })
// 访问嵌套属性
console.log(user.value.name) // '张三'
// 修改嵌套属性
user.value.name = '李四'
// 内部对象会被reactive处理
console.log(isReactive(user.value)) // true
// 整体替换
user.value = { name: '王五', age: 30 }
</script>
1.4 数组类型的ref #
vue
<script setup>
import { ref } from 'vue'
const list = ref([1, 2, 3])
// 修改数组元素
list.value[0] = 10 // 响应式的
// 数组方法
list.value.push(4)
list.value.pop()
list.value.splice(0, 1)
// 整体替换
list.value = [4, 5, 6]
</script>
二、reactive详解 #
2.1 基本使用 #
vue
<template>
<div>
<p>姓名: {{ state.name }}</p>
<p>年龄: {{ state.age }}</p>
<button @click="state.age++">增加年龄</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({
name: '张三',
age: 25
})
</script>
2.2 reactive的特点 #
vue
<script setup>
import { reactive, isReactive } from 'vue'
const state = reactive({
count: 0,
nested: {
value: 1
}
})
// 1. 直接访问和修改,无需.value
console.log(state.count) // 0
state.count++
// 2. 深层响应式
state.nested.value = 2 // 响应式的
// 3. 检查是否为reactive
console.log(isReactive(state)) // true
console.log(isReactive(state.nested)) // true
</script>
2.3 数组类型的reactive #
vue
<script setup>
import { reactive } from 'vue'
const list = reactive([1, 2, 3])
// 修改数组元素
list[0] = 10 // 响应式的
// 数组方法
list.push(4)
list.pop()
list.splice(0, 1)
// 注意:不能整体替换
// list = [4, 5, 6] // 错误!会丢失响应性
</script>
2.4 Map和Set的响应式 #
vue
<script setup>
import { reactive } from 'vue'
const map = reactive(new Map([['key', 'value']]))
const set = reactive(new Set([1, 2, 3]))
// Map操作
map.set('newKey', 'newValue')
map.get('key')
map.delete('key')
map.clear()
// Set操作
set.add(4)
set.has(1)
set.delete(1)
set.clear()
</script>
三、ref vs reactive #
3.1 对比表 #
| 特性 | ref | reactive |
|---|---|---|
| 访问方式 | .value | 直接访问 |
| 适用类型 | 任意类型 | 对象类型 |
| 整体替换 | 支持 | 不支持 |
| 解构 | 不会丢失响应性 | 会丢失响应性 |
| 模板使用 | 自动解包 | 直接使用 |
| TypeScript | 类型推断更好 | 需要类型标注 |
3.2 使用场景 #
vue
<script setup>
import { ref, reactive } from 'vue'
// ✅ ref适合的场景
// 1. 基本类型
const count = ref(0)
const name = ref('张三')
const isActive = ref(false)
// 2. 需要整体替换的对象
const user = ref(null)
user.value = await fetchUser()
// 3. 需要整体替换的数组
const list = ref([])
list.value = await fetchList()
// ✅ reactive适合的场景
// 1. 表单对象
const form = reactive({
username: '',
password: '',
remember: false
})
// 2. 组件状态
const state = reactive({
loading: false,
error: null,
data: []
})
// 3. 复杂嵌套对象
const config = reactive({
theme: {
primary: '#409eff',
mode: 'light'
},
settings: {
language: 'zh-CN',
timezone: 'Asia/Shanghai'
}
})
</script>
四、进阶用法 #
4.1 shallowRef #
vue
<script setup>
import { shallowRef, triggerRef } from 'vue'
// 浅层ref,只有.value的变化是响应式的
const state = shallowRef({
count: 0,
nested: {
value: 1
}
})
// 不会触发更新
state.value.count++
state.value.nested.value++
// 会触发更新
state.value = { count: 1, nested: { value: 2 } }
// 手动触发更新
state.value.count++
triggerRef(state)
</script>
4.2 shallowReactive #
vue
<script setup>
import { shallowReactive } from 'vue'
// 浅层reactive,只有顶层属性是响应式的
const state = shallowReactive({
count: 0,
nested: {
value: 1
}
})
// 会触发更新
state.count++
// 不会触发更新
state.nested.value++
</script>
4.3 toRef #
vue
<script setup>
import { reactive, toRef } from 'vue'
const state = reactive({
count: 0,
name: '张三'
})
// 为单个属性创建ref,保持响应连接
const countRef = toRef(state, 'count')
console.log(countRef.value) // 0
countRef.value++ // state.count也会变化
state.count++ // countRef.value也会变化
</script>
4.4 toRefs #
vue
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: '张三'
})
// 解构reactive对象,保持响应性
const { count, name } = toRefs(state)
console.log(count.value) // 0
count.value++ // state.count也会变化
// 常用于组合式函数返回
function useUser() {
const state = reactive({
user: null,
loading: false,
error: null
})
return toRefs(state)
}
const { user, loading, error } = useUser()
</script>
4.5 unref #
vue
<script setup>
import { ref, unref } from 'vue'
const count = ref(0)
const value = 10
// 如果是ref返回.value,否则返回本身
console.log(unref(count)) // 0
console.log(unref(value)) // 10
// 等价于
console.log(count.value ?? count)
</script>
4.6 toRaw #
vue
<script setup>
import { reactive, toRaw } from 'vue'
const state = reactive({ count: 0 })
const raw = toRaw(state)
console.log(raw === state) // false
raw.count++ // 不会触发更新
// 用途:比较对象、传递给不需要响应式的库
</script>
4.7 markRaw #
vue
<script setup>
import { reactive, markRaw } from 'vue'
// 标记对象永远不会被转为响应式
const staticData = markRaw({
config: { apiUrl: '/api' }
})
const state = reactive({
data: staticData // staticData不会被转为响应式
})
</script>
五、实际应用示例 #
5.1 表单处理 #
vue
<template>
<form @submit.prevent="submit">
<input v-model="form.username" placeholder="用户名">
<input v-model="form.password" type="password" placeholder="密码">
<button type="submit" :disabled="loading">
{{ loading ? '提交中...' : '提交' }}
</button>
</form>
</template>
<script setup>
import { reactive, ref } from 'vue'
const form = reactive({
username: '',
password: ''
})
const loading = ref(false)
async function submit() {
loading.value = true
try {
await login(form)
} finally {
loading.value = false
}
}
</script>
5.2 列表管理 #
vue
<template>
<div>
<input v-model="newItem" @keyup.enter="addItem">
<button @click="addItem">添加</button>
<ul>
<li v-for="item in list" :key="item.id">
{{ item.text }}
<button @click="removeItem(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const newItem = ref('')
const list = ref([
{ id: 1, text: '项目1' },
{ id: 2, text: '项目2' }
])
function addItem() {
if (!newItem.value.trim()) return
list.value.push({
id: Date.now(),
text: newItem.value
})
newItem.value = ''
}
function removeItem(id) {
const index = list.value.findIndex(item => item.id === id)
if (index > -1) {
list.value.splice(index, 1)
}
}
</script>
5.3 组合式函数 #
javascript
// useCounter.js
import { ref, computed, toRefs } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
doubled,
increment,
decrement,
reset
}
}
vue
<script setup>
import { useCounter } from './useCounter'
const { count, doubled, increment, decrement, reset } = useCounter(10)
</script>
六、常见问题 #
6.1 解构丢失响应性 #
vue
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: '张三'
})
// ❌ 直接解构会丢失响应性
let { count, name } = state
count++ // 不会触发更新
// ✅ 使用toRefs
const { count, name } = toRefs(state)
count.value++ // 会触发更新
</script>
6.2 reactive整体替换 #
vue
<script setup>
import { reactive, ref } from 'vue'
// ❌ reactive不能整体替换
let state = reactive({ count: 0 })
state = { count: 1 } // 丢失响应性
// ✅ 使用ref
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 } // 保持响应性
// ✅ 使用Object.assign
const state = reactive({ count: 0 })
Object.assign(state, { count: 1, name: '张三' })
</script>
6.3 在reactive中使用ref #
vue
<script setup>
import { reactive, ref, isRef } from 'vue'
// reactive会自动解包顶层ref
const count = ref(0)
const state = reactive({ count })
console.log(state.count) // 0,自动解包
state.count = 10 // 正常工作
console.log(count.value) // 10
// 但嵌套的ref不会自动解包
const nested = ref({ value: 1 })
const state2 = reactive({ nested })
console.log(isRef(state2.nested)) // true,仍然是ref
</script>
七、总结 #
选择指南 #
| 场景 | 推荐 |
|---|---|
| 基本类型 | ref |
| 需要整体替换 | ref |
| 表单对象 | reactive |
| 复杂状态对象 | reactive |
| 组合式函数返回 | toRefs + reactive |
最佳实践:
- 基本类型使用ref
- 对象类型优先使用reactive
- 需要整体替换时使用ref
- 解构reactive时使用toRefs
- 组合式函数返回时使用toRefs
- 保持一致性,团队统一风格
最后更新:2026-03-26