响应性 API:进阶 {#reactivity-api-advanced}

shallowRef() {#shallowref}

ref() 的浅层作用形式。

  • 类型

    1. function shallowRef<T>(value: T): ShallowRef<T>
    2. interface ShallowRef<T> {
    3. value: T
    4. }
  • 详细信息

    ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

    shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。

  • 示例

    1. const state = shallowRef({ count: 1 })
    2. // 不会触发更改
    3. state.value.count = 2
    4. // 会触发更改
    5. state.value = { count: 2 }
  • 相关内容:

triggerRef() {#triggerref}

强制触发依赖于一个 浅层 ref 的副作用,这常常用在对浅层 ref 的内部值做了变更之后。

  • 类型

    1. function triggerRef(ref: ShallowRef): void
  • 示例

    1. const shallow = shallowRef({
    2. greet: 'Hello, world'
    3. })
    4. // 触发该副作用第一次应该会打印 "Hello, world"
    5. watchEffect(() => {
    6. console.log(shallow.value.greet)
    7. })
    8. // 这次变更不应触发副作用,因为这个 ref 是浅层的
    9. shallow.value.greet = 'Hello, universe'
    10. // 打印 "Hello, universe"
    11. triggerRef(shallow)

customRef() {#customref}

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

  • 类型

    1. function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
    2. type CustomRefFactory<T> = (
    3. track: () => void,
    4. trigger: () => void
    5. ) => {
    6. get: () => T
    7. set: (value: T) => void
    8. }
  • 详细信息

    customRef() 预期接受一个工厂函数,这个工厂函数接受 tracktrigger 两个函数作为参数,并应该返回一个带 getset 方法的对象。

    一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而,你对它们该怎么调用、需不需要调用都有完全的控制权。

  • 示例

    创建一个防抖 ref,即仅在上一次 set 调用后的一端固定间隔后再调用:

    1. import { customRef } from 'vue'
    2. export function useDebouncedRef(value, delay = 200) {
    3. let timeout
    4. return customRef((track, trigger) => {
    5. return {
    6. get() {
    7. track()
    8. return value
    9. },
    10. set(newValue) {
    11. clearTimeout(timeout)
    12. timeout = setTimeout(() => {
    13. value = newValue
    14. trigger()
    15. }, delay)
    16. }
    17. }
    18. })
    19. }

    在组件中使用:

    1. <script setup>
    2. import { useDebouncedRef } from './debouncedRef'
    3. const text = useDebouncedRef('hello')
    4. </script>
    5. <template>
    6. <input v-model="text" />
    7. </template>

    在 Playground 中尝试一下

shallowReactive() {#shallowreactive}

reactive() 的浅层作用形式。

  • 类型

    1. function shallowReactive<T extends object>(target: T): T
  • 详细信息

    reactive()不同,这里不会有深层次转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着属性为 ref 的值 不会 被自动解包了。

    :::warning 谨慎使用 浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它会创建一个具有不一致的响应行为的树,这可能很难理解和调试。 :::

  • 示例

    1. const state = shallowReactive({
    2. foo: 1,
    3. nested: {
    4. bar: 2
    5. }
    6. })
    7. // 更改状态自身的属性是响应式的
    8. state.foo++
    9. // ...但下层嵌套对象不会被转为响应式
    10. isReactive(state.nested) // false
    11. // 不是响应式的
    12. state.nested.bar++

shallowReadonly() {#shallowreadonly}

readonly() 的浅层作用形式

  • 类型

    1. function shallowReadonly<T extends object>(target: T): Readonly<T>
  • 详细信息

    readonly() 不同,这里没有深层级的转换:只有跟根层级的属性变为了只读。属性值都会被原样存储和暴露,这也意味着值为 ref 的属性 不会 被自动解包了。

    :::warning 谨慎使用 浅层数据结构应该只用于组件中的根级状态。请避免将其嵌套在深层次的响应式对象中,因为它会创建一个具有不一致的响应行为的树,这可能很难理解和调试。 :::

  • 示例

    1. const state = shallowReadonly({
    2. foo: 1,
    3. nested: {
    4. bar: 2
    5. }
    6. })
    7. // 更改状态自身的属性会失败
    8. state.foo++
    9. // ...但可以更改下层嵌套对象
    10. isReadonly(state.nested) // false
    11. // 这是可以通过的
    12. state.nested.bar++

toRaw() {#toraw}

根据一个 Vue 创建的代理返回其原始对象。

  • 类型

    1. function toRaw<T>(proxy: T): T
  • 详细信息

    toRaw() 可以返回由 reactive()readonly()shallowReactive() 或者 shallowReadonly() 创建的代理对应的源对象。

    这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。

  • 示例

    1. const foo = {}
    2. const reactiveFoo = reactive(foo)
    3. console.log(toRaw(reactiveFoo) === foo) // true

markRaw() {#markraw}

将一个对象标记为不可被转为代理的对象。返回也是该对象。

  • 类型

    1. function markRaw<T extends object>(value: T): T
  • 示例

    1. const foo = markRaw({})
    2. console.log(isReactive(reactive(foo))) // false
    3. // 也适用于嵌套在其他响应性对象
    4. const bar = reactive({ foo })
    5. console.log(isReactive(bar.foo)) // false

    :::warning 谨慎使用 markRaw()shallowReactive() 这样的浅层式 API 是你可以有选择地 有选择地避开默认的深度响应/只读转换,并在状态关系谱中嵌入原始的、非代理的对象。它们可能用于各种各样的原因:

    • 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。

    • 当呈现带有不可变数据源的大型列表时,跳过代理转换可以提供性能改进。

    这应该是一种进阶需求,因为只在根层访问能到原始值,所以如果你把一个嵌套的、没有标记的原始对象设置成一个反应式对象,然后再次访问它,你会得到代理的版本回来。这可能会导致识别风险,即是说执行一个依赖于对象标识的操作,但同时使用同一对象的原始版本和代理版本:

    1. const foo = markRaw({
    2. nested: {}
    3. })
    4. const bar = reactive({
    5. // 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有
    6. nested: foo.nested
    7. })
    8. console.log(foo.nested === bar.nested) // false

    识别风险一般是很罕见的。然而,要正确使用这些 API,同时安全地避免这样的风险,需要你对响应性系统的工作方式有充分的了解。

    :::

effectScope() {#effectscope}

创建一个 effect 作用域,可以捕获其中所创建的响应式依赖(例如计算属性和侦听器),这样捕获到的作用都可以一起处理。对于该 API 的使用细节,请查阅对应的 RFC

  • 类型

    1. function effectScope(detached?: boolean): EffectScope
    2. interface EffectScope {
    3. run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefined
    4. stop(): void
    5. }
  • 示例

    1. const scope = effectScope()
    2. scope.run(() => {
    3. const doubled = computed(() => counter.value * 2)
    4. watch(doubled, () => console.log(doubled.value))
    5. watchEffect(() => console.log('Count: ', doubled.value))
    6. })
    7. // 处理掉当前作用域内的所有 effect
    8. scope.stop()

getCurrentScope() {#getcurrentscope}

如果有的话,返回当前活跃的 effect 作用域

  • 类型

    1. function getCurrentScope(): EffectScope | undefined

onScopeDispose() {#onscopedispose}

在当前活动的 effect 作用域 上注册一个处置回调。这个回调函数会在相关的副作用范围停止时被调用。

这个方法可以作为可重用的组合式函数中 onUnmounted 的替代品,它并不与组件耦合,而每一个 Vue 组件的 setup() 函数也是在一个 effect 作用域中调用的。

  • 类型

    1. function onScopeDispose(fn: () => void): void