@yiin/reactive-proxy-state 1.0.4 → 1.0.6
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 +56 -17
- package/dist/computed.d.ts +9 -4
- package/dist/computed.js +28 -29
- package/dist/reactive.js +47 -32
- package/dist/ref.d.ts +8 -7
- package/dist/ref.js +22 -22
- package/dist/state.js +89 -85
- package/dist/types.d.ts +1 -2
- package/dist/utils.d.ts +5 -10
- package/dist/utils.js +50 -87
- package/dist/watch.d.ts +3 -8
- package/dist/watch.js +15 -45
- package/dist/watchEffect.d.ts +10 -5
- package/dist/watchEffect.js +61 -46
- package/dist/wrapArray.js +22 -32
- package/dist/wrapMap.js +26 -37
- package/dist/wrapSet.js +27 -44
- package/package.json +8 -3
package/dist/watch.js
CHANGED
|
@@ -1,74 +1,44 @@
|
|
|
1
1
|
import { watchEffect } from './watchEffect';
|
|
2
2
|
import { traverse, deepClone } from './utils';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @param source - A function that returns the value to watch
|
|
7
|
-
* @param callback - Function to call when the source changes
|
|
8
|
-
* @param options - Watch options (immediate, deep)
|
|
9
|
-
* @returns A function to stop watching
|
|
4
|
+
* watches a reactive source (getter function or reactive object/ref)
|
|
5
|
+
* and runs a callback when the source's value changes.
|
|
10
6
|
*/
|
|
11
|
-
export function watch(sourceInput,
|
|
12
|
-
callback, options = {}) {
|
|
13
|
-
// Default deep to true unless explicitly false
|
|
7
|
+
export function watch(sourceInput, callback, options = {}) {
|
|
14
8
|
const { immediate = false, deep = true } = options;
|
|
15
|
-
//
|
|
9
|
+
// normalize source to always be a getter function
|
|
16
10
|
const source = typeof sourceInput === 'function'
|
|
17
11
|
? sourceInput
|
|
18
12
|
: () => sourceInput;
|
|
19
13
|
let oldValue;
|
|
20
14
|
let initialized = false;
|
|
21
|
-
//
|
|
15
|
+
// use watchEffect internally to handle dependency tracking
|
|
22
16
|
const stopEffect = watchEffect(() => {
|
|
23
|
-
// 1. Run the source function to get the current value
|
|
24
17
|
const currentValue = source();
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// This check becomes redundant if deep defaults to true, but keep for potential explicit {deep: false} on collections?
|
|
29
|
-
// Maybe simplify: if deep is true, always traverse.
|
|
30
|
-
/*
|
|
31
|
-
if (!needsDeepTracking && currentValue && typeof currentValue === 'object') {
|
|
32
|
-
if (Array.isArray(currentValue) || currentValue instanceof Map || currentValue instanceof Set) {
|
|
33
|
-
needsDeepTracking = true; // Track collections even if deep:false? Vue does shallow on collections by default.
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
*/
|
|
37
|
-
// 2. If deep tracking needed, traverse the value *for tracking purposes only*
|
|
38
|
-
if (needsDeepTracking) {
|
|
39
|
-
traverse(currentValue); // Discard result, only needed for effect tracking
|
|
18
|
+
// if deep watching, traverse the current value to track nested dependencies
|
|
19
|
+
if (deep) {
|
|
20
|
+
traverse(currentValue);
|
|
40
21
|
}
|
|
41
|
-
// 3. Compare the actual currentValue with the oldValue
|
|
42
22
|
if (initialized) {
|
|
43
23
|
let hasChanged = false;
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
// For deep watches, the effect running *implies* a relevant change occurred.
|
|
50
|
-
// The traverse() call ensures dependencies were tracked. If the effect
|
|
51
|
-
// runs, we assume a change happened without needing deepEqual.
|
|
52
|
-
hasChanged = true;
|
|
53
|
-
}
|
|
24
|
+
// for deep watches, the effect running implies a dependency changed.
|
|
25
|
+
// for shallow, explicitly check reference equality.
|
|
26
|
+
hasChanged = deep || currentValue !== oldValue;
|
|
54
27
|
if (hasChanged) {
|
|
55
|
-
// Get the value to pass as the previous oldValue to the callback
|
|
56
28
|
const prevOldValue = oldValue;
|
|
57
|
-
//
|
|
29
|
+
// store a clone for deep watches to pass as the correct oldValue next time
|
|
58
30
|
oldValue = deep ? deepClone(currentValue) : currentValue;
|
|
59
31
|
callback(currentValue, prevOldValue);
|
|
60
32
|
}
|
|
61
33
|
}
|
|
62
34
|
else {
|
|
63
|
-
//
|
|
35
|
+
// first run: store initial value (cloned if deep) and run immediate callback if requested
|
|
64
36
|
oldValue = deep ? deepClone(currentValue) : currentValue;
|
|
65
37
|
initialized = true;
|
|
66
38
|
if (immediate) {
|
|
67
|
-
//
|
|
68
|
-
callback(currentValue, undefined);
|
|
39
|
+
callback(currentValue, undefined); // pass undefined as oldValue for immediate
|
|
69
40
|
}
|
|
70
41
|
}
|
|
71
|
-
});
|
|
72
|
-
// Return the stop handle from watchEffect
|
|
42
|
+
}, { lazy: false }); // run immediately (watchEffect handles `immediate` option internally)
|
|
73
43
|
return stopEffect;
|
|
74
44
|
}
|
package/dist/watchEffect.d.ts
CHANGED
|
@@ -14,15 +14,19 @@ export interface TrackedEffect<T = any> {
|
|
|
14
14
|
export declare let activeEffect: TrackedEffect<any> | null;
|
|
15
15
|
export declare function setActiveEffect(effect: TrackedEffect<any> | null): void;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* removes an effect from all dependency sets it belongs to.
|
|
18
|
+
* this is crucial to prevent memory leaks and unnecessary updates when an effect is stopped or re-run.
|
|
18
19
|
*/
|
|
19
20
|
export declare function cleanupEffect(effect: TrackedEffect<any>): void;
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
+
* establishes a dependency between the currently active effect and a specific object property.
|
|
23
|
+
* called by proxy getters or ref getters.
|
|
22
24
|
*/
|
|
23
25
|
export declare function track(target: object, key: string | symbol): void;
|
|
24
26
|
/**
|
|
25
|
-
*
|
|
27
|
+
* triggers all active effects associated with a specific object property.
|
|
28
|
+
* called by proxy setters/deleters or ref setters.
|
|
29
|
+
* currently runs effects synchronously.
|
|
26
30
|
*/
|
|
27
31
|
export declare function trigger(target: object, key: string | symbol): void;
|
|
28
32
|
export interface WatchEffectOptions {
|
|
@@ -42,8 +46,9 @@ export interface WatchEffectOptions {
|
|
|
42
46
|
}) => void;
|
|
43
47
|
}
|
|
44
48
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
49
|
+
* runs a function immediately, tracks its reactive dependencies, and re-runs it
|
|
50
|
+
* synchronously whenever any of those dependencies change.
|
|
51
|
+
* returns a stop handle to manually stop the effect.
|
|
47
52
|
*/
|
|
48
53
|
export declare function watchEffect<T>(effectCallback: EffectCallback<T>, options?: WatchEffectOptions): WatchEffectStopHandle<T>;
|
|
49
54
|
export {};
|
package/dist/watchEffect.js
CHANGED
|
@@ -1,139 +1,154 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Note: activeEffect can hold effects with different return types
|
|
1
|
+
// tracks the currently executing effect to establish dependencies
|
|
3
2
|
export let activeEffect = null;
|
|
4
|
-
//
|
|
3
|
+
// allows setting the active effect, used internally by the effect runner
|
|
5
4
|
export function setActiveEffect(effect) {
|
|
6
5
|
activeEffect = effect;
|
|
7
6
|
}
|
|
8
|
-
//
|
|
9
|
-
// The Sets will hold effects of potentially different types
|
|
7
|
+
// storage for dependencies: target object -> property key -> set of effects that depend on this key
|
|
10
8
|
const targetMap = new WeakMap();
|
|
11
9
|
/**
|
|
12
|
-
*
|
|
10
|
+
* removes an effect from all dependency sets it belongs to.
|
|
11
|
+
* this is crucial to prevent memory leaks and unnecessary updates when an effect is stopped or re-run.
|
|
13
12
|
*/
|
|
14
13
|
export function cleanupEffect(effect) {
|
|
15
14
|
if (effect.dependencies) {
|
|
16
15
|
effect.dependencies.forEach(dep => {
|
|
16
|
+
// remove this effect from the dependency set associated with a specific target/key
|
|
17
17
|
dep.delete(effect);
|
|
18
18
|
});
|
|
19
|
-
|
|
19
|
+
// clear the effect's own list of dependencies for the next run
|
|
20
|
+
effect.dependencies.clear();
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
+
* establishes a dependency between the currently active effect and a specific object property.
|
|
25
|
+
* called by proxy getters or ref getters.
|
|
24
26
|
*/
|
|
25
27
|
export function track(target, key) {
|
|
26
|
-
//
|
|
28
|
+
// do nothing if there is no active effect or if the effect is stopped
|
|
27
29
|
if (!activeEffect || !activeEffect.active)
|
|
28
30
|
return;
|
|
29
|
-
//
|
|
31
|
+
// get or create the dependency map for the target object
|
|
30
32
|
let depsMap = targetMap.get(target);
|
|
31
33
|
if (!depsMap) {
|
|
32
34
|
depsMap = new Map();
|
|
33
35
|
targetMap.set(target, depsMap);
|
|
34
36
|
}
|
|
35
|
-
//
|
|
37
|
+
// get or create the set of effects for the specific property key
|
|
36
38
|
let dep = depsMap.get(key);
|
|
37
39
|
if (!dep) {
|
|
38
40
|
dep = new Set();
|
|
39
41
|
depsMap.set(key, dep);
|
|
40
42
|
}
|
|
41
|
-
//
|
|
42
|
-
// Ensure we are adding the correct type to the Set
|
|
43
|
+
// add the current effect to the dependency set if it's not already there
|
|
43
44
|
const effectToAdd = activeEffect;
|
|
44
45
|
if (!dep.has(effectToAdd)) {
|
|
45
46
|
dep.add(effectToAdd);
|
|
46
|
-
//
|
|
47
|
+
// also add this dependency set to the effect's own tracking list for cleanup purposes
|
|
47
48
|
if (!effectToAdd.dependencies) {
|
|
48
49
|
effectToAdd.dependencies = new Set();
|
|
49
50
|
}
|
|
50
51
|
effectToAdd.dependencies.add(dep);
|
|
51
|
-
//
|
|
52
|
+
// trigger the onTrack debug hook if provided
|
|
52
53
|
if (effectToAdd.options?.onTrack) {
|
|
53
|
-
//
|
|
54
|
+
// pass the original user callback to the hook, not the internal wrapper
|
|
54
55
|
effectToAdd.options.onTrack({ effect: effectToAdd._rawCallback, target, key, type: 'track' });
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
/**
|
|
59
|
-
*
|
|
60
|
+
* triggers all active effects associated with a specific object property.
|
|
61
|
+
* called by proxy setters/deleters or ref setters.
|
|
62
|
+
* currently runs effects synchronously.
|
|
60
63
|
*/
|
|
61
64
|
export function trigger(target, key) {
|
|
62
65
|
const depsMap = targetMap.get(target);
|
|
63
66
|
if (!depsMap)
|
|
64
|
-
return;
|
|
65
|
-
//
|
|
67
|
+
return; // no effects tracked for this target
|
|
68
|
+
// use a set to collect effects to run, avoiding duplicate executions within the same trigger cycle
|
|
66
69
|
const effectsToRun = new Set();
|
|
70
|
+
// helper to add effects from a specific dependency set to the run queue
|
|
67
71
|
const addEffects = (depKey) => {
|
|
68
72
|
const dep = depsMap.get(depKey);
|
|
69
73
|
if (dep) {
|
|
70
74
|
dep.forEach(effect => {
|
|
71
|
-
//
|
|
72
|
-
//
|
|
75
|
+
// avoid triggering the effect if it's the one currently running (prevents infinite loops)
|
|
76
|
+
// also ensure the effect hasn't been stopped
|
|
73
77
|
if (effect !== activeEffect && effect.active) {
|
|
74
78
|
effectsToRun.add(effect);
|
|
75
79
|
}
|
|
76
80
|
});
|
|
77
81
|
}
|
|
78
82
|
};
|
|
83
|
+
// add effects associated with the specific key that changed
|
|
79
84
|
addEffects(key);
|
|
80
|
-
//
|
|
85
|
+
// todo: consider adding effects associated with iteration keys (like Symbol.iterator or 'length' for arrays) if applicable
|
|
86
|
+
// schedule or run the collected effects
|
|
81
87
|
effectsToRun.forEach(effect => {
|
|
82
|
-
//
|
|
88
|
+
// trigger the onTrigger debug hook if provided
|
|
83
89
|
if (effect.options?.onTrigger) {
|
|
84
90
|
effect.options.onTrigger({ effect: effect._rawCallback, target, key, type: 'trigger' });
|
|
85
91
|
}
|
|
86
|
-
//
|
|
92
|
+
// use a custom scheduler if provided, otherwise run the effect synchronously
|
|
87
93
|
if (effect.options?.scheduler) {
|
|
88
94
|
effect.options.scheduler(effect.run);
|
|
89
95
|
}
|
|
90
96
|
else {
|
|
91
|
-
effect.run(); //
|
|
97
|
+
effect.run(); // execute the effect's wrapper function (`run`)
|
|
92
98
|
}
|
|
93
99
|
});
|
|
94
100
|
}
|
|
95
101
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
102
|
+
* runs a function immediately, tracks its reactive dependencies, and re-runs it
|
|
103
|
+
* synchronously whenever any of those dependencies change.
|
|
104
|
+
* returns a stop handle to manually stop the effect.
|
|
98
105
|
*/
|
|
99
106
|
export function watchEffect(effectCallback, options = {}) {
|
|
100
|
-
//
|
|
107
|
+
// the wrapper function that manages the effect lifecycle (cleanup, tracking, execution)
|
|
101
108
|
const run = () => {
|
|
102
|
-
if (!effectFn.active)
|
|
103
|
-
|
|
109
|
+
if (!effectFn.active) {
|
|
110
|
+
// if stopped, potentially run the callback once without tracking, though behavior might be undefined
|
|
111
|
+
// vue's behavior here might differ, review needed if exact compatibility matters
|
|
112
|
+
try {
|
|
113
|
+
return effectCallback();
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
console.error("error in stopped watchEffect callback:", e);
|
|
117
|
+
// decide on return value for stopped effects that error
|
|
118
|
+
return undefined; // or rethrow?
|
|
119
|
+
}
|
|
120
|
+
}
|
|
104
121
|
const previousEffect = activeEffect;
|
|
105
122
|
try {
|
|
106
|
-
cleanupEffect(effectFn);
|
|
107
|
-
setActiveEffect(effectFn);
|
|
108
|
-
return effectCallback(); //
|
|
123
|
+
cleanupEffect(effectFn); // clean up dependencies from the previous run
|
|
124
|
+
setActiveEffect(effectFn); // set this effect as the one currently tracking
|
|
125
|
+
return effectCallback(); // execute the user's function, triggering tracks
|
|
109
126
|
}
|
|
110
127
|
finally {
|
|
111
|
-
setActiveEffect(previousEffect);
|
|
128
|
+
setActiveEffect(previousEffect); // restore the previous active effect
|
|
112
129
|
}
|
|
113
130
|
};
|
|
114
|
-
//
|
|
131
|
+
// create the internal effect object
|
|
115
132
|
const effectFn = {
|
|
116
133
|
run: run,
|
|
117
|
-
dependencies: new Set(),
|
|
134
|
+
dependencies: new Set(), // initialize empty dependencies
|
|
118
135
|
options: options,
|
|
119
|
-
active: true,
|
|
120
|
-
_rawCallback: effectCallback
|
|
136
|
+
active: true, // start as active
|
|
137
|
+
_rawCallback: effectCallback // store the original callback
|
|
121
138
|
};
|
|
122
|
-
//
|
|
139
|
+
// run the effect immediately unless the `lazy` option is true
|
|
123
140
|
if (!options.lazy) {
|
|
124
141
|
effectFn.run();
|
|
125
142
|
}
|
|
126
|
-
//
|
|
143
|
+
// create the function that stops the effect
|
|
127
144
|
const stopHandle = () => {
|
|
128
145
|
if (effectFn.active) {
|
|
129
|
-
cleanupEffect(effectFn);
|
|
130
|
-
effectFn.active = false;
|
|
131
|
-
//
|
|
132
|
-
// effectFn.dependencies = undefined; // Keep dependencies for potential re-activation?
|
|
133
|
-
// effectFn.options = undefined;
|
|
146
|
+
cleanupEffect(effectFn); // remove from dependency lists
|
|
147
|
+
effectFn.active = false; // mark as inactive
|
|
148
|
+
// potentially clear other properties like dependencies/options if desired, but keeping them might allow restart? TBD.
|
|
134
149
|
}
|
|
135
150
|
};
|
|
136
|
-
//
|
|
151
|
+
// attach the effect instance to the stop handle for potential advanced usage
|
|
137
152
|
stopHandle.effect = effectFn;
|
|
138
153
|
return stopHandle;
|
|
139
154
|
}
|
package/dist/wrapArray.js
CHANGED
|
@@ -3,20 +3,19 @@ import { reactive } from './reactive';
|
|
|
3
3
|
import { wrapMap } from './wrapMap';
|
|
4
4
|
import { wrapSet } from './wrapSet';
|
|
5
5
|
import { track, trigger } from './watchEffect';
|
|
6
|
-
//
|
|
6
|
+
// avoid repeated typeof checks
|
|
7
7
|
function isObject(v) {
|
|
8
8
|
return v && typeof v === 'object';
|
|
9
9
|
}
|
|
10
10
|
export function wrapArray(arr, emit, path) {
|
|
11
|
-
//
|
|
11
|
+
// reuse existing proxy if available for performance
|
|
12
12
|
const cachedProxy = wrapperCache.get(arr);
|
|
13
13
|
if (cachedProxy)
|
|
14
14
|
return cachedProxy;
|
|
15
15
|
const proxy = new Proxy(arr, {
|
|
16
16
|
get(target, prop, receiver) {
|
|
17
|
-
// Original track call - might be redundant if handled below but keep for now
|
|
18
17
|
track(target, prop);
|
|
19
|
-
//
|
|
18
|
+
// handle specific array mutation methods that require custom logic and event emission
|
|
20
19
|
switch (prop) {
|
|
21
20
|
case 'push':
|
|
22
21
|
track(target, 'length');
|
|
@@ -28,7 +27,7 @@ export function wrapArray(arr, emit, path) {
|
|
|
28
27
|
const event = {
|
|
29
28
|
action: 'array-push',
|
|
30
29
|
path: path,
|
|
31
|
-
key: oldLength, //
|
|
30
|
+
key: oldLength, // start index was the old length
|
|
32
31
|
items: items
|
|
33
32
|
};
|
|
34
33
|
emit(event);
|
|
@@ -132,15 +131,11 @@ export function wrapArray(arr, emit, path) {
|
|
|
132
131
|
}
|
|
133
132
|
return result;
|
|
134
133
|
};
|
|
135
|
-
//
|
|
134
|
+
// handle methods that rely on iteration state
|
|
136
135
|
case Symbol.iterator:
|
|
137
|
-
case 'values':
|
|
138
|
-
case 'keys':
|
|
139
|
-
case 'entries':
|
|
140
|
-
// Track dependency on iteration
|
|
141
|
-
track(target, Symbol.iterator);
|
|
142
|
-
// Fall through to Reflect.get and bind below
|
|
143
|
-
break;
|
|
136
|
+
case 'values':
|
|
137
|
+
case 'keys':
|
|
138
|
+
case 'entries':
|
|
144
139
|
case 'forEach':
|
|
145
140
|
case 'map':
|
|
146
141
|
case 'filter':
|
|
@@ -150,38 +145,34 @@ export function wrapArray(arr, emit, path) {
|
|
|
150
145
|
case 'findIndex':
|
|
151
146
|
case 'every':
|
|
152
147
|
case 'some':
|
|
153
|
-
case 'join':
|
|
154
|
-
// These methods depend on iteration
|
|
148
|
+
case 'join':
|
|
155
149
|
track(target, Symbol.iterator);
|
|
156
|
-
//
|
|
150
|
+
// fall through to default behavior (usually binding)
|
|
157
151
|
break;
|
|
158
152
|
case 'length':
|
|
159
|
-
// Explicitly track length access
|
|
160
153
|
track(target, 'length');
|
|
161
154
|
return Reflect.get(target, prop, receiver);
|
|
162
155
|
}
|
|
163
|
-
// Fallback for index access and other properties
|
|
164
156
|
const value = Reflect.get(target, prop, receiver);
|
|
165
|
-
//
|
|
157
|
+
// determine if the property access is numeric array index access
|
|
166
158
|
const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(prop, 10)));
|
|
167
159
|
if (isNumericIndex) {
|
|
168
|
-
// Track access to specific index
|
|
169
160
|
track(target, String(prop));
|
|
170
161
|
if (!isObject(value))
|
|
171
162
|
return value;
|
|
172
|
-
//
|
|
163
|
+
// reuse existing proxy for nested object/array if available
|
|
173
164
|
const cachedValueProxy = wrapperCache.get(value);
|
|
174
165
|
if (cachedValueProxy)
|
|
175
166
|
return cachedValueProxy;
|
|
176
|
-
//
|
|
167
|
+
// calculate the nested path for the element, optimizing with caching
|
|
177
168
|
const propKey = String(prop);
|
|
178
|
-
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
|
|
169
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
|
|
179
170
|
let newPath = getPathConcat(pathKey);
|
|
180
171
|
if (newPath === undefined) {
|
|
181
172
|
newPath = path.concat(propKey);
|
|
182
173
|
setPathConcat(pathKey, newPath);
|
|
183
174
|
}
|
|
184
|
-
//
|
|
175
|
+
// recursively wrap nested structures
|
|
185
176
|
if (Array.isArray(value))
|
|
186
177
|
return wrapArray(value, emit, newPath);
|
|
187
178
|
if (value instanceof Map)
|
|
@@ -189,12 +180,10 @@ export function wrapArray(arr, emit, path) {
|
|
|
189
180
|
if (value instanceof Set)
|
|
190
181
|
return wrapSet(value, emit, newPath);
|
|
191
182
|
if (value instanceof Date)
|
|
192
|
-
return new Date(value.getTime()); //
|
|
193
|
-
// Default to reactive for plain objects
|
|
183
|
+
return new Date(value.getTime()); // dates are not proxied, return a copy
|
|
194
184
|
return reactive(value, emit, newPath);
|
|
195
185
|
}
|
|
196
|
-
//
|
|
197
|
-
// Also handle functions bound to the target
|
|
186
|
+
// ensure functions accessed directly are bound to the original target
|
|
198
187
|
if (typeof value === 'function') {
|
|
199
188
|
return value.bind(target);
|
|
200
189
|
}
|
|
@@ -202,18 +191,19 @@ export function wrapArray(arr, emit, path) {
|
|
|
202
191
|
},
|
|
203
192
|
set(target, prop, value, receiver) {
|
|
204
193
|
const oldValue = target[prop];
|
|
205
|
-
//
|
|
194
|
+
// avoid unnecessary triggers if value hasn't changed
|
|
206
195
|
if (oldValue === value)
|
|
207
196
|
return true;
|
|
208
|
-
// Deep equality check with new WeakMap
|
|
209
197
|
if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value, new WeakMap()))
|
|
210
198
|
return true;
|
|
211
199
|
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
212
200
|
const result = Reflect.set(target, prop, value, receiver);
|
|
213
201
|
const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(String(prop))));
|
|
202
|
+
// emit event and trigger effects only if the set was successful and wasn't intercepted by a setter
|
|
203
|
+
// (unless it's a direct numeric index set, which doesn't have a descriptor.set)
|
|
214
204
|
if (result && (!descriptor || !descriptor.set || isNumericIndex)) {
|
|
215
205
|
const propKey = String(prop);
|
|
216
|
-
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
|
|
206
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
|
|
217
207
|
let newPath = getPathConcat(pathKey);
|
|
218
208
|
if (newPath === undefined) {
|
|
219
209
|
newPath = path.concat(propKey);
|
|
@@ -231,7 +221,7 @@ export function wrapArray(arr, emit, path) {
|
|
|
231
221
|
return result;
|
|
232
222
|
}
|
|
233
223
|
});
|
|
234
|
-
//
|
|
224
|
+
// cache the newly created proxy before returning
|
|
235
225
|
wrapperCache.set(arr, proxy);
|
|
236
226
|
return proxy;
|
|
237
227
|
}
|