作用
reactive
和 ref
一样都是用于声明响应式状态的方式
局限性
reactive
只能用于对象类型(对象、数组和像Map
、Set
这样的集合类型),不可以对基本数据类型进行响应式处理
使用
这里摘取官网的案例
js
// 在模版中
<button @click="state.count++"> {{ state.count }} </button>
// js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
源码解析
让我们看看const state = reactive({ count: 0 })
这一行代码发生了什么?
js
export function reactive(target: object) {
// * 如果传入的target是只读,则直接返回
if (isReadonly(target)) {
return target
}
// * 返回一个代理对象
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
- 这里我们可以看出执行
reactive
函数- 会先检查传入的值是否是只读,是的话则直接返回
target
target
非只读,则会返回createReactiveObject
函数的返回值,并传入四个参数
- 会先检查传入的值是否是只读,是的话则直接返回
接下来我们解析下createReactiveObject
函数内部
js
function createReactiveObject(
target: Target,
isReadonly: boolean, // * 是否是只读的,初次调用时为false
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// * 如果target不是一个对象则直接返回
if (!isObject(target)) {
if (__DEV__) {
// * 如果处于开发环境则打印警告
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// * target[ReactiveFlags.RAW] 为true说明target已经被包装过了,是一个响应式对象的原始版本
// * (isReadonly && target[ReactiveFlags.IS_REACTIVE]) 是否是只读的响应式对象
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// * 在proxyMap中查找是否已经有target的代理对象
const existingProxy = proxyMap.get(target)
// * 如果已经有了则直接返回
if (existingProxy) {
return existingProxy
}
// * 判断target是否是一个可以被监听的对象
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
// * 如果target是一个不可以监听的对象则返回
return target
}
// * 创建一个代理对象,TargetType.COLLECTION表示target是一个集合对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
// * TargetType.COLLECTION 为true则代表是一个集合类型的对象
)
// * 将target和proxy存入proxyMap中
proxyMap.set(target, proxy)
// * 返回代理对象
return proxy
}
-
createReactiveObject
接受四个参数target
:需要进行代理的对象也就是{count: 0}
isReadonly
:是否只读baseHandlers
:非集合类型的handlercollectionHandlers
:集合类型使用的handlerproxyMap
:存储target的代理对象
-
其本质就是查询是否存在
target
的代理对象,如果有则返回,没有,则重新创建一个代理对象,并将新创建的代理对象存入reactiveMap
中
接下来解析下返回出去的代理对象proxy
const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers, )
js
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _shallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object) {
const isReadonly = this._isReadonly,
shallow = this._shallow
// * 传入的值是非响应式,下面的判断不会被触发
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (key === ReactiveFlags.RAW) {
if (
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the reciever is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// early return undefined
return
}
// * targetIsArray: target是否是数组
const targetIsArray = isArray(target)
// * 是否只读
if (!isReadonly) {
// * 如果是数组,且key是数组的索引
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// * Reflect.get(target, key, receiver) 会触发getter,返回target[key]
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// * 依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
// * 如果返回值是对象,且不是只读的,递归包装成响应式对象
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
// * 1.继承了BaseReactiveHandler实现get方法
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(shallow = false) {
super(false, shallow)
}
set(
target: object,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
// * 存储旧值
let oldValue = (target as any)[key]
if (!this._shallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
// * 旧值和新值都转换成原始值
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// * 如果不是数组、旧值是ref类型、新值不是ref类型
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
return false
} else {
oldValue.value = value
return true
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 查看是否有key
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// * target是否为原始值,也就是没有包装的值
if (target === toRaw(receiver)) {
// * 如果没有key
if (!hadKey) {
// * 触发添加操作
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// * 触发修改操作
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
- 当我们查看
state.count
的值时发生了什么?
*state.count
执行了代理对象的get
方法,也就是BaseReactiveHandler
中的get
const res = Reflect.get(target, key, receiver)
最关键的是这一行代码,返回了state[count]
也就是0
- 为什么
reactive()
传入多层对象,对响应式对象所有属性的访问和修改,都可以被依赖追踪到,就在第80行代码
js
if (isObject(res)) {
// * 如果返回值是对象,且不是只读的,递归包装成响应式对象
return isReadonly ? readonly(res) : reactive(res)
}
- 当我们修改
state.count
的值发生了什么?- 核心就是这一行
const result = Reflect.set(target, key, value, receiver);
- 核心就是这一行
总结
-
reactive
响应式是如何做到的?- 当我们调用
reactive
时,会先检查传入的值是不是一个只读对象,如果不是则会调用createReactiveObject
函数 - 在
createReactiveObject
函数中会判断传入的target
是不是一个对象,如果不是会直接返回 - 在
reactiveMap
查找是否存在传入target
的代理对象,如果存在会直接返回代理对象,不存在则会重新创建,并将新创建的proxy
存入reactiveMap
- 当我们调用
-
当我们在
reactive
函数中传入多层嵌套对象,它是如何做到响应式的?- 在
BaseReactiveHandler
中,get
方法会判断查看的值是否也是一个对象并且不是只读的,将会递归包装成响应式对象
- 在