collectionHandlers.ts 文件
collectionHandlers.ts 文件包含了 Map
WeakMap
Set
WeakSet
的处理器对象,分别对应完全响应式的 proxy 实例、浅层响应的 proxy 实例、只读 proxy 实例。这里只讲解对应完全响应式的 proxy 实例的处理器对象:
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = { get: createInstrumentationGetter(false, false) }
为什么只监听 get 操作,set has 等操作呢?不着急,先看一个示例:
const p = new Proxy(new Map(), { get(target, key, receiver) { console.log('get: ', key) return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { console.log('set: ', key, value) return Reflect.set(target, key, value, receiver) } }) p.set('ab', 100) // Uncaught TypeError: Method Map.prototype.set called on incompatible receiver [object Object]
运行上面的代码会报错。其实这和 Map Set 的内部实现有关,必须通过 this 才能访问它们的数据。但是通过 Reflect 反射的时候,target 内部的 this 其实是指向 proxy 实例的,所以就不难理解为什么会报错了。
那怎么解决这个问题?通过源码可以发现,在 Vue3.0 中是通过代理的方式来实现对 Map Set 等数据结构监听的:
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) { const instrumentations = shallow ? shallowInstrumentations : isReadonly ? readonlyInstrumentations : mutableInstrumentations return ( target: CollectionTypes, key: string | symbol, receiver: CollectionTypes ) => { // 这三个 if 判断和 baseHandlers 的处理方式一样 if (key === ReactiveFlags.isReactive) { return !isReadonly } else if (key === ReactiveFlags.isReadonly) { return isReadonly } else if (key === ReactiveFlags.raw) { return target } return Reflect.get( hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver ) } }
把最后一行代码简化一下:
target = hasOwn(instrumentations, key) && key in target? instrumentations : target return Reflect.get(target, key, receiver);
其中 instrumentations 的内容是:
const mutableInstrumentations: Record<string, Function> = { get(this: MapTypes, key: unknown) { return get(this, key, toReactive) }, get size() { return size((this as unknown) as IterableCollections) }, has, add, set, delete: deleteEntry, clear, forEach: createForEach(false, false) }
从代码可以看到,原来真正的处理器对象是 mutableInstrumentations。现在再看一个示例:
const proxy = reactive(new Map()) proxy.set('key', 100)
生成 proxy 实例后,执行 proxy.set('key', 100)
。proxy.set
这个操作会触发 proxy 的属性读取拦截操作。
打断点可以看到,此时的 key 为 set
。拦截了 set
操作后,调用 Reflect.get(target, key, receiver)
,这个时候的 target 已经不是原来的 target 了,而是 mutableInstrumentations 对象。也就是说,最终执行的是 mutableInstrumentations.set()
。
接下来再看看 mutableInstrumentations 的各个处理器逻辑。
get
// 如果 value 是对象,则返回一个响应式对象(`reactive(value)`),否则直接返回 value。 const toReactive = <T extends unknown>(value: T): T => isObject(value) ? reactive(value) : value get(this: MapTypes, key: unknown) { // this 指向 proxy return get(this, key, toReactive) } function get( target: MapTypes, key: unknown, wrap: typeof toReactive | typeof toReadonly | typeof toShallow ) { target = toRaw(target) const rawKey = toRaw(key) // 如果 key 是响应式的,额外收集一次依赖 if (key !== rawKey) { track(target, TrackOpTypes.GET, key) } track(target, TrackOpTypes.GET, rawKey) // 使用 target 原型上的方法 const { has, get } = getProto(target) // 原始 key 和响应式的 key 都试一遍 if (has.call(target, key)) { // 读取的值要使用包装函数处理一下 return wrap(get.call(target, key)) } else if (has.call(target, rawKey)) { return wrap(get.call(target, rawKey)) } }
get 的处理逻辑很简单,拦截 get 之后,调用 get(this, key, toReactive)
。
set
function set(this: MapTypes, key: unknown, value: unknown) { value = toRaw(value) // 取得原始数据 const target = toRaw(this) // 使用 target 原型上的方法 const { has, get, set } = getProto(target) let hadKey = has.call(target, key) if (!hadKey) { key = toRaw(key) hadKey = has.call(target, key) } else if (__DEV__) { checkIdentityKeys(target, has, key) } const oldValue = get.call(target, key) const result = set.call(target, key, value) // 防止重复触发依赖,如果 key 已存在就不触发依赖 if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { // 如果新旧值相等,也不会触发依赖 trigger(target, TriggerOpTypes.SET, key, value, oldValue) } return result }
set 的处理逻辑也较为简单,配合注释一目了然。
还有剩下的 has
add
delete
等方法就不讲解了,代码行数比较少,逻辑也很简单,建议自行阅读。
ref.ts 文件
const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val export function ref(value?: unknown) { return createRef(value) } function createRef(rawValue: unknown, shallow = false) { // 如果已经是 ref 对象了,直接返回原值 if (isRef(rawValue)) { return rawValue } // 如果不是浅层响应并且 rawValue 是个对象,调用 reactive(rawValue) let value = shallow ? rawValue : convert(rawValue) const r = { __v_isRef: true, // 用于标识这是一个 ref 对象,防止重复监听 ref 对象 get value() { // 读取值时收集依赖 track(r, TrackOpTypes.GET, 'value') return value }, set value(newVal) { if (hasChanged(toRaw(newVal), rawValue)) { rawValue = newVal value = shallow ? newVal : convert(newVal) // 设置值时触发依赖 trigger( r, TriggerOpTypes.SET, 'value', __DEV__ ? { newValue: newVal } : void 0 ) } } } return r }
在 Vue2.x 中,基本数值类型是不能监听的。但在 Vue3.0 中通过 ref()
可以实现这一效果。
const r = ref(0) effect(() => console.log(r.value)) // 打印 0 r.value++ // 打印 1
ref()
会把 0 转成一个 ref 对象。如果给 ref(value)
传的值是个对象,在函数内部会调用 reactive(value)
将其转为 proxy 实例。
computed.ts 文件
export function computed<T>( options: WritableComputedOptions<T> ): WritableComputedRef<T> export function computed<T>( getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T> ) { let getter: ComputedGetter<T> let setter: ComputedSetter<T> // 如果 getterOrOptions 是个函数,则是不可被配置的,setter 设为空函数 if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP } else { // 如果是个对象,则可读可写 getter = getterOrOptions.get setter = getterOrOptions.set } // dirty 用于判断计算属性依赖的响应式属性有没有被改变 let dirty = true let value: T let computed: ComputedRef<T> const runner = effect(getter, { lazy: true, // lazy 为 true,生成的 effect 不会马上执行 // mark effect as computed so that it gets priority during trigger computed: true, scheduler: () => { // 调度器 // trigger 时,计算属性执行的是 effect.options.scheduler(effect) 而不是 effect() if (!dirty) { dirty = true trigger(computed, TriggerOpTypes.SET, 'value') } } }) computed = { __v_isRef: true, // expose effect so computed can be stopped effect: runner, get value() { if (dirty) { value = runner() dirty = false } track(computed, TrackOpTypes.GET, 'value') return value }, set value(newValue: T) { setter(newValue) } } as any return computed }
下面通过一个示例,来讲解一下 computed 是怎么运作的:
const value = reactive({}) const cValue = computed(() => value.foo) console.log(cValue.value === undefined) value.foo = 1 console.log(cValue.value === 1)
- 生成一个 proxy 实例 value。
computed()
生成计算属性对象,当对 cValue 进行取值时(cValue.value
),根据 dirty 判断是否需要运行 effect 函数进行取值,如果 dirty 为 false,直接把值返回。- 在 effect 函数里将 effect 设为 activeEffect,并运行 getter(
() => value.foo
) 取值。在取值过程中,读取 foo 的值(value.foo
)。 - 这会触发 get 属性读取拦截操作,进而触发 track 收集依赖,而收集的依赖函数就是第 3 步产生的 activeEffect。
- 当响应式属性进行重新赋值时(
value.foo = 1
),就会 trigger 这个 activeEffect 函数。 - 然后调用
scheduler()
将 dirty 设为 true,这样 computed 下次求值时会重新执行 effect 函数进行取值。
index.ts 文件
index.ts 文件向外导出 reactivity 模块的 API。