# 响应式基础 API
本节使用单文件组件语法作为代码示例
# reactive
返回对象的响应式副本
const obj = reactive({ count: 0 })
响应式转换是“深层的” —— 会影响对象内部所有嵌套的属性。基于 ES2015 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。
类型定义:
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
# readonly
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”:对象内部任何嵌套的属性也都是只读的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
2
3
4
5
6
7
8
9
10
11
12
13
14
# isProxy
检查一个对象是否是由 reactive
或者 readonly
方法创建的代理。
# isReactive
检查一个对象是否是由 reactive
创建的响应式代理。
import { reactive, isReactive } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John',
})
console.log(isReactive(state)) // -> true
},
}
2
3
4
5
6
7
8
9
如果这个代理是由 readonly
创建的,但是又被 reactive
创建的另一个代理包裹了一层,那么同样也会返回 true
。
import { reactive, isReactive, readonly } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John',
})
// 从普通对象创建的只读代理
const plain = readonly({
name: 'Mary',
})
console.log(isReactive(plain)) // -> false
// 从响应式代理创建的只读代理
const stateCopy = readonly(state)
console.log(isReactive(stateCopy)) // -> true
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# isReadonly
检查一个对象是否是由 readonly
创建的只读代理。
# toRaw
返回由 reactive
或 readonly
方法转换成响应式代理的普通对象。这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发更改。不建议一直持有原始对象的引用。请谨慎使用。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
2
3
4
# markRaw
显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
2
3
4
5
6
注意
markRaw
和下面的 shallowXXX 一族的 API 允许你可选择性的覆盖 reactive readonly 默认 "深层的" 特性,或者使用无代理的普通对象。设计这种「浅层读取」有很多原因,比如:
一些值的实际上的用法非常简单,并没有必要转为响应式,比如某个复杂的第三方类库的实例,或者 Vue 组件对象。
当渲染一个元素数量庞大,但是数据是不可变的,跳过 Proxy 的转换可以带来性能提升。
这些 API 被认为是高级的,是因为这种特性仅停留在根级别,所以如果你将一个嵌套的,没有 markRaw
的对象设置为 reactive 对象的属性,在重新访问时,你又会得到一个 Proxy 的版本,在使用中最终会导致标识混淆的严重问题:执行某个操作同时依赖于某个对象的原始版本和代理版本。
const foo = markRaw({
nested: {},
})
const bar = reactive({
// 尽管 `foo` 己经被标记为 raw 了, 但 foo.nested 并没有
nested: foo.nested,
})
console.log(foo.nested === bar.nested) // false
2
3
4
5
6
7
8
9
10
标识混淆在一般使用当中应该是非常罕见的,但是要想完全避免这样的问题,必须要对整个响应式系统的工作原理有一个相当清晰的认知。
# shallowReactive
只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样。
const state = shallowReactive({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性是响应式的
state.foo++
// ...但不会深层代理
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
2
3
4
5
6
7
8
9
10
11
12
# shallowReadonly
只为某个对象的自有(第一层)属性创建浅层的只读响应式代理,同样也不会做深层次、递归地代理,深层次的属性并不是只读的。
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2,
},
})
// 变更 state 的自有属性会失败
state.foo++
// ...但是嵌套的对象是可以变更的
isReadonly(state.nested) // false
state.nested.bar++ // 嵌套属性依然可修改
2
3
4
5
6
7
8
9
10
11
12