Vue中响应式Api

21 阅读5分钟

📊 API 分类概览

响应式 API 分类:
├── 基础 API
│   ├── ref()           # 响应式引用
│   └── reactive()      # 响应式对象
│
├── 浅层 API (性能优化)
│   ├── shallowRef()    # 浅层 ref
│   └── shallowReactive() # 浅层 reactive
│
├── 只读 API
│   ├── readonly()      # 深度只读
│   └── shallowReadonly() # 浅层只读
│
├── 转换工具
│   ├── toRef()         # 属性转 ref
│   ├── toRefs()        # 对象转 refs
│   ├── toRaw()         # 获取原始对象
│   └── markRaw()       # 标记为非响应式
│
└── 检查工具
    ├── isRef()         # 检查 ref
    ├── isReactive()    # 检查 reactive
    ├── isReadonly()    # 检查 readonly
    ├── isProxy()       # 检查代理
    └── unref()         # 解包 ref

🎯 基础响应式 API

1. ref() - 响应式引用

import { ref } from 'vue'

// 创建响应式引用(任意类型)
const count = ref(0)           // Ref<number>
const name = ref('Vue')        // Ref<string>
const user = ref({ id: 1 })    // Ref<object>

// 访问和修改
console.log(count.value)       // 0
count.value = 1                // 响应式更新

// 模板中自动解包
// <div>{{ count }}</div>      // 不需要 .value

// TypeScript 类型
import type { Ref } from 'vue'
const count: Ref<number> = ref(0)

使用场景

  • 基本类型数据(字符串、数字、布尔值)
  • DOM 引用(模板引用)
  • 需要明确 .value 访问的响应式值

2. reactive() - 响应式对象

import { reactive } from 'vue'

// 创建响应式对象(只能是对象类型)
const state = reactive({
  count: 0,
  user: {
    name: 'Alice',
    age: 25
  },
  items: ['a', 'b', 'c']
})

// 直接访问属性(不需要 .value)
console.log(state.count)       // 0
state.count = 1                // 响应式更新
state.user.name = 'Bob'        // 嵌套属性也是响应式的

// 但不能替换整个对象
// state = { count: 2 }        // ❌ 错误

// TypeScript 自动推断类型
interface State {
  count: number
  user: { name: string; age: number }
}
const state: State = reactive({ count: 0, user: { name: 'A', age: 20 } })

使用场景

  • 表单对象
  • 复杂的状态对象
  • 嵌套数据结构

⚡ 浅层响应式 API(性能优化)

3. shallowRef() - 浅层响应式引用

import { shallowRef, triggerRef } from 'vue'

// 创建浅层 ref(仅对 .value 替换响应式)
const obj = shallowRef({ count: 0, nested: { value: 1 } })

// 替换整个对象会触发更新
obj.value = { count: 1, nested: { value: 1 } }  // ✅ 触发更新

// 修改嵌套属性不会触发更新
obj.value.count = 2                             // ❌ 不会触发更新
obj.value.nested.value = 3                      // ❌ 不会触发更新

// 手动触发更新
triggerRef(obj)                                 // ✅ 强制触发更新

// 使用场景:大对象或频繁更新的数据
const largeData = shallowRef({
  // 包含大量数据的对象
  items: Array(10000).fill({ /* ... */ }),
  metadata: { /* ... */ }
})

使用场景

  • 大型数据集
  • 第三方库对象(不需要 Vue 追踪)
  • 频繁更新的嵌套对象

4. shallowReactive() - 浅层响应式对象

import { shallowReactive } from 'vue'

// 创建浅层 reactive(仅根级别属性响应式)
const state = shallowReactive({
  count: 0,
  user: {  // 嵌套对象不是响应式的
    name: 'Alice',
    profile: { age: 25 }  // 深度嵌套也不响应
  }
})

// 根级别属性修改会触发更新
state.count = 1           // ✅ 触发更新
state.user = { name: 'Bob' } // ✅ 触发更新(替换整个 user)

// 嵌套属性修改不会触发更新
state.user.name = 'Charlie'   // ❌ 不会触发更新
state.user.profile.age = 26   // ❌ 不会触发更新

// 使用场景:配置对象或不需要深度观察的对象
const config = shallowReactive({
  apiUrl: 'https://api.example.com',
  headers: {  // 通常 headers 配置不变
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token'
  },
  timeout: 5000
})

使用场景

  • 配置对象
  • 包含大量嵌套数据的对象
  • 不希望深度观察的对象

🔧 转换工具 API

5. toRef() - 将属性转换为 ref

import { reactive, toRef } from 'vue'

const state = reactive({
  count: 0,
  name: 'Vue'
})

// 将 reactive 的属性转换为独立的 ref
const countRef = toRef(state, 'count')
const nameRef = toRef(state, 'name')

// 修改 ref 会更新原始对象
countRef.value++                    // state.count 变为 1
console.log(state.count)            // 1

// 也可以用于 props
const props = defineProps({
  id: { type: Number, required: true }
})

const idRef = toRef(props, 'id')    // 保持响应式的 props 引用

使用场景

  • 从 reactive 或 props 中提取单个属性
  • 保持属性的响应式引用

6. toRefs() - 将对象的所有属性转换为 refs

import { reactive, toRefs } from 'vue'

const state = reactive({
  count: 0,
  name: 'Vue',
  user: { id: 1 }
})

// 将所有属性转换为 ref
const refs = toRefs(state)
// refs = { count: Ref<0>, name: Ref<'Vue'>, user: Ref<{id: 1}> }

// 可以在不丢失响应式的情况下解构
const { count, name } = toRefs(state)

// 修改 ref 会更新原始对象
count.value++                       // state.count 变为 1

// 在组合式函数中非常有用
function useFeature() {
  const state = reactive({ x: 0, y: 0 })
  const update = () => { state.x++; state.y++ }
  
  return { ...toRefs(state), update }  // 返回可解构的响应式数据
}

// 使用
const { x, y, update } = useFeature()

使用场景

  • 组合式函数的返回值
  • 解构 reactive 对象
  • 模板中直接使用解构的属性

7. unref() - 解包 ref 或返回原始值

import { ref, unref } from 'vue'

const count = ref(0)
const plain = 42

// 如果参数是 ref,返回 .value
console.log(unref(count))          // 0
console.log(unref(plain))          // 42(如果不是 ref,直接返回)

// 等同于
const value = isRef(val) ? val.value : val

// 实用场景:编写接受 ref 或原始值的函数
function double(value) {
  return unref(value) * 2
}

console.log(double(count))         // 0 * 2 = 0
console.log(double(10))            // 10 * 2 = 20

// 在计算属性中使用
import { computed } from 'vue'
const doubleCount = computed(() => unref(count) * 2)

使用场景

  • 编写灵活的函数(可接受 ref 或原始值)
  • 简化 ref 的访问
  • 工具函数中处理可能的 ref 值

🔍 检查工具 API

8. isRef() - 检查是否为 ref

import { ref, reactive, isRef } from 'vue'

const r = ref(0)
const obj = reactive({ count: 0 })
const plain = 42

console.log(isRef(r))              // true
console.log(isRef(obj.count))      // false
console.log(isRef(plain))          // false

// 实际应用:安全地访问 ref
function getValue(value) {
  if (isRef(value)) {
    return value.value
  }
  return value
}

// 或使用 unref 简化
function getValue(value) {
  return unref(value)
}

9. isReactive() - 检查是否为 reactive 对象

import { reactive, readonly, ref, isReactive } from 'vue'

const state = reactive({ count: 0 })
const roState = readonly(state)
const r = ref({ count: 0 })
const shallow = shallowReactive({ count: 0 })

console.log(isReactive(state))     // true
console.log(isReactive(roState))   // true(只读的也是 reactive)
console.log(isReactive(r.value))   // false(ref 包装的对象)
console.log(isReactive(shallow))   // true(浅层也是 reactive)

// 检查 ref 内部的值
console.log(isReactive(unref(r)))  // false(ref 解包后是普通对象)

10. isReadonly() - 检查是否为只读对象

import { reactive, readonly, ref, isReadonly } from 'vue'

const state = reactive({ count: 0 })
const roState = readonly(state)
const roRef = readonly(ref(0))
const plain = { count: 0 }

console.log(isReadonly(state))     // false
console.log(isReadonly(roState))   // true
console.log(isReadonly(roRef))     // true(只读 ref)
console.log(isReadonly(plain))     // false

// 实际应用:防止意外修改
function safeUpdate(obj, key, value) {
  if (isReadonly(obj)) {
    console.warn('Cannot update readonly object')
    return
  }
  obj[key] = value
}

11. isProxy() - 检查是否为代理对象

import { reactive, readonly, ref, isProxy } from 'vue'

const state = reactive({ count: 0 })
const roState = readonly(state)
const r = ref(0)
const plain = { count: 0 }

console.log(isProxy(state))        // true
console.log(isProxy(roState))      // true
console.log(isProxy(r))            // false(ref 不是 Proxy)
console.log(isProxy(plain))        // false

// isProxy 检查是否是 reactive、readonly、shallowReactive、shallowReadonly
const shallow = shallowReactive({})
const shallowRo = shallowReadonly({})
console.log(isProxy(shallow))      // true
console.log(isProxy(shallowRo))    // true

🎨 实际应用模式

模式1:安全的数据访问器

import { unref, isRef, isReactive, isReadonly } from 'vue'

// 安全的属性访问器
export function getSafeValue(source, key) {
  // 解包可能的 ref
  const obj = unref(source)
  
  if (!obj || typeof obj !== 'object') {
    return undefined
  }
  
  // 检查是否可写
  if (isReadonly(obj)) {
    console.warn(`Attempting to read from readonly object: ${key}`)
  }
  
  return obj[key]
}

// 安全的属性设置器
export function setSafeValue(source, key, value) {
  const obj = unref(source)
  
  if (!obj || typeof obj !== 'object') {
    throw new Error('Source is not an object')
  }
  
  if (isReadonly(obj)) {
    throw new Error('Cannot modify readonly object')
  }
  
  obj[key] = value
  return true
}

模式2:响应式数据验证器

import { 
  ref, reactive, 
  isRef, isReactive, 
  unref, toRefs 
} from 'vue'

// 验证并标准化响应式数据
export function normalizeReactiveData(input) {
  // 如果是 ref,返回解包后的值
  if (isRef(input)) {
    console.log('Input is a ref, unwrapping...')
    return unref(input)
  }
  
  // 如果是 reactive,转换为普通对象(失去响应式)
  if (isReactive(input)) {
    console.log('Input is reactive, converting to plain object...')
    return { ...input }
  }
  
  // 否则直接返回
  return input
}

// 创建安全的响应式状态
export function createSafeState(initialState) {
  // 深度响应式
  const state = reactive({
    data: initialState,
    lastUpdated: null,
    error: null
  })
  
  // 返回只读接口和更新方法
  return {
    // 只读数据
    get data() {
      return readonly(state.data)
    },
    get lastUpdated() {
      return state.lastUpdated
    },
    get error() {
      return state.error
    },
    
    // 更新方法
    update(updater) {
      try {
        const result = updater(state.data)
        if (result !== undefined) {
          state.data = result
        }
        state.lastUpdated = new Date()
        state.error = null
      } catch (err) {
        state.error = err.message
        throw err
      }
    },
    
    // 重置方法
    reset() {
      state.data = initialState
      state.lastUpdated = null
      state.error = null
    }
  }
}

模式3:组合式函数的响应式处理

import { 
  ref, reactive, 
  toRefs, unref,
  isRef, isReactive 
} from 'vue'

// 通用的响应式参数处理
export function useComposableLogic(input) {
  // 处理可能的 ref 输入
  const inputValue = unref(input)
  
  // 创建响应式状态
  const state = reactive({
    processedData: null,
    isLoading: false,
    error: null
  })
  
  // 处理函数
  const process = async () => {
    state.isLoading = true
    state.error = null
    
    try {
      // 实际处理逻辑
      state.processedData = await someAsyncOperation(inputValue)
    } catch (err) {
      state.error = err.message
    } finally {
      state.isLoading = false
    }
  }
  
  // 返回响应式引用
  return {
    // 使用 toRefs 保持响应式
    ...toRefs(state),
    process,
    
    // 原始状态的只读视图
    get rawState() {
      return readonly(state)
    }
  }
}

📊 API 对比总结表

API输入类型输出类型深度响应式可写主要用途
ref()任意Ref<T>✅ 深度基本类型、任意值包装
reactive()对象T✅ 深度复杂对象状态
shallowRef()任意Ref<T>❌ 浅层大型对象、性能优化
shallowReactive()对象T❌ 浅层配置对象、浅层观察
readonly()任意T✅ 深度保护数据不被修改
toRef()对象, 属性名Ref<T>保持原样提取单个属性
toRefs()对象{ [K]: Ref<T> }保持原样解构对象
unref()任意T--解包 ref
isRef()任意boolean--检查 ref
isReactive()任意boolean--检查 reactive
isReadonly()任意boolean--检查 readonly
isProxy()任意boolean--检查代理对象

🚀 最佳实践指南

1. 选择正确的 API

// 场景:基本类型值
const count = ref(0)                    // ✅ 推荐
const count = reactive({ value: 0 })    // ❌ 过度设计

// 场景:复杂对象
const form = reactive({                 // ✅ 推荐
  name: '',
  email: '',
  age: null
})

const form = ref({                      // ❌ 需要 .value 访问
  name: '',
  email: ''
})

// 场景:大型嵌套对象
const largeConfig = shallowReactive({   // ✅ 性能优化
  // ... 大量嵌套数据
})

2. 正确处理解构

import { reactive, toRefs } from 'vue'

// ❌ 错误:直接解构会丢失响应式
const state = reactive({ x: 0, y: 0 })
const { x, y } = state  // x, y 不是响应式的

// ✅ 正确:使用 toRefs 保持响应式
const { x, y } = toRefs(state)  // x, y 是响应式的 ref

// ✅ 正确:在组合式函数中
function usePosition() {
  const state = reactive({ x: 0, y: 0 })
  return { ...toRefs(state) }
}

3. 使用 unref 编写灵活的函数

// ❌ 不灵活:只能接受 ref
function double(refValue) {
  return refValue.value * 2
}

// ✅ 灵活:接受 ref 或原始值
function double(value) {
  return unref(value) * 2
}

// 都可以使用
double(ref(10))    // 20
double(10)         // 20

4. 性能优化技巧

// 1. 使用 shallowRef 处理大对象
const largeData = shallowRef(/* 大数据 */)

// 2. 批量更新避免频繁触发
const items = ref([])
// ❌ 不佳:每次 push 都触发更新
for (let i = 0; i < 1000; i++) {
  items.value.push(i)
}

// ✅ 优化:批量更新
items.value = Array.from({ length: 1000 }, (_, i) => i)

// 3. 避免不必要的深度响应式
const config = shallowReactive({
  // 不变的配置
})

🔧 调试技巧

检查响应式状态

import { 
  ref, reactive, 
  isRef, isReactive, 
  isProxy, unref 
} from 'vue'

function debugReactive(obj, name = 'object') {
  console.group(`Debug: ${name}`)
  console.log('Value:', unref(obj))
  console.log('isRef:', isRef(obj))
  console.log('isReactive:', isReactive(unref(obj)))
  console.log('isProxy:', isProxy(unref(obj)))
  console.groupEnd()
}

// 使用
const state = reactive({ count: 0 })
debugReactive(state, 'state')

通过掌握这些 API,你可以更精细地控制 Vue 的响应式系统,在性能、灵活性和开发体验之间找到最佳平衡。