阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 响应式工具函数(toRef/toRefs/isProxy等)

响应式工具函数(toRef/toRefs/isProxy等)

作者:陈川 阅读数:55650人阅读 分类: Vue.js

Vue 3 的响应式系统带来了几个关键工具函数,它们能更精细地控制响应式数据的行为。toReftoRefsisProxy 等函数在组合式 API 中频繁出现,理解它们的特性和使用场景能显著提升开发效率。

toRef:创建响应式引用

toRef 用于从响应式对象的属性创建独立的 ref 引用。这个 ref 会与源属性保持同步,修改 ref 会更新源对象,反之亦然。

import { reactive, toRef } from 'vue'

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

const countRef = toRef(state, 'count')

// 修改 ref 会影响源对象
countRef.value++
console.log(state.count) // 输出 1

// 修改源对象也会更新 ref
state.count = 42
console.log(countRef.value) // 输出 42

典型使用场景是在解构响应式对象时保持响应性:

// 错误做法:直接解构会丢失响应性
const { count } = state

// 正确做法:使用 toRef 保持响应性
const count = toRef(state, 'count')

toRefs:批量创建响应式引用

toRefstoRef 的批量版本,它会将响应式对象的所有属性转换为 ref 对象集合。

import { reactive, toRefs } from 'vue'

const state = reactive({
  x: 10,
  y: 20
})

const refs = toRefs(state)

// refs 现在是 { x: Ref<number>, y: Ref<number> }
console.log(refs.x.value) // 10
console.log(refs.y.value) // 20

在组合式函数中返回响应式对象时,toRefs 特别有用:

function usePosition() {
  const pos = reactive({
    x: 0,
    y: 0
  })

  // 更新位置的方法
  function update(newX, newY) {
    pos.x = newX
    pos.y = newY
  }

  // 返回解构后的响应式引用
  return { ...toRefs(pos), update }
}

// 使用时可以解构而不丢失响应性
const { x, y, update } = usePosition()

isProxy:检查代理对象

isProxy 用于检查一个对象是否是由 reactivereadonly 创建的代理。

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

const reactiveObj = reactive({})
const readonlyObj = readonly({})
const plainObj = {}

console.log(isProxy(reactiveObj)) // true
console.log(isProxy(readonlyObj)) // true
console.log(isProxy(plainObj)) // false

这个函数在开发工具或库时特别有用,可以用来验证参数是否为预期的响应式对象。

isReactive 和 isReadonly

这两个函数提供了更具体的代理类型检查:

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

const reactiveObj = reactive({})
const readonlyObj = readonly({})
const readonlyReactive = readonly(reactive({}))

console.log(isReactive(reactiveObj)) // true
console.log(isReactive(readonlyReactive)) // true
console.log(isReadonly(readonlyObj)) // true
console.log(isReadonly(readonlyReactive)) // true

注意 readonly(reactive(obj)) 会同时返回 isReactiveisReadonly 为 true。

shallowReactive 和 shallowRef

对于不需要深度响应式的场景,Vue 提供了浅层响应式版本:

import { shallowReactive, shallowRef } from 'vue'

const shallowState = shallowReactive({
  nested: {
    count: 0
  }
})

// 顶层属性是响应式的
shallowState.nested = { count: 1 } // 触发响应

// 嵌套属性不是响应式的
shallowState.nested.count++ // 不会触发响应

const shallow = shallowRef({ count: 0 })

// .value 的变化会触发响应
shallow.value = { count: 1 } // 触发响应

// 嵌套属性的变化不会
shallow.value.count++ // 不会触发响应

customRef:创建自定义 ref

customRef 允许创建具有自定义 getter 和 setter 的 ref:

import { customRef } from 'vue'

function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

// 使用
const text = useDebouncedRef('hello')

// 快速连续修改只会触发一次更新
text.value = 'h'
text.value = 'he'
text.value = 'hel'
// 200ms 后最终值会更新为 'hel'

triggerRef:强制触发 shallowRef 更新

对于 shallowRef,直接修改嵌套属性不会触发更新,这时可以使用 triggerRef

import { shallowRef, triggerRef } from 'vue'

const shallow = shallowRef({ count: 0 })

// 修改嵌套属性
shallow.value.count++

// 手动触发更新
triggerRef(shallow)

markRaw 和 toRaw

有时我们需要确保某些对象永远不会被转为代理:

import { reactive, markRaw, toRaw } from 'vue'

const obj = { foo: 1 }
const marked = markRaw(obj)

const reactiveObj = reactive({ nested: marked })

console.log(isReactive(reactiveObj.nested)) // false

// 获取原始对象
const raw = toRaw(reactiveObj)
console.log(raw === obj) // true

这在集成第三方库或性能敏感场景下很有用。

响应式工具函数的实际应用

这些工具函数在构建可组合逻辑时特别强大。例如,实现一个共享状态管理:

import { reactive, toRefs, computed } from 'vue'

function createStore() {
  const state = reactive({
    count: 0,
    double: computed(() => state.count * 2)
  })

  function increment() {
    state.count++
  }

  return { ...toRefs(state), increment }
}

// 在多个组件中共享同一状态
const store = createStore()

// 组件A
const { count, double, increment } = store

// 组件B
const { count: otherCount } = store

响应式工具与 TypeScript

这些工具函数与 TypeScript 配合良好,能提供完善的类型推断:

import { reactive, toRef } from 'vue'

interface User {
  id: number
  name: string
}

const user = reactive<User>({
  id: 1,
  name: 'Alice'
})

const nameRef = toRef(user, 'name') // Ref<string>

性能考量

虽然响应式系统很高效,但在某些情况下需要注意:

  1. 大型响应式对象使用 shallowReactiveshallowRef
  2. 频繁更新的数据考虑使用原始值 + ref
  3. 不变的静态数据使用 markRaw 避免不必要的代理
const heavyArray = markRaw([...]) // 避免数千个元素的数组被代理

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌