源码位置:package/reactivity/src/reactive ,另外笔者在代码中去除了一些边界代码
src下的 baseHandlers 导出数据类型为
数组 对象
的proxy配置对象
src下的 collectionHandlers 导出数据类型为set map weakSet weakMap
的proxy配置对象这篇文章 介绍了Proxy以及Vue是如何代理不同数据类型
对于reactive
ShallowReactive
readonly
shallowReadonly
的核心都是createReactiveObject
(工厂函数),这四个函数只需要负责接收参数,再将参数传递给createReactiveObject
就可以返回正确的代理对象
接着让我们进入createReactiveObject
,下面的代码里给出了详细注释.
另外笔者在阅读的时候遇到一个问题,这里分享给大家:
ReactiveFlags
是一个枚举类型,因此ReactiveFlags.RAW
可以拿到对应的key
一开始我以为它是在某个地方给target设置了这么一个key属性.比如代理后给原来的target做个标记以区别是否代理过. 但是在源码里死活找不到在哪里赋的值
然而真相是...... 这个属性的读取会被proxy拦截
- 如果target是一个未被代理的普通对象,肯定没有
ReactiveFlags.RAW
属性,判断条件为假,不会直接返回target. - 如果target已经被代理,找到proxy中的get操作,如果访问的key是
ReactiveFlags.RAW
,会返回原始对象target.访问的key是ReactiveFlags.IS_REACTIVE
,isReadonly
是只读的标志,也就是标识是否是只读的代理对象.
//reactive
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'
}
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
//baseHandlers
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
else if (
key === ReactiveFlags.RAW
){
return target
}
function createReactiveObject(
target: Target,//被代理对象
isReadonly: boolean,//是否只读
baseHandlers: ProxyHandler<any>,//数据类型为数组,对象时的proxy配置对象
collectionHandlers: ProxyHandler<any>,//数据类型为set map weakSet weakMap的proxy配置对象
proxyMap: WeakMap<Target, any>//代理对象与被代理对象的联系存储在weakMap,
//对于浅响应,深响应,浅只读,深只读都有自己的weakMap,
//所以对于一个数据,可以建立它的深浅响应,深浅只读代理对象。
) {
//判断是否是对象,typeof set === 'object'
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${
String(target)}`)
}
return target
}
// 如果target本身是一个代理,并且
// 除了 这个代理对象是不是只读对象并调用readonly,其他情况不需要处理,直接返回代理对象
//exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
//判断target是否被代理过,如果代理过会存在 `proxyMap`
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 除了上面提到的数据类型都不能被代理
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
//proxy的第二个参数是一个配置对象可以包含一组捕获器(handler traps),
//这些捕获器定义了在代理对象上触发的各种操作的行为。
//每个捕获器都是一个函数,负责处理相应的操作。
const proxy = new Proxy(
target,
//如果被代理对象是数组或者对象使用baseHandlers,set map weakSet weakMap使用collectionHandlers作为配置
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
//最后将传人的proxyMap设置一下
proxyMap.set(target, proxy)
//返回代理对象
return proxy
}
紧接着,再来看看proxy的第二个参数即配置对象,看看这四个函数分别传递了什么作为配置对象
- 在下面代码里注释了配置对象里不同方法的用途
- 可以看到
ShallowReactive
共用了reactive
的配置对象,并在重写了get set
,shallowReadonly
也是共用了readonly
的配置对象,重写了get
- 对于
只读对象
来说,其实只需要拦截get即可,这里还加上set,deleteProperty
只是为了展示错误提示.
//reactive
export const mutableHandlers: ProxyHandler<object> = {
get,//拦截属性读取
set,//拦截设置属性
deleteProperty,//拦截删除属性
has,//拦截`in`,(key in obj)
ownKeys//拦截 for in 循环
}
//ShallowReactive
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
{
},
mutableHandlers,
{
get: shallowGet,
set: shallowSet
}
)
//readonly
export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
set(target, key) {
if (__DEV__) {
warn(
`Set operation on key "${
String(key)}" failed: target is readonly.`,
target
)
}
return true
},
deleteProperty(target, key) {
if (__DEV__) {
warn(
`Delete operation on key "${
String(key)}" failed: target is readonly.`,
target
)
}
return true
}
}
//shallowReadonly
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
{
},
readonlyHandlers,
{
get: shallowReadonlyGet
}
)
下面分别介绍了这几个捕获器的实现流程
get
先来看看这四个函数的get是怎么创建的,可以看出它也是被一个工厂函数createGetter
创建的.
让我们再进入createGetter
函数,可以知道他根据接收的参数返回合适的get
const get = /*#__PURE__*/ createGetter()
const shallowGet = /*#__PURE__*/ createGetter(false, true)
const readonlyGet = /*#__PURE__*/ createGetter(true)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
可以看到get中果然拦截了key为ReactiveFlags.IS_REACTIVE
等等,awesome,这与上面的内容就对上了.
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
..........
}}
接着判断是数组,需要 重写数组方法
- 代理对象设置为只读,不需要使用重写后的方法
hasOwn
,它用于检查对象是否具有指定的属性.也就是代理的是数组并且调用的方法是重写的方法之一,改为调用重写之后的方法.- 下面这个判断为了解决一个bug,当访问hasOwnProperty会收集依赖,但是proxyData.hasOwnProperty(key)里面这个key也应该收集依赖.
const targetIsArray = isArray(target)
if (!isReadonly) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
function hasOwnProperty(this: object, key: string) {
const obj = toRaw(this)
track(obj, TrackOpTypes.HAS, key)
return obj.hasOwnProperty(key)
}
isObject就是利用里typeof进行判断并排除了null的情况
- 如果是只读,代表数据变化不需要做出任何响应,也就是
不用收集依赖
. - 如果是浅处理,直接返回res.否则应该返回
reactive或者readonly
处理过后的res 这里与Vue2相比,不会一次性把对象变成深响应.而是只有访问到这个属性才会给它添加响应性.因为proxy可以监听到数据读取.这个时候给他添加上响应性
const res = Reflect.get(target, key, receiver) if (!isReadonly) { track(target, TrackOpTypes.GET, key) } if (shallow) { return res } if (isObject(res)) { return isReadonly ? readonly(res) : reactive(res) } return res const isObject = (val: unknown): val is Record<any, any> => val !== null && typeof val === 'object'
set
拦截修改操作
,对属性进行修改
对于readonly,shallowReadyonly
来说,在set捕获器中只需要抛出错误即可.
对于reactive,shallowReactive
,它们的set是通过工厂函数createSetter
生成
hadKey
是判断新增属性还是修改属性的标记.当handKey为false,即新增.触发依赖,并将ADD传递给trigger
hasChanged
这个函数判断新旧值是否相同,如果相同就没必要重新触发依赖.另外hasChanged里利用的是Object.is
,它与全等的区别是能处理NAN
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
deleteProperty
拦截修改操作
,即删除属性.
如果删除的属性不属于target,或者删除失败,都不需要触发依赖(trigger)
function deleteProperty(target: object, key: string | symbol): boolean {
const hadKey = hasOwn(target, key)
const oldValue = (target as any)[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
has
拦截读取操作
中的一种情况,判断对象或原型上是否存在给定的 key:key in
obj。
如果key是一个symbol
类型不进行依赖收集.因为利用for...of遍历数组还会读取数组的 Symbol.iterator 属性。该属性是一个 symbol值,没有必要建立响应联系
function has(target: object, key: string | symbol): boolean {
const result = Reflect.has(target, key)
if (!isSymbol(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}
ownKeys
为了拦截读取操作
中的另一种情况,使用 for...in/of 循环遍历对象
直接收集依赖(track)
即可,因为ownKeys
参数没有key,所以用一个symbol类型的值来表示key.对应数组,可以直接把length当成key.
function ownKeys(target: object): (string | symbol)[] {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
接下来让我们看看reactive.ts文件里还导出了什么函数
toReactive() toReadonly()
还是利用了reactive readonly,不再赘述
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
export const toReadonly = <T extends unknown>(value: T): T =>
isObject(value) ? readonly(value) : value
markRaw()
将一个对象标记为不可被转为代理。返回该对象本身。
实现原理是给对象添加一个属性ReactiveFlags.SKIP
用来标记不能代理.在createReactiveObject
中会对这个属性进行判断,如果是true,直接返回原始对象.
export function markRaw<T extends object>(value: T): Raw<T> {
def(value, ReactiveFlags.SKIP, true)
return value
}
export const def = (obj: object, key: string | symbol, value: any) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
})
}
isReactive() , isReadonly() , isShallow() , toRaw()
实现原理:都是在get捕获器中拦截ReactiveFlags里对应的key
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
export function isShallow(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_SHALLOW])
}
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
isProxy()
不是Reactive也不是isReadonly类型肯定就不是proxy了
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
至此,reactive.ts中导出的函数都已经被罗列并分析了一遍.
可以看出reactive.ts文件的作用是为了实现对数据的代理,从'@vue/shared'
导入了一些全局共享工具函数,从baseHandlers.ts和collectionHandlers.ts
文件导入了关于proxy的配置对象.并导出了一些与代理相关的函数.
对于这两个文件,它们导入了effect模块,用来进行收集依赖,并派发通知.并且接收了reactive.ts中的一些函数.