# 响应式基础 API

本节使用单文件组件语法作为代码示例

# reactive

返回对象的响应式副本

const obj = reactive({ count: 0 })
1

响应式转换是“深层的” —— 会影响对象内部所有嵌套的属性。基于 ES2015 Proxy 实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。

类型定义:

function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
1

# readonly

传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”:对象内部任何嵌套的属性也都是只读的。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
	// 依赖追踪
	console.log(copy.count)
})

// original 上的修改会触发 copy 上的侦听
original.count++

// 无法修改 copy 并会被警告
copy.count++ // warning!
1
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
	},
}
1
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
	},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# isReadonly

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

# toRaw

返回由 reactivereadonly 方法转换成响应式代理的普通对象。这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发更改。不建议一直持有原始对象的引用。请谨慎使用。

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

console.log(toRaw(reactiveFoo) === foo) // true
1
2
3
4

# markRaw

显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。

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

// 如果被 markRaw 标记了,即使在响应式对象中作属性,也依然不是响应式的
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
1
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
1
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++ // 非响应式
1
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++ // 嵌套属性依然可修改
1
2
3
4
5
6
7
8
9
10
11
12