@feng3d/reactivity 0.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -26
- package/dist/index.js +1018 -58
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +1017 -57
- package/dist/index.umd.cjs.map +1 -1
- package/lib/Reactivity.d.ts +69 -0
- package/lib/Reactivity.d.ts.map +1 -0
- package/lib/arrayInstrumentations.d.ts +2 -0
- package/lib/arrayInstrumentations.d.ts.map +1 -0
- package/lib/baseHandlers.d.ts +5 -0
- package/lib/baseHandlers.d.ts.map +1 -0
- package/lib/batch.d.ts +23 -0
- package/lib/batch.d.ts.map +1 -0
- package/lib/collectionHandlers.d.ts +7 -0
- package/lib/collectionHandlers.d.ts.map +1 -0
- package/lib/computed.d.ts +94 -0
- package/lib/computed.d.ts.map +1 -0
- package/lib/effect.d.ts +52 -0
- package/lib/effect.d.ts.map +1 -0
- package/lib/index.d.ts +7 -18
- package/lib/index.d.ts.map +1 -1
- package/lib/property.d.ts +44 -0
- package/lib/property.d.ts.map +1 -0
- package/lib/reactive.d.ts +66 -0
- package/lib/reactive.d.ts.map +1 -0
- package/lib/ref.d.ts +41 -0
- package/lib/ref.d.ts.map +1 -0
- package/lib/shared/constants.d.ts +34 -0
- package/lib/shared/constants.d.ts.map +1 -0
- package/lib/shared/general.d.ts +36 -0
- package/lib/shared/general.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/Reactivity.ts +126 -0
- package/src/arrayInstrumentations.ts +448 -0
- package/src/baseHandlers.ts +220 -0
- package/src/batch.ts +91 -0
- package/src/collectionHandlers.ts +298 -0
- package/src/computed.ts +201 -0
- package/src/effect.ts +108 -0
- package/src/index.ts +7 -200
- package/src/property.ts +223 -0
- package/src/reactive.ts +130 -0
- package/src/ref.ts +88 -0
- package/src/shared/constants.ts +41 -0
- package/src/shared/general.ts +109 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/* eslint-disable prefer-rest-params */
|
|
2
|
+
|
|
3
|
+
import { batchRun } from './batch';
|
|
4
|
+
import { noTrack } from './Reactivity';
|
|
5
|
+
import { PropertyReactivity } from './property';
|
|
6
|
+
import { isProxy, toReactive } from './reactive';
|
|
7
|
+
import { ARRAY_ITERATE_KEY, TrackOpTypes } from './shared/constants';
|
|
8
|
+
import { isArray, toRaw } from './shared/general';
|
|
9
|
+
|
|
10
|
+
export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
|
|
11
|
+
__proto__: null,
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 返回一个迭代器,用于遍历数组的响应式值
|
|
15
|
+
*/
|
|
16
|
+
[Symbol.iterator]()
|
|
17
|
+
{
|
|
18
|
+
return iterator(this, Symbol.iterator, toReactive);
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 连接数组并返回新数组,处理响应式数组
|
|
23
|
+
*/
|
|
24
|
+
concat(...args: unknown[])
|
|
25
|
+
{
|
|
26
|
+
return reactiveReadArray(this).concat(
|
|
27
|
+
...args.map((x) => (isArray(x) ? reactiveReadArray(x) : x)),
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 返回一个迭代器,用于遍历数组的键值对,并将值转换为响应式
|
|
33
|
+
*/
|
|
34
|
+
entries()
|
|
35
|
+
{
|
|
36
|
+
return iterator(this, 'entries', (value: [number, unknown]) =>
|
|
37
|
+
{
|
|
38
|
+
value[1] = toReactive(value[1]);
|
|
39
|
+
|
|
40
|
+
return value;
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 测试数组中的所有元素是否都通过了指定函数的测试
|
|
46
|
+
*/
|
|
47
|
+
every(fn: (item: unknown, index: number, array: unknown[]) => unknown,
|
|
48
|
+
thisArg?: unknown,
|
|
49
|
+
)
|
|
50
|
+
{
|
|
51
|
+
return apply(this, 'every', fn, thisArg, undefined, arguments);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 创建一个新数组,包含通过指定函数测试的所有元素
|
|
56
|
+
*/
|
|
57
|
+
filter(fn: (item: unknown, index: number, array: unknown[]) => unknown,
|
|
58
|
+
thisArg?: unknown,
|
|
59
|
+
)
|
|
60
|
+
{
|
|
61
|
+
return apply(this, 'filter', fn, thisArg, (v) => v.map(toReactive), arguments);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 返回数组中满足指定测试函数的第一个元素
|
|
66
|
+
*/
|
|
67
|
+
find(fn: (item: unknown, index: number, array: unknown[]) => boolean,
|
|
68
|
+
thisArg?: unknown,
|
|
69
|
+
)
|
|
70
|
+
{
|
|
71
|
+
return apply(this, 'find', fn, thisArg, toReactive, arguments);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 返回数组中满足指定测试函数的第一个元素的索引
|
|
76
|
+
*/
|
|
77
|
+
findIndex(fn: (item: unknown, index: number, array: unknown[]) => boolean,
|
|
78
|
+
thisArg?: unknown,
|
|
79
|
+
)
|
|
80
|
+
{
|
|
81
|
+
return apply(this, 'findIndex', fn, thisArg, undefined, arguments);
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 返回数组中满足指定测试函数的最后一个元素
|
|
86
|
+
*/
|
|
87
|
+
findLast(fn: (item: unknown, index: number, array: unknown[]) => boolean,
|
|
88
|
+
thisArg?: unknown,
|
|
89
|
+
)
|
|
90
|
+
{
|
|
91
|
+
return apply(this, 'findLast', fn, thisArg, toReactive, arguments);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 返回数组中满足指定测试函数的最后一个元素的索引
|
|
96
|
+
*/
|
|
97
|
+
findLastIndex(fn: (item: unknown, index: number, array: unknown[]) => boolean,
|
|
98
|
+
thisArg?: unknown,
|
|
99
|
+
)
|
|
100
|
+
{
|
|
101
|
+
return apply(this, 'findLastIndex', fn, thisArg, undefined, arguments);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 对数组中的每个元素执行指定函数
|
|
108
|
+
*/
|
|
109
|
+
forEach(fn: (item: unknown, index: number, array: unknown[]) => unknown,
|
|
110
|
+
thisArg?: unknown,
|
|
111
|
+
)
|
|
112
|
+
{
|
|
113
|
+
return apply(this, 'forEach', fn, thisArg, undefined, arguments);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 判断数组是否包含指定元素,处理响应式值
|
|
118
|
+
*/
|
|
119
|
+
includes(...args: unknown[])
|
|
120
|
+
{
|
|
121
|
+
return searchProxy(this, 'includes', args);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 返回数组中指定元素第一次出现的索引,处理响应式值
|
|
126
|
+
*/
|
|
127
|
+
indexOf(...args: unknown[])
|
|
128
|
+
{
|
|
129
|
+
return searchProxy(this, 'indexOf', args);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 将数组的所有元素连接成一个字符串
|
|
134
|
+
*/
|
|
135
|
+
join(separator?: string)
|
|
136
|
+
{
|
|
137
|
+
return reactiveReadArray(this).join(separator);
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// keys() iterator only reads `length`, no optimisation required
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 返回数组中指定元素最后一次出现的索引,处理响应式值
|
|
144
|
+
*/
|
|
145
|
+
lastIndexOf(...args: unknown[])
|
|
146
|
+
{
|
|
147
|
+
return searchProxy(this, 'lastIndexOf', args);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 创建一个新数组,包含对原数组每个元素调用指定函数的结果
|
|
152
|
+
*/
|
|
153
|
+
map(fn: (item: unknown, index: number, array: unknown[]) => unknown,
|
|
154
|
+
thisArg?: unknown,
|
|
155
|
+
)
|
|
156
|
+
{
|
|
157
|
+
return apply(this, 'map', fn, thisArg, undefined, arguments);
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 移除数组的最后一个元素并返回该元素,避免跟踪长度变化
|
|
162
|
+
*/
|
|
163
|
+
pop()
|
|
164
|
+
{
|
|
165
|
+
return noTracking(this, 'pop');
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 向数组末尾添加一个或多个元素,并返回新的长度,避免跟踪长度变化
|
|
170
|
+
*/
|
|
171
|
+
push(...args: unknown[])
|
|
172
|
+
{
|
|
173
|
+
return noTracking(this, 'push', args);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 对数组中的每个元素执行累加器函数,并返回最终结果
|
|
178
|
+
*/
|
|
179
|
+
reduce(fn: (
|
|
180
|
+
acc: unknown,
|
|
181
|
+
item: unknown,
|
|
182
|
+
index: number,
|
|
183
|
+
array: unknown[],
|
|
184
|
+
) => unknown,
|
|
185
|
+
...args: unknown[]
|
|
186
|
+
)
|
|
187
|
+
{
|
|
188
|
+
return reduce(this, 'reduce', fn, args);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 从右到左对数组中的每个元素执行累加器函数,并返回最终结果
|
|
193
|
+
*/
|
|
194
|
+
reduceRight(
|
|
195
|
+
fn: (
|
|
196
|
+
acc: unknown,
|
|
197
|
+
item: unknown,
|
|
198
|
+
index: number,
|
|
199
|
+
array: unknown[],
|
|
200
|
+
) => unknown,
|
|
201
|
+
...args: unknown[]
|
|
202
|
+
)
|
|
203
|
+
{
|
|
204
|
+
return reduce(this, 'reduceRight', fn, args);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 移除数组的第一个元素并返回该元素,避免跟踪长度变化
|
|
209
|
+
*/
|
|
210
|
+
shift()
|
|
211
|
+
{
|
|
212
|
+
return noTracking(this, 'shift');
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// slice could use ARRAY_ITERATE but also seems to beg for range tracking
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 测试数组中的某些元素是否通过了指定函数的测试
|
|
219
|
+
*/
|
|
220
|
+
some(
|
|
221
|
+
fn: (item: unknown, index: number, array: unknown[]) => unknown,
|
|
222
|
+
thisArg?: unknown,
|
|
223
|
+
)
|
|
224
|
+
{
|
|
225
|
+
return apply(this, 'some', fn, thisArg, undefined, arguments);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 通过删除或替换现有元素或添加新元素来修改数组,避免跟踪长度变化
|
|
230
|
+
*/
|
|
231
|
+
splice(...args: unknown[])
|
|
232
|
+
{
|
|
233
|
+
return noTracking(this, 'splice', args);
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 返回一个新数组,包含原数组的反转副本
|
|
238
|
+
*/
|
|
239
|
+
toReversed()
|
|
240
|
+
{
|
|
241
|
+
// @ts-expect-error user code may run in es2016+
|
|
242
|
+
return reactiveReadArray(this).toReversed();
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 返回一个新数组,包含原数组的排序副本
|
|
247
|
+
*/
|
|
248
|
+
toSorted(comparer?: (a: unknown, b: unknown) => number)
|
|
249
|
+
{
|
|
250
|
+
// @ts-expect-error user code may run in es2016+
|
|
251
|
+
return reactiveReadArray(this).toSorted(comparer);
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 返回一个新数组,包含原数组的切片副本
|
|
256
|
+
*/
|
|
257
|
+
toSpliced(...args: unknown[])
|
|
258
|
+
{
|
|
259
|
+
// @ts-expect-error user code may run in es2016+
|
|
260
|
+
return (reactiveReadArray(this).toSpliced as any)(...args);
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 向数组开头添加一个或多个元素,并返回新的长度,避免跟踪长度变化
|
|
265
|
+
*/
|
|
266
|
+
unshift(...args: unknown[])
|
|
267
|
+
{
|
|
268
|
+
return noTracking(this, 'unshift', args);
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 返回一个迭代器,用于遍历数组的响应式值
|
|
273
|
+
*/
|
|
274
|
+
values()
|
|
275
|
+
{
|
|
276
|
+
return iterator(this, 'values', toReactive);
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// instrument iterators to take ARRAY_ITERATE dependency
|
|
281
|
+
function iterator(
|
|
282
|
+
self: unknown[],
|
|
283
|
+
method: keyof Array<unknown>,
|
|
284
|
+
wrapValue: (value: any) => unknown,
|
|
285
|
+
)
|
|
286
|
+
{
|
|
287
|
+
// note that taking ARRAY_ITERATE dependency here is not strictly equivalent
|
|
288
|
+
// to calling iterate on the proxified array.
|
|
289
|
+
// creating the iterator does not access any array property:
|
|
290
|
+
// it is only when .next() is called that length and indexes are accessed.
|
|
291
|
+
// pushed to the extreme, an iterator could be created in one effect scope,
|
|
292
|
+
// partially iterated in another, then iterated more in yet another.
|
|
293
|
+
// given that JS iterator can only be read once, this doesn't seem like
|
|
294
|
+
// a plausible use-case, so this tracking simplification seems ok.
|
|
295
|
+
const arr = shallowReadArray(self);
|
|
296
|
+
const iter = (arr[method] as any)() as IterableIterator<unknown> & {
|
|
297
|
+
_next: IterableIterator<unknown>['next']
|
|
298
|
+
};
|
|
299
|
+
if (arr !== self)
|
|
300
|
+
{
|
|
301
|
+
iter._next = iter.next;
|
|
302
|
+
iter.next = () =>
|
|
303
|
+
{
|
|
304
|
+
const result = iter._next();
|
|
305
|
+
if (result.value)
|
|
306
|
+
{
|
|
307
|
+
result.value = wrapValue(result.value);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return result;
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return iter;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 跟踪数组的迭代操作并返回原始数组
|
|
319
|
+
*/
|
|
320
|
+
function shallowReadArray<T>(arr: T[]): T[]
|
|
321
|
+
{
|
|
322
|
+
PropertyReactivity.track((arr = toRaw(arr)), TrackOpTypes.ITERATE, ARRAY_ITERATE_KEY);
|
|
323
|
+
|
|
324
|
+
return arr;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function reactiveReadArray<T>(array: T[]): T[]
|
|
328
|
+
{
|
|
329
|
+
const raw = toRaw(array);
|
|
330
|
+
if (raw === array) return raw;
|
|
331
|
+
PropertyReactivity.track(raw, TrackOpTypes.ITERATE, ARRAY_ITERATE_KEY);
|
|
332
|
+
|
|
333
|
+
return raw.map(toReactive);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// in the codebase we enforce es2016, but user code may run in environments
|
|
337
|
+
// higher than that
|
|
338
|
+
type ArrayMethods = keyof Array<any> | 'findLast' | 'findLastIndex';
|
|
339
|
+
|
|
340
|
+
// instrument functions that read (potentially) all items
|
|
341
|
+
// to take ARRAY_ITERATE dependency
|
|
342
|
+
function apply(
|
|
343
|
+
self: unknown[],
|
|
344
|
+
method: ArrayMethods,
|
|
345
|
+
fn: (item: unknown, index: number, array: unknown[]) => unknown,
|
|
346
|
+
thisArg?: unknown,
|
|
347
|
+
wrappedRetFn?: (result: any) => unknown,
|
|
348
|
+
args?: IArguments,
|
|
349
|
+
)
|
|
350
|
+
{
|
|
351
|
+
const arr = shallowReadArray(self);
|
|
352
|
+
const needsWrap = arr !== self;
|
|
353
|
+
const methodFn = arr[method];
|
|
354
|
+
|
|
355
|
+
// #11759
|
|
356
|
+
// If the method being called is from a user-extended Array, the arguments will be unknown
|
|
357
|
+
// (unknown order and unknown parameter types). In this case, we skip the shallowReadArray
|
|
358
|
+
// handling and directly call apply with self.
|
|
359
|
+
if (methodFn !== Array.prototype[method as any])
|
|
360
|
+
{
|
|
361
|
+
const result = methodFn.apply(self, args);
|
|
362
|
+
|
|
363
|
+
return needsWrap ? toReactive(result) : result;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let wrappedFn = fn;
|
|
367
|
+
if (arr !== self)
|
|
368
|
+
{
|
|
369
|
+
if (needsWrap)
|
|
370
|
+
{
|
|
371
|
+
wrappedFn = function (this: unknown, item, index)
|
|
372
|
+
{
|
|
373
|
+
return fn.call(this, toReactive(item), index, self);
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
else if (fn.length > 2)
|
|
377
|
+
{
|
|
378
|
+
wrappedFn = function (this: unknown, item, index)
|
|
379
|
+
{
|
|
380
|
+
return fn.call(this, item, index, self);
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const result = methodFn.call(arr, wrappedFn, thisArg);
|
|
385
|
+
|
|
386
|
+
return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// instrument reduce and reduceRight to take ARRAY_ITERATE dependency
|
|
390
|
+
function reduce(
|
|
391
|
+
self: unknown[],
|
|
392
|
+
method: keyof Array<any>,
|
|
393
|
+
fn: (acc: unknown, item: unknown, index: number, array: unknown[]) => unknown,
|
|
394
|
+
args: unknown[],
|
|
395
|
+
)
|
|
396
|
+
{
|
|
397
|
+
const arr = shallowReadArray(self);
|
|
398
|
+
let wrappedFn = fn;
|
|
399
|
+
if (arr !== self)
|
|
400
|
+
{
|
|
401
|
+
wrappedFn = function (this: unknown, acc, item, index)
|
|
402
|
+
{
|
|
403
|
+
return fn.call(this, acc, toReactive(item), index, self);
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return (arr[method] as any)(wrappedFn, ...args);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// instrument identity-sensitive methods to account for reactive proxies
|
|
411
|
+
function searchProxy(
|
|
412
|
+
self: unknown[],
|
|
413
|
+
method: keyof Array<any>,
|
|
414
|
+
args: unknown[],
|
|
415
|
+
)
|
|
416
|
+
{
|
|
417
|
+
const arr = toRaw(self) as any;
|
|
418
|
+
PropertyReactivity.track(arr, TrackOpTypes.ITERATE, ARRAY_ITERATE_KEY);
|
|
419
|
+
// we run the method using the original args first (which may be reactive)
|
|
420
|
+
const res = arr[method](...args);
|
|
421
|
+
|
|
422
|
+
// if that didn't work, run it again using raw values.
|
|
423
|
+
if ((res === -1 || res === false) && isProxy(args[0]))
|
|
424
|
+
{
|
|
425
|
+
args[0] = toRaw(args[0]);
|
|
426
|
+
|
|
427
|
+
return arr[method](...args);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return res;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// instrument length-altering mutation methods to avoid length being tracked
|
|
434
|
+
// which leads to infinite loops in some cases (#2137)
|
|
435
|
+
function noTracking(
|
|
436
|
+
self: unknown[],
|
|
437
|
+
method: keyof Array<any>,
|
|
438
|
+
args: unknown[] = [],
|
|
439
|
+
)
|
|
440
|
+
{
|
|
441
|
+
const res = batchRun(() =>
|
|
442
|
+
noTrack(() =>
|
|
443
|
+
(toRaw(self) as any)[method].apply(self, args)
|
|
444
|
+
)
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
return res;
|
|
448
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { arrayInstrumentations } from './arrayInstrumentations';
|
|
2
|
+
import { reactive, reactiveMap } from './reactive';
|
|
3
|
+
import { isRef } from './ref';
|
|
4
|
+
import { ITERATE_KEY, ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './shared/constants';
|
|
5
|
+
import { hasChanged, hasOwn, isArray, isIntegerKey, isObject, isSymbol, makeMap, Target, toRaw } from './shared/general';
|
|
6
|
+
import { PropertyReactivity } from './property';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 基础响应式处理器。
|
|
10
|
+
*/
|
|
11
|
+
class BaseReactiveHandler implements ProxyHandler<Target>
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* 获取对象的属性值。
|
|
15
|
+
*
|
|
16
|
+
* @param target 对象本身
|
|
17
|
+
* @param key 属性名
|
|
18
|
+
* @param receiver 代理对象
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
get(target: Target, key: string | symbol, receiver: object): any
|
|
22
|
+
{
|
|
23
|
+
//
|
|
24
|
+
if (key === ReactiveFlags.IS_REACTIVE) // 判断是否为响应式对象
|
|
25
|
+
{
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
else if (key === ReactiveFlags.RAW) // 获取原始对象
|
|
29
|
+
{
|
|
30
|
+
if (
|
|
31
|
+
receiver
|
|
32
|
+
=== reactiveMap.get(target)
|
|
33
|
+
// receiver is not the reactive proxy, but has the same prototype
|
|
34
|
+
// this means the receiver is a user proxy of the reactive proxy
|
|
35
|
+
|| Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
|
|
36
|
+
)
|
|
37
|
+
{
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
// early return undefined
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const targetIsArray = isArray(target);
|
|
46
|
+
|
|
47
|
+
let fn: Function | undefined;
|
|
48
|
+
if (targetIsArray && (fn = arrayInstrumentations[key]))
|
|
49
|
+
{
|
|
50
|
+
return fn;
|
|
51
|
+
}
|
|
52
|
+
if (key === 'hasOwnProperty')
|
|
53
|
+
{
|
|
54
|
+
return hasOwnProperty;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const res = Reflect.get(
|
|
58
|
+
target,
|
|
59
|
+
key,
|
|
60
|
+
isRef(target) ? target : receiver,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key))
|
|
64
|
+
{
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//
|
|
69
|
+
PropertyReactivity.track(target, TrackOpTypes.GET, key as any);
|
|
70
|
+
|
|
71
|
+
// 如果是 ref,则返回 ref.value
|
|
72
|
+
if (isRef(res))
|
|
73
|
+
{
|
|
74
|
+
return targetIsArray && isIntegerKey(key) ? res : res.value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 如果是对象,则递归响应式化
|
|
78
|
+
if (isObject(res))
|
|
79
|
+
{
|
|
80
|
+
return reactive(res);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return res;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 可变响应式处理器。
|
|
89
|
+
*/
|
|
90
|
+
class MutableReactiveHandler extends BaseReactiveHandler
|
|
91
|
+
{
|
|
92
|
+
/**
|
|
93
|
+
* 设置对象的属性值。
|
|
94
|
+
* @param target 被代理的对象。
|
|
95
|
+
* @param key 属性名。
|
|
96
|
+
* @param value 新值。
|
|
97
|
+
* @param receiver 代理对象。
|
|
98
|
+
* @returns 设置是否成功。
|
|
99
|
+
*/
|
|
100
|
+
set(
|
|
101
|
+
target: Record<string | symbol, unknown>,
|
|
102
|
+
key: string | symbol,
|
|
103
|
+
value: unknown,
|
|
104
|
+
receiver: object,
|
|
105
|
+
): boolean
|
|
106
|
+
{
|
|
107
|
+
let oldValue = target[key];
|
|
108
|
+
|
|
109
|
+
oldValue = toRaw(oldValue);
|
|
110
|
+
value = toRaw(value);
|
|
111
|
+
|
|
112
|
+
if (!isArray(target) && isRef(oldValue) && !isRef(value))
|
|
113
|
+
{
|
|
114
|
+
oldValue.value = value;
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const hadKey
|
|
120
|
+
= isArray(target) && isIntegerKey(key)
|
|
121
|
+
? Number(key) < target.length
|
|
122
|
+
: hasOwn(target, key);
|
|
123
|
+
const result = Reflect.set(
|
|
124
|
+
target,
|
|
125
|
+
key,
|
|
126
|
+
value,
|
|
127
|
+
isRef(target) ? target : receiver,
|
|
128
|
+
);
|
|
129
|
+
//
|
|
130
|
+
__DEV__ && console.assert(target === toRaw(receiver));
|
|
131
|
+
|
|
132
|
+
// 如果目标在原始原型链中,则不要触发
|
|
133
|
+
if (target === toRaw(receiver))
|
|
134
|
+
{
|
|
135
|
+
if (!hadKey)
|
|
136
|
+
{
|
|
137
|
+
PropertyReactivity.trigger(target, TriggerOpTypes.ADD, key, value);
|
|
138
|
+
}
|
|
139
|
+
else if (hasChanged(value, oldValue))
|
|
140
|
+
{
|
|
141
|
+
PropertyReactivity.trigger(target, TriggerOpTypes.SET, key, value, oldValue);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 删除对象的属性。
|
|
150
|
+
*
|
|
151
|
+
* @param target 被代理的对象。
|
|
152
|
+
* @param key 属性名。
|
|
153
|
+
* @returns 删除是否成功。
|
|
154
|
+
*/
|
|
155
|
+
deleteProperty(
|
|
156
|
+
target: Record<string | symbol, unknown>,
|
|
157
|
+
key: string | symbol,
|
|
158
|
+
): boolean
|
|
159
|
+
{
|
|
160
|
+
const hadKey = hasOwn(target, key);
|
|
161
|
+
const oldValue = target[key];
|
|
162
|
+
const result = Reflect.deleteProperty(target, key);
|
|
163
|
+
if (result && hadKey)
|
|
164
|
+
{
|
|
165
|
+
PropertyReactivity.trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean
|
|
172
|
+
{
|
|
173
|
+
const result = Reflect.has(target, key);
|
|
174
|
+
if (!isSymbol(key) || !builtInSymbols.has(key))
|
|
175
|
+
{
|
|
176
|
+
PropertyReactivity.track(target, TrackOpTypes.HAS, key);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[]
|
|
183
|
+
{
|
|
184
|
+
PropertyReactivity.track(
|
|
185
|
+
target,
|
|
186
|
+
TrackOpTypes.ITERATE,
|
|
187
|
+
isArray(target) ? 'length' : ITERATE_KEY,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return Reflect.ownKeys(target);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 可变响应式处理器。
|
|
196
|
+
*/
|
|
197
|
+
export const mutableHandlers: ProxyHandler<object> = new MutableReactiveHandler();
|
|
198
|
+
|
|
199
|
+
function hasOwnProperty(this: object, key: unknown)
|
|
200
|
+
{
|
|
201
|
+
// #10455 hasOwnProperty may be called with non-string values
|
|
202
|
+
if (!isSymbol(key)) key = String(key);
|
|
203
|
+
const obj = toRaw(this);
|
|
204
|
+
PropertyReactivity.track(obj, TrackOpTypes.HAS, key);
|
|
205
|
+
|
|
206
|
+
return obj.hasOwnProperty(key as string);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const builtInSymbols = new Set(
|
|
210
|
+
/* @__PURE__*/
|
|
211
|
+
Object.getOwnPropertyNames(Symbol)
|
|
212
|
+
// ios10.x Object.getOwnPropertyNames(Symbol) can enumerate 'arguments' and 'caller'
|
|
213
|
+
// but accessing them on Symbol leads to TypeError because Symbol is a strict mode
|
|
214
|
+
// function
|
|
215
|
+
.filter((key) => key !== 'arguments' && key !== 'caller')
|
|
216
|
+
.map((key) => Symbol[key as keyof SymbolConstructor])
|
|
217
|
+
.filter(isSymbol),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const isNonTrackableKeys = /* @__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`);
|