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