vue 3.0响应式采用了原生的proxy,原理其实和2.0差不多,主要也是订阅收集的模式。
所有需要被代理的对象需要调用Vue的reactive方法,顾名思义就是响应式的意思,方法返回一个proxy
const original = {foo: 1}
const observed = Vue.reactive(original)
reactive方法的源码如下:
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (readonlyToRaw.has(target)) {
return target
}
// target is explicitly marked as readonly by user
if (readonlyValues.has(target)) {
return readonly(target)
}
return createReactiveObject(
target,
rawToReactive,
reactiveToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}
readonlyToRaw与readonlyValues都是WeakMap对象
const readonlyValues = new WeakSet()
const readonlyToRaw = new WeakMap()
readonlyToRaw的作用的判断如果传进去的对象已经是一个经过Vue包装过的readonly proxy的话,直接返回。readonlyValues的作用是判断如果用户设置了一个对象为只读的,使用Vue.reactive返回一个只读的proxy,例如:
// readonlyToRaw
const original = {foo: 1}
const observed = Vue.readonly(original)
const observed2 = Vue.reactive(observed)
console.log(observed === observed2) // true
observed2.foo = 2
console.log(observed2.foo) // 1
// readonlyValues
const original = {foo: 1}
Vue.markReadonly(original)
const observed = Vue.reactive(original)
observed.foo = 2
console.log(observed.foo) // 1
reactive方法核心在于createReactiveObject,顾名思义就是创建一个响应式的对象即proxy
function createReactiveObject(
target: any,
toProxy: WeakMap,
toRaw: WeakMap,
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target already has corresponding Proxy
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
// target is already a Proxy
if (toRaw.has(target)) {
return target
}
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target
}
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
}
这里分别为每段代码做个解释
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
这段代码主要是判断如果传入的target不是对象,直接返回,例如:
const original = 1
const observed = Vue.reactive(1)
console.log(observed === original) // true
// target already has corresponding Proxy
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
这段代码主要是判断如果一个对象已经被包装proxy,再次包装的话,返回之前的proxy,例如:
const original = {foo: 1}
const observed = Vue.reactive(original)
const observed2 = Vue.reactive(original)
console.log(observed === observed2) // true
// target is already a Proxy
if (toRaw.has(target)) {
return target
}
这段代码主要是判断如果传入的对象是一个被包装的proxy,直接返回此对象,防止用户多次嵌套
const original = {foo: 1}
const observed = Vue.reactive(original)
const observed2 = Vue.reactive(observed)
console.log(observed === observed2) // true
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target
}
这段代码是设置一些白名单,白名单之外的target直接返回
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
const canObserve = (value: any): boolean => {
return (
!value._isVue &&
!value._isVNode &&
observableValueRE.test(toTypeString(value)) &&
!nonReactiveValues.has(value)
)
}
其中nonReactiveValues存储用户设置的不能被代理的对象,Vue3提供了对应的接口来设置
const original = {foo: 1}
Vue.markNonReactive(original)
const observed = Vue.reactive(original)
console.log(observed === original) // true
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
这段代码主要是两件事,首先判断如果传入的target是否为Set, Map, WeakMap, WeakSet对象,则使用collectionHandles proxy处理函数,否则使用baseHandlers proxy处理函数。
const collectionTypes = new Set([Set, Map, WeakMap, WeakSet])
第二件事就是将对应的target与observed传入到WeakMap对象中,Vue3定了4中WeakMap,看变量名字就能发现,后两个是只读的,rawTo代表 object <-> observed ,toRaw代表 observed <-> object
const rawToReactive = new WeakMap()
const reactiveToRaw = new WeakMap()
const rawToReadonly = new WeakMap()
const readonlyToRaw = new WeakMap()
还记得调用createReactiveObject时候一开始做的判断,即
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
// target is already a Proxy
if (toRaw.has(target)) {
return target
}
下次调用createReactiveObject时,会进行对应的判断,检查传入的target是否存是已经包装过的proxy或者已经有对应的proxy的对象了
至于最后的 targetMap.set(target, new Map())是为在后面proxy handle中进行依赖收集做准备的。