reactive.ts详解
1. APIs
在reactive.ts里,主要提供了四个API和一些其他方法
API | 功能 | 使用场景 |
---|---|---|
reactive | 将普通对象转换为响应式对象。这个转换是深层的,即对象内部所有嵌套的属性也都会变成响应式的。 | 当你需要一个深层响应式的对象,以便在对象或其任何嵌套属性改变时更新UI。常用于存储和管理组件的状态。 |
shallowReactive | 将普通对象转换为响应式对象,但转换仅限于对象的第一层属性。 | 当你只需要对象的顶层属性是响应式的,而嵌套对象不需要转换为响应式。这可以在处理大型对象或深层嵌套对象时减少性能开销。 |
readonly | 将普通对象转换为只读的响应式对象。这个转换是深层的,即对象内部所有嵌套的属性也都会变成只读的。 | 用于创建一个不应该被修改的响应式对象,常用于将状态传递给子组件时,确保子组件不能修改这些状态。 |
shallowReadonly | 将普通对象转换为只读的响应式对象,但转换仅限于对象的第一层属性。 | 当你想要保护对象的顶层属性不被修改,同时不需要对象内部嵌套的属性是只读的。适用于优化性能,特别是当嵌套属性很大时。 |
本文以reactive API来说明在vue底层是如何实现的
2. 使用示例
在页面上显示count值,【+1】按钮修改count值
3. 调用reactive时,发生了什么?
查看源码 @vue/reactivity/reactive.ts,首先解读注释内容:
3.1 返回一个对象的响应式代理:
使用reactive函数时,它会返回原始对象的一个代理(Proxy)。这个代理对象能够拦截对原始对象的各种操作(如属性读取、写入等),使Vue能够追踪这些操作,并在必要时自动更新UI。
3.2 响应式转换是“深度”的:
当你对一个对象使用reactive时,不仅该对象本身变为响应式的,它的所有嵌套属性也都将递归地被转换为响应式的。这意味着,无论对象结构有多深,Vue都能追踪到任何属性的变化,并在属性值改变
3.3 深度展开ref属性并保持响应性:
如果对象的某个属性是一个ref(引用),在通过reactive转换的响应式对象中访问该属性时,你将直接得到ref内部的值,而不需要使用.value来访问。这个过程称为“深度展开”或“自动解引用”。重要的是,尽管ref被自动解引用,但对这些值的读取和修改仍然是响应式的,Vue会追踪这些操作并在值变化时更新UI。
源代码解读
/**
* 返回一个对象的响应式代理
* 响应式转换是“深度”的:它的所有嵌套属性也都将递归地被转换为响应式的
* 深度展开ref属性并保持响应性:如果对象的某个属性是一个ref(引用),
* 在通过reactive转换的响应式对象中访问该属性时,你将直接得到ref内部的值,
* 而不需要使用.value来访问
*/
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
//如果尝试将一个已经是只读代理(readonly proxy)的对象转换为响应式对象,则直接返回这个只读版本的对象。
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
4. 创建Proxy: createReactiveObject()
这个函数是创建响应式代理对象(包括响应式、只读、浅响应式、浅只读)的底层逻辑。它根据传入的参数决定代理对象的行为
function createReactiveObject(
target: Target, //要被代理的原始对象
isReadonly: boolean, //创建的代理将是否是只读的
baseHandlers: ProxyHandler<any>, //用于普通对象的Proxy处理器
collectionHandlers: ProxyHandler<any>, //用于集合类型对象的Proxy处理器
proxyMap: WeakMap<Target, any>, //用于存储原始对象与其代理之间的映射
) {
// 确保target是对象
if (!isObject(target)) {
if (__DEV__) {
warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 如果target已经是代理对象(通过target上的[ReactiveFlags.RAW]属性来判断),直接返回:
// 例外: 对于在reactive对象上的readonly调用是个例外
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 检查是否已存在代理
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 检查target类型是否有效(只有特定的对象类型可以被观察)
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建代理
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
5. 小结
总之,reactive.ts的核心功能就是返回一个proxy,而这个proxy具体是如何实现响应式,请看后续章节。