目录
- 一、前言
- 二、新的方案
- 1. 缘由
- 2. Proxy 和 Reflect
- 1) Proxy
- 2) Reflect
- 3. reactive
- 1) createReactiveObject() 函数
- 2) mutableHandlers() 函数 -> 对象类型的 handlers
- 3) mutableInstrumentations() 函数 -> Map Set等类型的 handlers
- 4. ref
- 1) createRef()
- 2) toReactive()
- 3)proxyRefs() 自动脱 ref
- 三、 结语
一、前言
距离 Vue3 出来了已经有一段时间了, 最近呢重温了一下Vue3的响应式方案,以及ref reactive的区别,相信你看完本文,也能对 Vue3 响应式的了解有所提高
在看源码的过程中,时常会感到框架的设计之美,看起来也会感到赏心悦目, 这也是能坚持把源码看下去的动力
二、新的方案
1. 缘由
- 已知在 Vue2 中, 响应式原理一直是采用 Object.defineProperty 来进行,那这样做有什么权限呢? 下面一一道来
- 这个API, 只能拦截 get / set 的属性
- 如对象 新增 或者 删除 了属性,则无法监听到改变
- 对于数组,若使用数组的原生方法改变数组元素的时候 也无法监听到改变
- 所以呢在Vue3中采用了 Proxy 和 Reflect搭配来代理数据
2. Proxy 和 Reflect
1) Proxy
既然Vue3中响应式数据是基于 Proxy 实现的,那么什么是Proxy呢?
使用Proxy可以创建一个代理对象,它可以实现对 对象数据 的 代理, 所以它 无法对非对象值进行代理,也就是为什么Vue3中对于非对象值要使用 ref 来进行响应式的原因 (后面讲解ref的时候再细说)
- 代理是指 允许我们拦截并重新定义对一个对象的基本操作。 例如: 拦截读取、 修改等操作.
const obj = {} | |
const newP = new Proxy(obj, { | |
// 拦截读取 | |
get(){/*...*/ }, | |
// 拦截设置属性操作 | |
set(){/*...*/ } | |
}) |
2) Reflect
说完了Proxy, 接下来我们来说说 Reflect
通过观察 MDN 官网可以发现, Reflect的方法 和 Proxy的拦截器方法 名字基本一致
那就出现了一个问题,我们为什么要用 Reflect 呢?
主要还是它的第三个参数,你可以理解为函数调用过程中的this,我们来看看它配合 Proxy 具体的用途吧
const obj = { | |
foo:, | |
// obj 中有一个 getter属性 通过this获取foo的值 | |
get getFoo() { return this.foo; } | |
}; | |
const newP = new Proxy(obj, | |
{ | |
// 拦截读取 | |
get(target, key) { | |
console.log('读取', key); // 注意这里目前没有使用 Reflect | |
return target[key]; | |
}, | |
// 拦截设置属性操作 | |
set(target, key, newVal) { | |
console.log('修改', key); | |
target[key] = newVal | |
} | |
}) | |
obj.foo++ | |
console.log(newP.getFoo); |
执行上面代码你会发现, 在 Proxy 中 get 拦截的中,只会触发对 getFoo 属性进行读取的拦截, 而无法触发在 getFoo 里面对 this.foo 进行读取的拦截!
问题就出现在 getFoo 这个getter里, 这里面的 this 在我们 未使用 Reflect 的时候指向它的原始对象,所以我们才无法通过 Proxy 拦截到属性读取
只需修改一下上面代码中 Proxy 里面的 get 拦截方法
// 拦截读取 | |
get(target, key, receiver) { | |
console.log('读取', key); | |
return Reflect.get(target, key, receiver); // 使用 Reflect返回读取的属性值 | |
}, |
这下再执行上面的例子,就会发现能正常对 getFoo 里面的 foo 属性进行读取的拦截。 因为这个时候的 this 已经指向了代理对象 newP
以上呢,就是对 Proxy 和 Reflect 的简易讲解,接下来我们讲讲 Vue3 中的 reactive
3. reactive
看源码会发现,我们平时使用 reactive 的时候,会调用一个 createReactiveObject 的方法
这个地方在: packages\reactivity\src\reactive
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> | |
export function reactive(target: object) { | |
// if trying to observe a readonly proxy, return the readonly version. | |
if (isReadonly(target)) { | |
return target | |
} | |
return createReactiveObject( | |
target, | |
false, | |
mutableHandlers, // 普通对象的 handlers | |
mutableCollectionHandlers, // Set Map 等类型的 handlers | |
reactiveMap | |
) | |
} |
1) createReactiveObject() 函数
其中主要是做一些前置判断,然后建立响应式地图
WeakMap -> Map -> Set
function createReactiveObject( | |
target: Target, | |
isReadonly: boolean, | |
baseHandlers: ProxyHandler<any>, | |
collectionHandlers: ProxyHandler<any>, | |
proxyMap: WeakMap<Target, any> | |
) { | |
// 若目标数据是不是对象则直接返回 | |
if (!isObject(target)) { | |
if (__DEV__) { | |
console.warn(`value cannot be made reactive: ${String(target)}`) | |
} | |
return target | |
} | |
// target is already a Proxy, return it. | |
// exception: calling readonly() on a reactive object | |
// raw 代表原始数据 | |
// 或者是非响应式数据就直接返回 原数据 | |
if ( | |
target[ReactiveFlags.RAW] && | |
!(isReadonly && target[ReactiveFlags.IS_REACTIVE]) | |
) { | |
return target | |
} | |
// 如已被代理则直接返回代理的这个对象 | |
const existingProxy = proxyMap.get(target) | |
if (existingProxy) { | |
return existingProxy | |
} | |
// only a whitelist of value types can be observed. | |
// 只有在白名单中的类型才可以被代理 | |
const targetType = getTargetType(target) | |
if (targetType === TargetType.INVALID) { | |
return target | |
} | |
// 建立代理 Proxy | |
const proxy = new Proxy( | |
target, | |
// 使用不同的 hanlders | |
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers | |
) | |
// 存储到响应式地图中 | |
proxyMap.set(target, proxy) | |
return proxy | |
} |
2) mutableHandlers() 函数 -> 对象类型的 handlers
这个地方在: packages\reactivity\src\baseHandlers
主要讲讲get和set
export const mutableHandlers: ProxyHandler<object> = { | |
get: createGetter(), // 读取属性 | |
set: createSetter(), // 设置属性 | |
deleteProperty, // 删除属性 | |
has, // 判断是否存在对应属性 | |
ownKeys // 获取自身的属性值 | |
} |
get
function createGetter(isReadonly = false, shallow = false) { | |
return function get(target: Target, key: string | symbol, receiver: object) { | |
// 判断返回一些特定的值 例如 是 readonly 的就返回 readonlyMap,是 reactive 的就返回 reactiveMap 等等 | |
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 && | |
receiver === | |
(isReadonly | |
? shallow | |
? shallowReadonlyMap | |
: readonlyMap | |
: shallow | |
? shallowReactiveMap | |
: reactiveMap | |
).get(target) | |
) { | |
return target | |
} | |
// 如果是数组要进行一些特殊处理 | |
const targetIsArray = isArray(target) | |
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { | |
// 重写数组的方法 | |
// 'includes', 'indexOf', 'lastIndexOf', 'push', 'pop', 'shift', 'unshift', 'splice' | |
return Reflect.get(arrayInstrumentations, key, receiver) | |
} | |
// 获取属性值 | |
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 | |
} | |
// 如果是ref 则自动进行 脱ref | |
if (isRef(res)) { | |
// ref unwrapping - does not apply for Array + integer key. | |
const shouldUnwrap = !targetIsArray || !isIntegerKey(key) | |
return shouldUnwrap ? res.value : res | |
} | |
// 返回值是对象 | |
// 如果是只读就用 readonly 包裹返回数据 | |
// 否则则进行递归深层包裹 reactive 返回 Proxy 代理对象 | |
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 | |
} | |
} |
set
function createSetter(shallow = false) { | |
return function set( | |
target: object, | |
key: string | symbol, | |
value: unknown, | |
receiver: object | |
): boolean { | |
// 缓存旧值 | |
let oldValue = (target as any)[key] | |
if (!shallow && !isReadonly(value)) { | |
if (!isShallow(value)) { | |
value = toRaw(value) | |
oldValue = toRaw(oldValue) | |
} | |
// 若是 ref 并且非只读 则直接修改 ref的值 | |
if (!isArray(target) && isRef(oldValue) && !isRef(value)) { | |
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) | |
// don't trigger if target is something up in the prototype chain of original | |
// 若目标是原型链上的内容就不触发依赖 | |
if (target === toRaw(receiver)) { | |
// 这里主要是判断是 新增属性 还是修改属性的操作 | |
if (!hadKey) { | |
trigger(target, TriggerOpTypes.ADD, key, value) | |
} else if (hasChanged(value, oldValue)) { | |
trigger(target, TriggerOpTypes.SET, key, value, oldValue) | |
} | |
} | |
// 最终返回结果 | |
return result | |
} | |
} |
3) mutableInstrumentations() 函数 -> Map Set等类型的 handlers
这个地方在: packages\reactivity\src\collectionHandlers
其主要是为了解决 代理对象 无法访问集合类型的属性和方法
function createInstrumentations() { | |
// 主要就是代理了 Map Set等类型的方法 具体实现各位可以去上面地址中的文件里查看 | |
const mutableInstrumentations: Record<string, Function> = { | |
get(this: MapTypes, key: unknown) { | |
return get(this, key) | |
}, | |
get size() { | |
return size(this as unknown as IterableCollections) | |
}, | |
has, | |
add, | |
set, | |
delete: deleteEntry, | |
clear, | |
forEach: createForEach(false, false) | |
} | |
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator] | |
iteratorMethods.forEach(method => { | |
mutableInstrumentations[method as string] = createIterableMethod( | |
method, | |
false, | |
false | |
) | |
}) | |
return [ | |
mutableInstrumentations | |
] | |
} |
4. ref
之前说过 Proxy 代理的必须是对象数据类型,而非对象数据类型 例如: string number 等等 则不能用其进行代理, 所以有了 ref 的概念
联想到上面说的 reactive 和我们日常使用的 .value 的形式, 是不是就认为 ref 直接把原始数据包裹成对象 然后通过 Proxy 进行代理的呢?
最开始我也以为是这样,但是查看了源码中发现其实并不是, 其实是创建 ref 的时候, 实例化了一个 class -> new RefImpl(rawValue, shallow) ,然后通过自定义的 get set来进行依赖收集和依赖更新
源码地址: packages\reactivity\src\ref
1) createRef()
export function ref(value?: unknown) { | |
// 调用创建方法 | |
return createRef(value, false) | |
} | |
function createRef(rawValue: unknown, shallow: boolean) { | |
// 如果已经是一个ref 则直接返回 | |
if (isRef(rawValue)) { | |
return rawValue | |
} | |
// 实例化 class | |
return new RefImpl(rawValue, shallow) | |
} | |
class RefImpl<T> { | |
private _value: T | |
private _rawValue: T | |
public dep?: Dep = undefined | |
// 用于区分 ref 的不可枚举属性 例如 isRef 方法就是直接判断这个属性 | |
public readonly __v_isRef = true | |
// 构造函数 | |
constructor(value: T, public readonly __v_isShallow: boolean) { | |
this._rawValue = __v_isShallow ? value : toRaw(value) | |
this._value = __v_isShallow ? value : toReactive(value) | |
} | |
get value() { | |
// 依赖收集 | |
trackRefValue(this) | |
return this._value | |
} | |
set value(newVal) { | |
// 拿到原始值 | |
newVal = this.__v_isShallow ? newVal : toRaw(newVal) | |
// 判断是否有变化 如有才进行更新 | |
if (hasChanged(newVal, this._rawValue)) { | |
this._rawValue = newVal | |
this._value = this.__v_isShallow ? newVal : toReactive(newVal) | |
// 依赖更新 | |
triggerRefValue(this, newVal) | |
} | |
} | |
} |
2) toReactive()
我们日常使用的时候会发现, ref 传入一个对象 也能正常使用,其玄机就在 创建class 的时候,构造函数中调用了 toReactive 这个函数
export const toReactive = <T extends unknown>(value: T): T => | |
// 如果是一个对象则利用 reactive 代理成 Proxy 返回 | |
isObject(value) ? reactive(value) : value | |
复制代码 |
3)proxyRefs() 自动脱 ref
我们在使用 ref 的时候会发现,从 setup 返回的 ref, 在页面中使用并不需要 .value ,这都归功 proxyRefs 这个函数,减少了我们在模板中需要判断 ref 的心智负担
<template> | |
// 这里并不需要 .value | |
// 并且如果我 直接在模板的点击事件中 使用 count++ 响应式也不会丢失 | |
<div @click="count++"> {{ count }} </div> | |
</template> | |
const myComponent = { | |
setup() { | |
const count = ref() | |
return { count } | |
} | |
} |
下面我们就来看看 proxyRefs 的实现
export function proxyRefs<T extends object>( | |
objectWithRefs: T | |
): ShallowUnwrapRef<T> { | |
// 如果是 reactive 则不处理 | |
return isReactive(objectWithRefs) | |
? objectWithRefs | |
// 如果是 ref 则直接通过 Proxy 代理一下 | |
: new Proxy(objectWithRefs, shallowUnwrapHandlers) | |
} | |
export function unref<T>(ref: T | Ref<T>): T { | |
// 如果是 ref 直接返回 .value 的值 | |
return isRef(ref) ? (ref.value as any) : ref | |
} | |
const shallowUnwrapHandlers: ProxyHandler<any> = { | |
// get 的时候直接脱 ref | |
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)), | |
set: (target, key, value, receiver) => { | |
const oldValue = target[key] | |
// 如果旧值是 ref 而新值不是 ref 直接把 新值 替换 旧值 的.value属性 | |
if (isRef(oldValue) && !isRef(value)) { | |
oldValue.value = value | |
return true | |
} else { | |
return Reflect.set(target, key, value, receiver) | |
} | |
} | |
} |
然后我们会发现在模板调用中,会自动把setup的返回值通过 proxyRefs 调用一遍
通过上面的源码来个总结:
- 我们在编写 Vue 组件的时候, 组件中 setup 的函数所返回的数据会自动传给 proxyRefs 函数处理一遍,所以我们在页面中使用 无需 .value
- ref 最后在 模板中 还是被 Proxy 代理 了一遍
三、 结语
以上呢就是对 Vue3 的响应式的方案解析了, 以及关于 reactive ref的区别相信你如果仔细看完了,也会心知肚明了