Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/collectionHandlers.ts

前言

接着上一篇《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) 中更齐全的操作的监听响应处理。

源码分析

1import { toRaw, reactive, readonly } from './reactive'
2import { track, trigger } from './effect'
3import { OperationTypes } from './operations'
4import { LOCKED } from './lock'
5import { isObject, capitalize, hasOwn } from '@vue/shared'
6 
7const toReactive = (value: any) => (isObject(value) ? reactive(value) : value) // 转成 reactive Object
8const toReadonly = (value: any) => (isObject(value) ? readonly(value) : value) // 转成 readonly Object
9 
10// 二次封装 target 的 get 取值操作
11function get(target: any, key: any, wrap: (t: any) => any): any { // get 操作的时候要 track 这个 effect
12  target = toRaw(target)
13  key = toRaw(key)
14  const proto: any = Reflect.getPrototypeOf(target)
15  track(target, OperationTypes.GET, key)
16  const res = proto.get.call(target, key)
17  return wrap(res)
18}
19 
20// 二次封装 target 的 has 取值操作
21function has(this: any, key: any): boolean { // has 操作的时候要 track 这个 effect
22  const target = toRaw(this)
23  key = toRaw(key)
24  const proto: any = Reflect.getPrototypeOf(target)
25  track(target, OperationTypes.HAS, key)
26  return proto.has.call(target, key)
27}
28 
29// 二次封装 target 的 size 取值操作(针对 Set 等类型的数据)
30function size(target: any) { // size 操作的时候要 track 这个 effect
31  target = toRaw(target)
32  const proto = Reflect.getPrototypeOf(target)
33  track(target, OperationTypes.ITERATE)
34  return Reflect.get(proto, 'size', target)
35}
36 
37// 二次封装 target 的 add 增加值操作(针对 Set 等类型的数据)
38function add(this: any, value: any) { // add 操作的时候要 trigger 相应的 effect 执行
39  value = toRaw(value)
40  const target = toRaw(this)
41  const proto: any = Reflect.getPrototypeOf(this)
42  const hadKey = proto.has.call(target, value)
43  const result = proto.add.call(target, value)
44  if (!hadKey) {
45    /* istanbul ignore else */
46    if (__DEV__) {
47      trigger(target, OperationTypes.ADD, value, { value })
48    } else {
49      trigger(target, OperationTypes.ADD, value)
50    }
51  }
52  return result
53}
54 
55// 二次封装 target 的 set 设值操作(针对 Map 等类型的数据)
56function set(this: any, key: any, value: any) { // set 操作的时候要 trigger 相应的 effect 执行
57  value = toRaw(value)
58  const target = toRaw(this)
59  const proto: any = Reflect.getPrototypeOf(this)
60  const hadKey = proto.has.call(target, key)
61  const oldValue = proto.get.call(target, key)
62  const result = proto.set.call(target, key, value)
63  if (value !== oldValue) { // 值改变了才要 trigger 相应的 effect 执行
64    /* istanbul ignore else */
65    if (__DEV__) {
66      const extraInfo = { oldValue, newValue: value }
67      if (!hadKey) {
68        trigger(target, OperationTypes.ADD, key, extraInfo)
69      } else {
70        trigger(target, OperationTypes.SET, key, extraInfo)
71      }
72    } else {
73      if (!hadKey) {
74        trigger(target, OperationTypes.ADD, key)
75      } else {
76        trigger(target, OperationTypes.SET, key)
77      }
78    }
79  }
80  return result
81}
82 
83// 二次封装 target 的 delete 删值操作(针对 Object 等类型的数据)
84function deleteEntry(this: any, key: any) { // delete 操作的时候要 trigger 相应的 effect 执行
85  const target = toRaw(this)
86  const proto: any = Reflect.getPrototypeOf(this)
87  const hadKey = proto.has.call(target, key)
88  const oldValue = proto.get ? proto.get.call(target, key) : undefined
89  // forward the operation before queueing reactions
90  const result = proto.delete.call(target, key)
91  if (hadKey) {
92    /* istanbul ignore else */
93    if (__DEV__) {
94      trigger(target, OperationTypes.DELETE, key, { oldValue })
95    } else {
96      trigger(target, OperationTypes.DELETE, key)
97    }
98  }
99  return result
100}
101 
102// 二次封装 target 的 clear 清空值操作(针对 Map 等类型的数据)
103function clear(this: any) { // 对 Map.prototype.clear() 函数进行二次封装,要 trigger 相应的 effect 执行
104  const target = toRaw(this)
105  const proto: any = Reflect.getPrototypeOf(this)
106  const hadItems = target.size !== 0
107  const oldTarget = target instanceof Map ? new Map(target) : new Set(target)
108  // forward the operation before queueing reactions
109  const result = proto.clear.call(target)
110  if (hadItems) { // 触发相关的 effect 执行
111    /* istanbul ignore else */
112    if (__DEV__) {
113      trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
114    } else {
115      trigger(target, OperationTypes.CLEAR)
116    }
117  }
118  return result
119}
120 
121// 二次封装 target 的 forEach 循环取值操作(针对 Array 等类型的数据)
122function createForEach(isReadonly: boolean) { // forEach 操作的时候要 track 这个 effect
123  return function forEach(this: any, callback: Function, thisArg?: any) {
124    const observed = this
125    const target = toRaw(observed)
126    const proto: any = Reflect.getPrototypeOf(target)
127    const wrap = isReadonly ? toReadonly : toReactive
128    track(target, OperationTypes.ITERATE)
129    // important: create sure the callback is
130    // 1. invoked with the reactive map as `this` and 3rd arg
131    // 2. the value received should be a corresponding reactive/readonly.
132    function wrappedCallback(value: any, key: any) {
133      return callback.call(observed, wrap(value), wrap(key), observed)
134    }
135    return proto.forEach.call(target, wrappedCallback, thisArg)
136  }
137}
138 
139// target 的 'keys', 'values', 'entries', Symbol.iterator 这些遍历取值方法都要进行二次封装
140function createIterableMethod(method: string | symbol, isReadonly: boolean) { // Iterate 操作的时候要 track 这个 effect
141  return function(this: any, ...args: any[]) {
142    const target = toRaw(this)
143    const proto: any = Reflect.getPrototypeOf(target)
144    const isPair =
145      method === 'entries' ||
146      (method === Symbol.iterator && target instanceof Map)
147    const innerIterator = proto[method].apply(target, args) // 执行方法先,后面再 track
148    const wrap = isReadonly ? toReadonly : toReactive
149    track(target, OperationTypes.ITERATE)
150    // return a wrapped iterator which returns observed versions of the
151    // values emitted from the real iterator
152    return {
153      // iterator protocol
154      next() {
155        const { value, done } = innerIterator.next()
156        return done
157          ? { value, done }
158          : {
159              value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
160              done
161            }
162      },
163      // iterable protocol 为对象添加 Iterator 接口
164      // 1、遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
165      // 2、Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。
166      [Symbol.iterator]() {
167        return this
168      }
169    }
170  }
171}
172 
173function createReadonlyMethod( // Readonly 方法不需要 track / trigger
174  method: Function,
175  type: OperationTypes
176): Function {
177  return function(this: any, ...args: any[]) {
178    if (LOCKED) {
179      if (__DEV__) {
180        const key = args[0] ? `on key "${args[0]}" ` : ``
181        console.warn(
182          `${capitalize(type)} operation ${key}failed: target is readonly.`,
183          toRaw(this)
184        )
185      }
186      return type === OperationTypes.DELETE ? false : this
187    } else {
188      return method.apply(this, args)
189    }
190  }
191}
192 
193const mutableInstrumentations: any = { // 尤大大起名真高端大气上档次,instrumentation 乐器,mutable 的 ProxyHandler
194  get(key: any) {
195    return get(this, key, toReactive)
196  },
197  get size() {
198    return size(this)
199  },
200  has,
201  add,
202  set,
203  delete: deleteEntry,
204  clear,
205  forEach: createForEach(false)
206}
207 
208const readonlyInstrumentations: any = { // 尤大大起名真高端大气上档次,instrumentation 乐器,readonly 的 ProxyHandler
209  get(key: any) {
210    return get(this, key, toReadonly)
211  },
212  get size() {
213    return size(this)
214  },
215  has,
216  add: createReadonlyMethod(add, OperationTypes.ADD),
217  set: createReadonlyMethod(set, OperationTypes.SET),
218  delete: createReadonlyMethod(deleteEntry, OperationTypes.DELETE),
219  clear: createReadonlyMethod(clear, OperationTypes.CLEAR),
220  forEach: createForEach(true)
221}
222 
223// 在 instrumentation 乐器上,给所有 Iterate 操作,添加 createIterableMethod 方法
224const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
225iteratorMethods.forEach(method => {
226  mutableInstrumentations[method] = createIterableMethod(method, false)
227  readonlyInstrumentations[method] = createIterableMethod(method, true)
228})
229 
230function createInstrumentationGetter(instrumentations: any) { // 二次封装 get 方法,通过 getter 操作把所有其他操作类型(method)都绑定上
231  return function getInstrumented(
232    target: any,
233    key: string | symbol,
234    receiver: any
235  ) {
236    target =
237      hasOwn(instrumentations, key) && key in target ? instrumentations : target
238    return Reflect.get(target, key, receiver)
239  }
240}
241 
242export const mutableCollectionHandlers: ProxyHandler<any> = { // 方式一、通过 getter 操作把 mutableInstrumentations 上的所有其他操作类型(method)都绑定上
243  get: createInstrumentationGetter(mutableInstrumentations)
244}
245 
246export const readonlyCollectionHandlers: ProxyHandler<any> = { // 方式二、通过 getter 操作把 readonlyInstrumentations 上的所有其他操作类型(method)都绑定上
247  get: createInstrumentationGetter(readonlyInstrumentations)
248}

总结

至此,我们已把 vue-reactivity 这个 package 整个实现响应式数据的代码包都过了一遍,通过其源码,我们可以学习到不少东西,这些东西及知识点基本上在我们写业务系统的时候是没有机会用上场的。但现在 ES 6 甚至 ES 7 大环境都开始支持起来了。我们基本上很多新东西都可以尝试在业务系统上面用起来了。新的特性,我们可以根据场景有选择的去用上。比如 Typescript,目前 React / Vue 的 boilerplate 项目 / cl 基本已经提供了 Typescript 版本的模版。接下来,将要来探索一下 Vue Next 的其他 package 吧。

作者: 博主

Talk is cheap, show me the code!

发表评论

邮箱地址不会被公开。

Captcha Code