前言
接着上一篇《Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/baseHandlers.ts》我们学习了使用 Proxy 实现的 reactive 构造出来的响应式的对象, new Proxy(target, handler) 中的基础 handler(baseHandlers)。这一篇,我们将来介绍另外一种 handler 叫 collectionHandlers。collectionHandler 比 baseHandler 提供了更加丰富的 OperationTypes(set、add、delete、clear、get、has、iterate) 中更齐全的操作的监听响应处理。
源码分析
import { toRaw, reactive, readonly } from './reactive'
import { track, trigger } from './effect'
import { OperationTypes } from './operations'
import { LOCKED } from './lock'
import { isObject, capitalize, hasOwn } from '@vue/shared'
const toReactive = (value: any) => (isObject(value) ? reactive(value) : value) // 转成 reactive Object
const toReadonly = (value: any) => (isObject(value) ? readonly(value) : value) // 转成 readonly Object
// 二次封装 target 的 get 取值操作
function get(target: any, key: any, wrap: (t: any) => any): any { // get 操作的时候要 track 这个 effect
target = toRaw(target)
key = toRaw(key)
const proto: any = Reflect.getPrototypeOf(target)
track(target, OperationTypes.GET, key)
const res = proto.get.call(target, key)
return wrap(res)
}
// 二次封装 target 的 has 取值操作
function has(this: any, key: any): boolean { // has 操作的时候要 track 这个 effect
const target = toRaw(this)
key = toRaw(key)
const proto: any = Reflect.getPrototypeOf(target)
track(target, OperationTypes.HAS, key)
return proto.has.call(target, key)
}
// 二次封装 target 的 size 取值操作(针对 Set 等类型的数据)
function size(target: any) { // size 操作的时候要 track 这个 effect
target = toRaw(target)
const proto = Reflect.getPrototypeOf(target)
track(target, OperationTypes.ITERATE)
return Reflect.get(proto, 'size', target)
}
// 二次封装 target 的 add 增加值操作(针对 Set 等类型的数据)
function add(this: any, value: any) { // add 操作的时候要 trigger 相应的 effect 执行
value = toRaw(value)
const target = toRaw(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadKey = proto.has.call(target, value)
const result = proto.add.call(target, value)
if (!hadKey) {
/* istanbul ignore else */
if (__DEV__) {
trigger(target, OperationTypes.ADD, value, { value })
} else {
trigger(target, OperationTypes.ADD, value)
}
}
return result
}
// 二次封装 target 的 set 设值操作(针对 Map 等类型的数据)
function set(this: any, key: any, value: any) { // set 操作的时候要 trigger 相应的 effect 执行
value = toRaw(value)
const target = toRaw(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadKey = proto.has.call(target, key)
const oldValue = proto.get.call(target, key)
const result = proto.set.call(target, key, value)
if (value !== oldValue) { // 值改变了才要 trigger 相应的 effect 执行
/* istanbul ignore else */
if (__DEV__) {
const extraInfo = { oldValue, newValue: value }
if (!hadKey) {
trigger(target, OperationTypes.ADD, key, extraInfo)
} else {
trigger(target, OperationTypes.SET, key, extraInfo)
}
} else {
if (!hadKey) {
trigger(target, OperationTypes.ADD, key)
} else {
trigger(target, OperationTypes.SET, key)
}
}
}
return result
}
// 二次封装 target 的 delete 删值操作(针对 Object 等类型的数据)
function deleteEntry(this: any, key: any) { // delete 操作的时候要 trigger 相应的 effect 执行
const target = toRaw(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadKey = proto.has.call(target, key)
const oldValue = proto.get ? proto.get.call(target, key) : undefined
// forward the operation before queueing reactions
const result = proto.delete.call(target, key)
if (hadKey) {
/* istanbul ignore else */
if (__DEV__) {
trigger(target, OperationTypes.DELETE, key, { oldValue })
} else {
trigger(target, OperationTypes.DELETE, key)
}
}
return result
}
// 二次封装 target 的 clear 清空值操作(针对 Map 等类型的数据)
function clear(this: any) { // 对 Map.prototype.clear() 函数进行二次封装,要 trigger 相应的 effect 执行
const target = toRaw(this)
const proto: any = Reflect.getPrototypeOf(this)
const hadItems = target.size !== 0
const oldTarget = target instanceof Map ? new Map(target) : new Set(target)
// forward the operation before queueing reactions
const result = proto.clear.call(target)
if (hadItems) { // 触发相关的 effect 执行
/* istanbul ignore else */
if (__DEV__) {
trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
} else {
trigger(target, OperationTypes.CLEAR)
}
}
return result
}
// 二次封装 target 的 forEach 循环取值操作(针对 Array 等类型的数据)
function createForEach(isReadonly: boolean) { // forEach 操作的时候要 track 这个 effect
return function forEach(this: any, callback: Function, thisArg?: any) {
const observed = this
const target = toRaw(observed)
const proto: any = Reflect.getPrototypeOf(target)
const wrap = isReadonly ? toReadonly : toReactive
track(target, OperationTypes.ITERATE)
// important: create sure the callback is
// 1. invoked with the reactive map as `this` and 3rd arg
// 2. the value received should be a corresponding reactive/readonly.
function wrappedCallback(value: any, key: any) {
return callback.call(observed, wrap(value), wrap(key), observed)
}
return proto.forEach.call(target, wrappedCallback, thisArg)
}
}
// target 的 'keys', 'values', 'entries', Symbol.iterator 这些遍历取值方法都要进行二次封装
function createIterableMethod(method: string | symbol, isReadonly: boolean) { // Iterate 操作的时候要 track 这个 effect
return function(this: any, ...args: any[]) {
const target = toRaw(this)
const proto: any = Reflect.getPrototypeOf(target)
const isPair =
method === 'entries' ||
(method === Symbol.iterator && target instanceof Map)
const innerIterator = proto[method].apply(target, args) // 执行方法先,后面再 track
const wrap = isReadonly ? toReadonly : toReactive
track(target, OperationTypes.ITERATE)
// return a wrapped iterator which returns observed versions of the
// values emitted from the real iterator
return {
// iterator protocol
next() {
const { value, done } = innerIterator.next()
return done
? { value, done }
: {
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
done
}
},
// iterable protocol 为对象添加 Iterator 接口
// 1、遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
// 2、Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。
[Symbol.iterator]() {
return this
}
}
}
}
function createReadonlyMethod( // Readonly 方法不需要 track / trigger
method: Function,
type: OperationTypes
): Function {
return function(this: any, ...args: any[]) {
if (LOCKED) {
if (__DEV__) {
const key = args[0] ? `on key "${args[0]}" ` : ``
console.warn(
`${capitalize(type)} operation ${key}failed: target is readonly.`,
toRaw(this)
)
}
return type === OperationTypes.DELETE ? false : this
} else {
return method.apply(this, args)
}
}
}
const mutableInstrumentations: any = { // 尤大大起名真高端大气上档次,instrumentation 乐器,mutable 的 ProxyHandler
get(key: any) {
return get(this, key, toReactive)
},
get size() {
return size(this)
},
has,
add,
set,
delete: deleteEntry,
clear,
forEach: createForEach(false)
}
const readonlyInstrumentations: any = { // 尤大大起名真高端大气上档次,instrumentation 乐器,readonly 的 ProxyHandler
get(key: any) {
return get(this, key, toReadonly)
},
get size() {
return size(this)
},
has,
add: createReadonlyMethod(add, OperationTypes.ADD),
set: createReadonlyMethod(set, OperationTypes.SET),
delete: createReadonlyMethod(deleteEntry, OperationTypes.DELETE),
clear: createReadonlyMethod(clear, OperationTypes.CLEAR),
forEach: createForEach(true)
}
// 在 instrumentation 乐器上,给所有 Iterate 操作,添加 createIterableMethod 方法
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
iteratorMethods.forEach(method => {
mutableInstrumentations[method] = createIterableMethod(method, false)
readonlyInstrumentations[method] = createIterableMethod(method, true)
})
function createInstrumentationGetter(instrumentations: any) { // 二次封装 get 方法,通过 getter 操作把所有其他操作类型(method)都绑定上
return function getInstrumented(
target: any,
key: string | symbol,
receiver: any
) {
target =
hasOwn(instrumentations, key) && key in target ? instrumentations : target
return Reflect.get(target, key, receiver)
}
}
export const mutableCollectionHandlers: ProxyHandler<any> = { // 方式一、通过 getter 操作把 mutableInstrumentations 上的所有其他操作类型(method)都绑定上
get: createInstrumentationGetter(mutableInstrumentations)
}
export const readonlyCollectionHandlers: ProxyHandler<any> = { // 方式二、通过 getter 操作把 readonlyInstrumentations 上的所有其他操作类型(method)都绑定上
get: createInstrumentationGetter(readonlyInstrumentations)
}
总结
至此,我们已把 vue-reactivity 这个 package 整个实现响应式数据的代码包都过了一遍,通过其源码,我们可以学习到不少东西,这些东西及知识点基本上在我们写业务系统的时候是没有机会用上场的。但现在 ES 6 甚至 ES 7 大环境都开始支持起来了。我们基本上很多新东西都可以尝试在业务系统上面用起来了。新的特性,我们可以根据场景有选择的去用上。比如 Typescript,目前 React / Vue 的 boilerplate 项目 / cl 基本已经提供了 Typescript 版本的模版。接下来,将要来探索一下 Vue Next 的其他 package 吧。