Vue3 响应式API-扩展方法

850 阅读4分钟

这是我参与8月更文挑战的第3天,活动详情查看: 8月更文挑战

什么是响应式

响应性是一种允许我们以声明式的方式去适应变化的编程范例。
当一个值依附另一个值进行计算时, 如果前一个值发生了变化, 依附的那个也会同时进行变更.
典型案例参见 excel 函数. 当单元格A3=SUM(A1:A2)时, 此时更改A1或A2任意一个发生改变时, A3会立即作出响应更新.

响应式是Vue3的核心API。
官方也把此响应式API进行单独打包,即便不使用Vue3的情况下也可以独立使用:@vue/reactivity

响应式API-扩展方法

Vue3核心响应式方法有两种: 一个是 ref 它是对单个值创建的响应式, 另一个 reactive 它是对一个对象创建的深度响应式.

在前面章节介绍了响应式API的核心方法, 本章介绍围绕核心方法的扩展.

响应式有6个扩展方法:

  • readonly()
  • isReadonly()
  • toRaw()
  • markRaw()
  • shallowReactive()
  • shallowReadonly()

readonly 创建只读对象

只读对象顾名思义一个不可变更的对象(禁止更改属性值的对象).
受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用于响应性追踪
  console.log(copy.count)
})

// 变更 original 会触发依赖于副本的侦听器
original.count++

// 变更副本将失败并导致警告
copy.count++ // 警告!

与 reactive 一样,如果任何 property 使用了 ref,当它通过代理访问时,则被自动解包:

const raw = {
  count: ref(123)
}

const copy = readonly(raw)

console.log(raw.count.value) // 123
console.log(copy.count) // 123

Vue Options API中 props 就是一个只读对象,利用只读的特性实现只允许向下传递数据.

isReadonly 检查是否是只读对象

检查对象是否是由 readonly 创建的只读代理。

const raw = {
  name: "Jerry"
}

const reactiveRaw = reactive(raw)
const readonlyRaw = readonly(raw)

console.log(isReadonly(raw)) // false
console.log(isReadonly(reactiveRaw)) // false
console.log(isReadonly(readonlyRaw)) // true

toRaw 返回原始对象

返回 reactive 或 readonly 代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。建议保留对原始对象的持久引用。请谨慎使用。

const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true

一般很少会使用这个方法, 除非想保留当前响应式下的状态快照兴许能用到该方法.

markRaw 禁止创建响应式

标记一个对象,使其永远不会转换为 proxy。返回对象本身。

const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 嵌套在其他响应式对象中时也可以使用
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

emmmm...目前只能标记对象没有取消标记对象配套的方法.
例如想在vue下兼容其他框架, 但那个框架又不应该使用响应式可以这么做, 案例: 地图API.
所以, 一定要在当前使用场景对具体的一些明确使用用途的对象谨慎标记.

shallowReactive

创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 改变 state 本身的性质是响应式的
state.foo++
// ...但是不转换嵌套对象
isReactive(state.nested) // false
state.nested.bar++ // 非响应式

与 reactive 不同,任何使用 ref 的 property 都不会被代理自动解包。

shallowReadonly

创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

// 改变 state 本身的 property 将失败
state.foo++
// ...但适用于嵌套对象
isReadonly(state.nested) // false
state.nested.bar++ // 适用

官方对 markRawshallowXXX 的重要说明

markRaw 和 shallowXXX 方法 使你可以有选择地退出默认的深度响应式/只读转换模式,并将原始的,未被代理的对象嵌入状态图中。它们可以根据情况灵活运用:

  • 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
  • 当渲染具有不可变数据源的大列表时,跳过 proxy 转换可以提高性能。

这些例子是进阶的运用,因为原始选择退出仅在根级别,因此,如果将嵌套在内的、未标记的原始对象添加进响应式对象,然后再次访问该响应式对象,就会得到原始对象被代理后的版本。这可能会导致同一性风险——即执行一个依赖于对象本身的操作,但同时使用同一对象的原始版本和被代理后的版本:

const foo = markRaw({
  nested: {}
})

const bar = reactive({
  // 虽然 `foo` 被标记为原始,但 foo.nested 不是。
  nested: foo.nested
})

console.log(foo.nested === bar.nested) // false

同一性风险通常很少见。然而,为了正确地使用这些 API,同时安全地避免同一性风险,就需要对响应性系统的工作原理有一个充分的理解。

文献