@yiin/reactive-proxy-state 1.0.12 → 1.0.14
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/dist/computed.d.ts +6 -0
- package/dist/constants.d.ts +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1474 -11
- package/dist/reactive.d.ts +14 -2
- package/dist/ref.d.ts +24 -7
- package/dist/state.d.ts +6 -0
- package/dist/types.d.ts +1 -1
- package/dist/watch-effect.d.ts +2 -1
- package/dist/wrap-array.d.ts +2 -2
- package/dist/wrap-map.d.ts +2 -2
- package/dist/wrap-set.d.ts +2 -2
- package/package.json +2 -2
- package/dist/computed.js +0 -58
- package/dist/reactive.js +0 -114
- package/dist/ref.js +0 -91
- package/dist/state.js +0 -211
- package/dist/types.js +0 -1
- package/dist/utils.js +0 -220
- package/dist/watch-effect.js +0 -154
- package/dist/watch.js +0 -44
- package/dist/watchEffect.d.ts +0 -54
- package/dist/watchEffect.js +0 -154
- package/dist/wrap-array.js +0 -237
- package/dist/wrap-map.js +0 -252
- package/dist/wrap-set.js +0 -182
- package/dist/wrapArray.d.ts +0 -2
- package/dist/wrapArray.js +0 -227
- package/dist/wrapMap.d.ts +0 -2
- package/dist/wrapMap.js +0 -230
- package/dist/wrapSet.d.ts +0 -2
- package/dist/wrapSet.js +0 -167
package/dist/utils.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// cache for memoized deepEqual results, using weakmap to avoid memory leaks
|
|
2
|
-
const deepEqualCache = new WeakMap();
|
|
3
|
-
const MAX_CACHE_SIZE = 1000;
|
|
4
|
-
// path traversal cache (object -> pathString -> value) with lru-like behavior
|
|
5
|
-
// weakmap for the root object ensures the cache entry is garbage collected when the object is
|
|
6
|
-
export const pathCache = new WeakMap();
|
|
7
|
-
const pathCacheSize = new WeakMap(); // track individual map sizes
|
|
8
|
-
// path concatenation cache ('a.b.c' -> ['a', 'b', 'c']) with size limit
|
|
9
|
-
export const pathConcatCache = new Map();
|
|
10
|
-
const MAX_PATH_CACHE_SIZE = 1000;
|
|
11
|
-
// global weakmap for circular reference detection during deep operations like clone or equal
|
|
12
|
-
export const globalSeen = new WeakMap();
|
|
13
|
-
// global cache to reuse proxy wrappers for the same original object
|
|
14
|
-
export const wrapperCache = new WeakMap();
|
|
15
|
-
// simple lru-like cleanup for individual object path caches
|
|
16
|
-
function cleanupPathCache(root) {
|
|
17
|
-
const cache = pathCache.get(root);
|
|
18
|
-
if (cache && pathCacheSize.get(root) > MAX_CACHE_SIZE) {
|
|
19
|
-
// clear oldest entries (first 20%)
|
|
20
|
-
const entriesToRemove = Math.floor(MAX_CACHE_SIZE * 0.2);
|
|
21
|
-
let count = 0;
|
|
22
|
-
for (const key of cache.keys()) {
|
|
23
|
-
if (count >= entriesToRemove)
|
|
24
|
-
break;
|
|
25
|
-
cache.delete(key);
|
|
26
|
-
count++;
|
|
27
|
-
}
|
|
28
|
-
pathCacheSize.set(root, MAX_CACHE_SIZE - entriesToRemove);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// simple lru-like cleanup for the global path concatenation cache
|
|
32
|
-
function cleanupPathConcatCache() {
|
|
33
|
-
if (pathConcatCache.size > MAX_PATH_CACHE_SIZE) {
|
|
34
|
-
// remove oldest entries (first 20%)
|
|
35
|
-
const entriesToRemove = Math.floor(MAX_PATH_CACHE_SIZE * 0.2);
|
|
36
|
-
let count = 0;
|
|
37
|
-
for (const key of pathConcatCache.keys()) {
|
|
38
|
-
if (count >= entriesToRemove)
|
|
39
|
-
break;
|
|
40
|
-
pathConcatCache.delete(key);
|
|
41
|
-
count++;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
export function deepEqual(a, b, seen = globalSeen) {
|
|
46
|
-
if (a === b)
|
|
47
|
-
return true;
|
|
48
|
-
if (a == null || b == null)
|
|
49
|
-
return a === b;
|
|
50
|
-
if (typeof a !== typeof b)
|
|
51
|
-
return false;
|
|
52
|
-
if (a instanceof Date && b instanceof Date)
|
|
53
|
-
return a.getTime() === b.getTime();
|
|
54
|
-
if (typeof a !== 'object')
|
|
55
|
-
return false;
|
|
56
|
-
if (Array.isArray(a) !== Array.isArray(b))
|
|
57
|
-
return false;
|
|
58
|
-
// handle circular references
|
|
59
|
-
if (seen.has(a))
|
|
60
|
-
return seen.get(a) === b;
|
|
61
|
-
seen.set(a, b);
|
|
62
|
-
// check memoization cache before diving deeper
|
|
63
|
-
if (deepEqualCache.has(a) && deepEqualCache.get(a)?.has(b)) {
|
|
64
|
-
return deepEqualCache.get(a).get(b);
|
|
65
|
-
}
|
|
66
|
-
if (!deepEqualCache.has(a)) {
|
|
67
|
-
deepEqualCache.set(a, new WeakMap());
|
|
68
|
-
}
|
|
69
|
-
let result;
|
|
70
|
-
if (Array.isArray(a)) {
|
|
71
|
-
result = a.length === b.length && a.every((val, idx) => deepEqual(val, b[idx], seen));
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
const keysA = Object.keys(a);
|
|
75
|
-
const keysB = Object.keys(b);
|
|
76
|
-
result = keysA.length === keysB.length && keysA.every(key => Object.prototype.hasOwnProperty.call(b, key) && deepEqual(a[key], b[key], seen));
|
|
77
|
-
}
|
|
78
|
-
// cache the result before returning
|
|
79
|
-
deepEqualCache.get(a).set(b, result);
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
export function getFromPathCache(root, pathKey) {
|
|
83
|
-
const cache = pathCache.get(root);
|
|
84
|
-
if (!cache)
|
|
85
|
-
return undefined;
|
|
86
|
-
const result = cache.get(pathKey);
|
|
87
|
-
if (result !== undefined) {
|
|
88
|
-
// simulate lru by deleting and re-setting the key
|
|
89
|
-
cache.delete(pathKey);
|
|
90
|
-
cache.set(pathKey, result);
|
|
91
|
-
}
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
export function setInPathCache(root, pathKey, value) {
|
|
95
|
-
if (!pathCache.has(root)) {
|
|
96
|
-
pathCache.set(root, new Map());
|
|
97
|
-
pathCacheSize.set(root, 0);
|
|
98
|
-
}
|
|
99
|
-
const cache = pathCache.get(root);
|
|
100
|
-
// adjust size only if the key is new
|
|
101
|
-
if (!cache.has(pathKey)) {
|
|
102
|
-
pathCacheSize.set(root, pathCacheSize.get(root) + 1);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
cache.delete(pathKey); // remove old entry before adding to end
|
|
106
|
-
}
|
|
107
|
-
cache.set(pathKey, value);
|
|
108
|
-
cleanupPathCache(root); // check if cleanup is needed after adding
|
|
109
|
-
}
|
|
110
|
-
export function getPathConcat(path) {
|
|
111
|
-
const result = pathConcatCache.get(path);
|
|
112
|
-
if (result !== undefined) {
|
|
113
|
-
// simulate lru
|
|
114
|
-
pathConcatCache.delete(path);
|
|
115
|
-
pathConcatCache.set(path, result);
|
|
116
|
-
}
|
|
117
|
-
return result;
|
|
118
|
-
}
|
|
119
|
-
export function setPathConcat(path, value) {
|
|
120
|
-
// delete first to ensure it's added at the end (most recent)
|
|
121
|
-
if (pathConcatCache.has(path)) {
|
|
122
|
-
pathConcatCache.delete(path);
|
|
123
|
-
}
|
|
124
|
-
pathConcatCache.set(path, value);
|
|
125
|
-
cleanupPathConcatCache(); // check size limit
|
|
126
|
-
}
|
|
127
|
-
function isObject(val) {
|
|
128
|
-
return val !== null && typeof val === 'object';
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* recursively traverses an object or array, accessing each property/element.
|
|
132
|
-
* used by `watch` with `deep: true` to establish dependencies on all nested properties.
|
|
133
|
-
* the actual tracking is done by the proxy `get` handlers triggered during traversal.
|
|
134
|
-
*/
|
|
135
|
-
export function traverse(value, seen = new Set()) {
|
|
136
|
-
if (!isObject(value) || seen.has(value)) {
|
|
137
|
-
return value;
|
|
138
|
-
}
|
|
139
|
-
seen.add(value);
|
|
140
|
-
if (Array.isArray(value)) {
|
|
141
|
-
value.length; // track length
|
|
142
|
-
for (let i = 0; i < value.length; i++) {
|
|
143
|
-
traverse(value[i], seen);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else if (value instanceof Set || value instanceof Map) {
|
|
147
|
-
for (const v of value) {
|
|
148
|
-
if (Array.isArray(v)) { // map entries [key, value]
|
|
149
|
-
traverse(v[0], seen); // key
|
|
150
|
-
traverse(v[1], seen); // value
|
|
151
|
-
}
|
|
152
|
-
else { // set values
|
|
153
|
-
traverse(v, seen);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return value; // no need to iterate plain object keys for map/set
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
for (const key in value) {
|
|
160
|
-
traverse(value[key], seen);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return value;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* creates a deep clone of a value.
|
|
167
|
-
* includes cycle detection using a weakmap.
|
|
168
|
-
*/
|
|
169
|
-
export function deepClone(value, seen = new WeakMap()) {
|
|
170
|
-
if (value === null || typeof value !== 'object') {
|
|
171
|
-
return value;
|
|
172
|
-
}
|
|
173
|
-
if (value instanceof Date) {
|
|
174
|
-
return new Date(value.getTime());
|
|
175
|
-
}
|
|
176
|
-
if (seen.has(value)) {
|
|
177
|
-
return seen.get(value);
|
|
178
|
-
}
|
|
179
|
-
if (Array.isArray(value)) {
|
|
180
|
-
const newArray = [];
|
|
181
|
-
seen.set(value, newArray); // store ref before recursing
|
|
182
|
-
for (let i = 0; i < value.length; i++) {
|
|
183
|
-
newArray[i] = deepClone(value[i], seen);
|
|
184
|
-
}
|
|
185
|
-
return newArray;
|
|
186
|
-
}
|
|
187
|
-
if (value instanceof Map) {
|
|
188
|
-
const newMap = new Map();
|
|
189
|
-
seen.set(value, newMap); // store ref before recursing
|
|
190
|
-
value.forEach((val, key) => {
|
|
191
|
-
newMap.set(deepClone(key, seen), deepClone(val, seen));
|
|
192
|
-
});
|
|
193
|
-
return newMap;
|
|
194
|
-
}
|
|
195
|
-
if (value instanceof Set) {
|
|
196
|
-
const newSet = new Set();
|
|
197
|
-
seen.set(value, newSet); // store ref before recursing
|
|
198
|
-
value.forEach(val => {
|
|
199
|
-
newSet.add(deepClone(val, seen));
|
|
200
|
-
});
|
|
201
|
-
return newSet;
|
|
202
|
-
}
|
|
203
|
-
// handle plain objects
|
|
204
|
-
const newObject = Object.create(Object.getPrototypeOf(value));
|
|
205
|
-
seen.set(value, newObject); // store ref before recursing
|
|
206
|
-
for (const key in value) {
|
|
207
|
-
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
208
|
-
newObject[key] = deepClone(value[key], seen);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// copy symbol properties
|
|
212
|
-
const symbolKeys = Object.getOwnPropertySymbols(value);
|
|
213
|
-
for (const symbolKey of symbolKeys) {
|
|
214
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, symbolKey);
|
|
215
|
-
if (descriptor && Object.prototype.propertyIsEnumerable.call(value, symbolKey)) {
|
|
216
|
-
newObject[symbolKey] = deepClone(value[symbolKey], seen);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return newObject;
|
|
220
|
-
}
|
package/dist/watch-effect.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// tracks the currently executing effect to establish dependencies
|
|
2
|
-
export let activeEffect = null;
|
|
3
|
-
// allows setting the active effect, used internally by the effect runner
|
|
4
|
-
export function setActiveEffect(effect) {
|
|
5
|
-
activeEffect = effect;
|
|
6
|
-
}
|
|
7
|
-
// storage for dependencies: target object -> property key -> set of effects that depend on this key
|
|
8
|
-
const targetMap = new WeakMap();
|
|
9
|
-
/**
|
|
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.
|
|
12
|
-
*/
|
|
13
|
-
export function cleanupEffect(effect) {
|
|
14
|
-
if (effect.dependencies) {
|
|
15
|
-
effect.dependencies.forEach(dep => {
|
|
16
|
-
// remove this effect from the dependency set associated with a specific target/key
|
|
17
|
-
dep.delete(effect);
|
|
18
|
-
});
|
|
19
|
-
// clear the effect's own list of dependencies for the next run
|
|
20
|
-
effect.dependencies.clear();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* establishes a dependency between the currently active effect and a specific object property.
|
|
25
|
-
* called by proxy getters or ref getters.
|
|
26
|
-
*/
|
|
27
|
-
export function track(target, key) {
|
|
28
|
-
// do nothing if there is no active effect or if the effect is stopped
|
|
29
|
-
if (!activeEffect || !activeEffect.active)
|
|
30
|
-
return;
|
|
31
|
-
// get or create the dependency map for the target object
|
|
32
|
-
let depsMap = targetMap.get(target);
|
|
33
|
-
if (!depsMap) {
|
|
34
|
-
depsMap = new Map();
|
|
35
|
-
targetMap.set(target, depsMap);
|
|
36
|
-
}
|
|
37
|
-
// get or create the set of effects for the specific property key
|
|
38
|
-
let dep = depsMap.get(key);
|
|
39
|
-
if (!dep) {
|
|
40
|
-
dep = new Set();
|
|
41
|
-
depsMap.set(key, dep);
|
|
42
|
-
}
|
|
43
|
-
// add the current effect to the dependency set if it's not already there
|
|
44
|
-
const effectToAdd = activeEffect;
|
|
45
|
-
if (!dep.has(effectToAdd)) {
|
|
46
|
-
dep.add(effectToAdd);
|
|
47
|
-
// also add this dependency set to the effect's own tracking list for cleanup purposes
|
|
48
|
-
if (!effectToAdd.dependencies) {
|
|
49
|
-
effectToAdd.dependencies = new Set();
|
|
50
|
-
}
|
|
51
|
-
effectToAdd.dependencies.add(dep);
|
|
52
|
-
// trigger the onTrack debug hook if provided
|
|
53
|
-
if (effectToAdd.options?.onTrack) {
|
|
54
|
-
// pass the original user callback to the hook, not the internal wrapper
|
|
55
|
-
effectToAdd.options.onTrack({ effect: effectToAdd._rawCallback, target, key, type: 'track' });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
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.
|
|
63
|
-
*/
|
|
64
|
-
export function trigger(target, key) {
|
|
65
|
-
const depsMap = targetMap.get(target);
|
|
66
|
-
if (!depsMap)
|
|
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
|
|
69
|
-
const effectsToRun = new Set();
|
|
70
|
-
// helper to add effects from a specific dependency set to the run queue
|
|
71
|
-
const addEffects = (depKey) => {
|
|
72
|
-
const dep = depsMap.get(depKey);
|
|
73
|
-
if (dep) {
|
|
74
|
-
dep.forEach(effect => {
|
|
75
|
-
// avoid triggering the effect if it's the one currently running (prevents infinite loops)
|
|
76
|
-
// also ensure the effect hasn't been stopped
|
|
77
|
-
if (effect !== activeEffect && effect.active) {
|
|
78
|
-
effectsToRun.add(effect);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
// add effects associated with the specific key that changed
|
|
84
|
-
addEffects(key);
|
|
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
|
|
87
|
-
effectsToRun.forEach(effect => {
|
|
88
|
-
// trigger the onTrigger debug hook if provided
|
|
89
|
-
if (effect.options?.onTrigger) {
|
|
90
|
-
effect.options.onTrigger({ effect: effect._rawCallback, target, key, type: 'trigger' });
|
|
91
|
-
}
|
|
92
|
-
// use a custom scheduler if provided, otherwise run the effect synchronously
|
|
93
|
-
if (effect.options?.scheduler) {
|
|
94
|
-
effect.options.scheduler(effect.run);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
effect.run(); // execute the effect's wrapper function (`run`)
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
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.
|
|
105
|
-
*/
|
|
106
|
-
export function watchEffect(effectCallback, options = {}) {
|
|
107
|
-
// the wrapper function that manages the effect lifecycle (cleanup, tracking, execution)
|
|
108
|
-
const run = () => {
|
|
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
|
-
}
|
|
121
|
-
const previousEffect = activeEffect;
|
|
122
|
-
try {
|
|
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
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
setActiveEffect(previousEffect); // restore the previous active effect
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
// create the internal effect object
|
|
132
|
-
const effectFn = {
|
|
133
|
-
run: run,
|
|
134
|
-
dependencies: new Set(), // initialize empty dependencies
|
|
135
|
-
options: options,
|
|
136
|
-
active: true, // start as active
|
|
137
|
-
_rawCallback: effectCallback // store the original callback
|
|
138
|
-
};
|
|
139
|
-
// run the effect immediately unless the `lazy` option is true
|
|
140
|
-
if (!options.lazy) {
|
|
141
|
-
effectFn.run();
|
|
142
|
-
}
|
|
143
|
-
// create the function that stops the effect
|
|
144
|
-
const stopHandle = () => {
|
|
145
|
-
if (effectFn.active) {
|
|
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.
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
// attach the effect instance to the stop handle for potential advanced usage
|
|
152
|
-
stopHandle.effect = effectFn;
|
|
153
|
-
return stopHandle;
|
|
154
|
-
}
|
package/dist/watch.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { watchEffect } from './watch-effect';
|
|
2
|
-
import { traverse, deepClone } from './utils';
|
|
3
|
-
/**
|
|
4
|
-
* watches a reactive source (getter function or reactive object/ref)
|
|
5
|
-
* and runs a callback when the source's value changes.
|
|
6
|
-
*/
|
|
7
|
-
export function watch(sourceInput, callback, options = {}) {
|
|
8
|
-
const { immediate = false, deep = true } = options;
|
|
9
|
-
// normalize source to always be a getter function
|
|
10
|
-
const source = typeof sourceInput === 'function'
|
|
11
|
-
? sourceInput
|
|
12
|
-
: () => sourceInput;
|
|
13
|
-
let oldValue;
|
|
14
|
-
let initialized = false;
|
|
15
|
-
// use watchEffect internally to handle dependency tracking
|
|
16
|
-
const stopEffect = watchEffect(() => {
|
|
17
|
-
const currentValue = source();
|
|
18
|
-
// if deep watching, traverse the current value to track nested dependencies
|
|
19
|
-
if (deep) {
|
|
20
|
-
traverse(currentValue);
|
|
21
|
-
}
|
|
22
|
-
if (initialized) {
|
|
23
|
-
let hasChanged = false;
|
|
24
|
-
// for deep watches, the effect running implies a dependency changed.
|
|
25
|
-
// for shallow, explicitly check reference equality.
|
|
26
|
-
hasChanged = deep || currentValue !== oldValue;
|
|
27
|
-
if (hasChanged) {
|
|
28
|
-
const prevOldValue = oldValue;
|
|
29
|
-
// store a clone for deep watches to pass as the correct oldValue next time
|
|
30
|
-
oldValue = deep ? deepClone(currentValue) : currentValue;
|
|
31
|
-
callback(currentValue, prevOldValue);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
// first run: store initial value (cloned if deep) and run immediate callback if requested
|
|
36
|
-
oldValue = deep ? deepClone(currentValue) : currentValue;
|
|
37
|
-
initialized = true;
|
|
38
|
-
if (immediate) {
|
|
39
|
-
callback(currentValue, undefined); // pass undefined as oldValue for immediate
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}, { lazy: false }); // run immediately (watchEffect handles `immediate` option internally)
|
|
43
|
-
return stopEffect;
|
|
44
|
-
}
|
package/dist/watchEffect.d.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
type EffectCallback<T = any> = () => T;
|
|
2
|
-
type Scheduler = (job: () => void) => void;
|
|
3
|
-
export interface WatchEffectStopHandle<T = any> {
|
|
4
|
-
(): void;
|
|
5
|
-
effect: TrackedEffect<T>;
|
|
6
|
-
}
|
|
7
|
-
export interface TrackedEffect<T = any> {
|
|
8
|
-
run: () => T;
|
|
9
|
-
dependencies?: Set<Set<TrackedEffect<any>>>;
|
|
10
|
-
options?: WatchEffectOptions;
|
|
11
|
-
active?: boolean;
|
|
12
|
-
_rawCallback: EffectCallback<T>;
|
|
13
|
-
}
|
|
14
|
-
export declare let activeEffect: TrackedEffect<any> | null;
|
|
15
|
-
export declare function setActiveEffect(effect: TrackedEffect<any> | null): void;
|
|
16
|
-
/**
|
|
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.
|
|
19
|
-
*/
|
|
20
|
-
export declare function cleanupEffect(effect: TrackedEffect<any>): void;
|
|
21
|
-
/**
|
|
22
|
-
* establishes a dependency between the currently active effect and a specific object property.
|
|
23
|
-
* called by proxy getters or ref getters.
|
|
24
|
-
*/
|
|
25
|
-
export declare function track(target: object, key: string | symbol): void;
|
|
26
|
-
/**
|
|
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.
|
|
30
|
-
*/
|
|
31
|
-
export declare function trigger(target: object, key: string | symbol): void;
|
|
32
|
-
export interface WatchEffectOptions {
|
|
33
|
-
lazy?: boolean;
|
|
34
|
-
scheduler?: Scheduler;
|
|
35
|
-
onTrack?: (event: {
|
|
36
|
-
effect: EffectCallback<any>;
|
|
37
|
-
target: object;
|
|
38
|
-
key: string | symbol;
|
|
39
|
-
type: 'track';
|
|
40
|
-
}) => void;
|
|
41
|
-
onTrigger?: (event: {
|
|
42
|
-
effect: EffectCallback<any>;
|
|
43
|
-
target: object;
|
|
44
|
-
key: string | symbol;
|
|
45
|
-
type: 'trigger';
|
|
46
|
-
}) => void;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
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.
|
|
52
|
-
*/
|
|
53
|
-
export declare function watchEffect<T>(effectCallback: EffectCallback<T>, options?: WatchEffectOptions): WatchEffectStopHandle<T>;
|
|
54
|
-
export {};
|
package/dist/watchEffect.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// tracks the currently executing effect to establish dependencies
|
|
2
|
-
export let activeEffect = null;
|
|
3
|
-
// allows setting the active effect, used internally by the effect runner
|
|
4
|
-
export function setActiveEffect(effect) {
|
|
5
|
-
activeEffect = effect;
|
|
6
|
-
}
|
|
7
|
-
// storage for dependencies: target object -> property key -> set of effects that depend on this key
|
|
8
|
-
const targetMap = new WeakMap();
|
|
9
|
-
/**
|
|
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.
|
|
12
|
-
*/
|
|
13
|
-
export function cleanupEffect(effect) {
|
|
14
|
-
if (effect.dependencies) {
|
|
15
|
-
effect.dependencies.forEach(dep => {
|
|
16
|
-
// remove this effect from the dependency set associated with a specific target/key
|
|
17
|
-
dep.delete(effect);
|
|
18
|
-
});
|
|
19
|
-
// clear the effect's own list of dependencies for the next run
|
|
20
|
-
effect.dependencies.clear();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* establishes a dependency between the currently active effect and a specific object property.
|
|
25
|
-
* called by proxy getters or ref getters.
|
|
26
|
-
*/
|
|
27
|
-
export function track(target, key) {
|
|
28
|
-
// do nothing if there is no active effect or if the effect is stopped
|
|
29
|
-
if (!activeEffect || !activeEffect.active)
|
|
30
|
-
return;
|
|
31
|
-
// get or create the dependency map for the target object
|
|
32
|
-
let depsMap = targetMap.get(target);
|
|
33
|
-
if (!depsMap) {
|
|
34
|
-
depsMap = new Map();
|
|
35
|
-
targetMap.set(target, depsMap);
|
|
36
|
-
}
|
|
37
|
-
// get or create the set of effects for the specific property key
|
|
38
|
-
let dep = depsMap.get(key);
|
|
39
|
-
if (!dep) {
|
|
40
|
-
dep = new Set();
|
|
41
|
-
depsMap.set(key, dep);
|
|
42
|
-
}
|
|
43
|
-
// add the current effect to the dependency set if it's not already there
|
|
44
|
-
const effectToAdd = activeEffect;
|
|
45
|
-
if (!dep.has(effectToAdd)) {
|
|
46
|
-
dep.add(effectToAdd);
|
|
47
|
-
// also add this dependency set to the effect's own tracking list for cleanup purposes
|
|
48
|
-
if (!effectToAdd.dependencies) {
|
|
49
|
-
effectToAdd.dependencies = new Set();
|
|
50
|
-
}
|
|
51
|
-
effectToAdd.dependencies.add(dep);
|
|
52
|
-
// trigger the onTrack debug hook if provided
|
|
53
|
-
if (effectToAdd.options?.onTrack) {
|
|
54
|
-
// pass the original user callback to the hook, not the internal wrapper
|
|
55
|
-
effectToAdd.options.onTrack({ effect: effectToAdd._rawCallback, target, key, type: 'track' });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
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.
|
|
63
|
-
*/
|
|
64
|
-
export function trigger(target, key) {
|
|
65
|
-
const depsMap = targetMap.get(target);
|
|
66
|
-
if (!depsMap)
|
|
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
|
|
69
|
-
const effectsToRun = new Set();
|
|
70
|
-
// helper to add effects from a specific dependency set to the run queue
|
|
71
|
-
const addEffects = (depKey) => {
|
|
72
|
-
const dep = depsMap.get(depKey);
|
|
73
|
-
if (dep) {
|
|
74
|
-
dep.forEach(effect => {
|
|
75
|
-
// avoid triggering the effect if it's the one currently running (prevents infinite loops)
|
|
76
|
-
// also ensure the effect hasn't been stopped
|
|
77
|
-
if (effect !== activeEffect && effect.active) {
|
|
78
|
-
effectsToRun.add(effect);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
// add effects associated with the specific key that changed
|
|
84
|
-
addEffects(key);
|
|
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
|
|
87
|
-
effectsToRun.forEach(effect => {
|
|
88
|
-
// trigger the onTrigger debug hook if provided
|
|
89
|
-
if (effect.options?.onTrigger) {
|
|
90
|
-
effect.options.onTrigger({ effect: effect._rawCallback, target, key, type: 'trigger' });
|
|
91
|
-
}
|
|
92
|
-
// use a custom scheduler if provided, otherwise run the effect synchronously
|
|
93
|
-
if (effect.options?.scheduler) {
|
|
94
|
-
effect.options.scheduler(effect.run);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
effect.run(); // execute the effect's wrapper function (`run`)
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
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.
|
|
105
|
-
*/
|
|
106
|
-
export function watchEffect(effectCallback, options = {}) {
|
|
107
|
-
// the wrapper function that manages the effect lifecycle (cleanup, tracking, execution)
|
|
108
|
-
const run = () => {
|
|
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
|
-
}
|
|
121
|
-
const previousEffect = activeEffect;
|
|
122
|
-
try {
|
|
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
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
setActiveEffect(previousEffect); // restore the previous active effect
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
// create the internal effect object
|
|
132
|
-
const effectFn = {
|
|
133
|
-
run: run,
|
|
134
|
-
dependencies: new Set(), // initialize empty dependencies
|
|
135
|
-
options: options,
|
|
136
|
-
active: true, // start as active
|
|
137
|
-
_rawCallback: effectCallback // store the original callback
|
|
138
|
-
};
|
|
139
|
-
// run the effect immediately unless the `lazy` option is true
|
|
140
|
-
if (!options.lazy) {
|
|
141
|
-
effectFn.run();
|
|
142
|
-
}
|
|
143
|
-
// create the function that stops the effect
|
|
144
|
-
const stopHandle = () => {
|
|
145
|
-
if (effectFn.active) {
|
|
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.
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
// attach the effect instance to the stop handle for potential advanced usage
|
|
152
|
-
stopHandle.effect = effectFn;
|
|
153
|
-
return stopHandle;
|
|
154
|
-
}
|