@yiin/reactive-proxy-state 1.0.4 → 1.0.5
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 +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 +1 -1
package/dist/computed.d.ts
CHANGED
|
@@ -6,11 +6,16 @@ export interface ComputedRef<T = any> extends Omit<Ref<T>, 'value'> {
|
|
|
6
6
|
readonly [isRefSymbol]: true;
|
|
7
7
|
}
|
|
8
8
|
export interface WritableComputedRef<T> extends Ref<T> {
|
|
9
|
+
readonly [isComputedSymbol]: true;
|
|
10
|
+
readonly [isRefSymbol]: true;
|
|
9
11
|
}
|
|
10
12
|
type ComputedGetter<T> = () => T;
|
|
13
|
+
type ComputedSetter<T> = (v: T) => void;
|
|
14
|
+
interface WritableComputedOptions<T> {
|
|
15
|
+
get: ComputedGetter<T>;
|
|
16
|
+
set: ComputedSetter<T>;
|
|
17
|
+
}
|
|
11
18
|
export declare function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export declare function isComputed<T>(c: any): c is ComputedRef<T>;
|
|
19
|
+
export declare function computed<T>(options: WritableComputedOptions<T>): WritableComputedRef<T>;
|
|
20
|
+
export declare function isComputed<T>(c: any): c is ComputedRef<T> | WritableComputedRef<T>;
|
|
16
21
|
export {};
|
package/dist/computed.js
CHANGED
|
@@ -1,59 +1,58 @@
|
|
|
1
1
|
import { watchEffect, track, trigger } from './watchEffect';
|
|
2
2
|
import { isRefSymbol } from './ref';
|
|
3
|
-
//
|
|
3
|
+
// symbol for identifying computed refs
|
|
4
4
|
const isComputedSymbol = Symbol('isComputed');
|
|
5
|
-
//
|
|
6
|
-
export function computed(
|
|
5
|
+
// implementation using a lazy watchEffect with a custom scheduler for caching
|
|
6
|
+
export function computed(getterOrOptions) {
|
|
7
|
+
let getter;
|
|
8
|
+
let setter;
|
|
9
|
+
const isGetter = typeof getterOrOptions === 'function';
|
|
10
|
+
if (isGetter) {
|
|
11
|
+
getter = getterOrOptions;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
getter = getterOrOptions.get;
|
|
15
|
+
setter = getterOrOptions.set;
|
|
16
|
+
}
|
|
7
17
|
let _value;
|
|
8
|
-
let _dirty = true; //
|
|
9
|
-
let computedRef; //
|
|
10
|
-
//
|
|
18
|
+
let _dirty = true; // flag to track if the cached value is stale
|
|
19
|
+
let computedRef; // placeholder to allow self-reference in scheduler
|
|
20
|
+
// create a lazy effect; scheduler intercepts triggers to mark dirty instead of recomputing immediately
|
|
11
21
|
const stopHandle = watchEffect(getter, {
|
|
12
|
-
lazy: true,
|
|
22
|
+
lazy: true,
|
|
13
23
|
scheduler: () => {
|
|
14
|
-
// When a dependency changes, don't re-run the getter immediately.
|
|
15
|
-
// Instead, mark the computed as dirty and trigger downstream effects.
|
|
16
24
|
if (!_dirty) {
|
|
17
25
|
_dirty = true;
|
|
18
|
-
//
|
|
26
|
+
// trigger effects that depend on this computed ref
|
|
19
27
|
trigger(computedRef, 'value');
|
|
20
28
|
}
|
|
21
29
|
},
|
|
22
30
|
});
|
|
23
|
-
// Access the internal effect runner
|
|
24
31
|
const effectRunner = stopHandle.effect;
|
|
25
32
|
computedRef = {
|
|
26
33
|
[isRefSymbol]: true,
|
|
27
34
|
[isComputedSymbol]: true,
|
|
28
35
|
get value() {
|
|
29
|
-
// 1. Track access to this computed value for any outer effects
|
|
30
36
|
track(computedRef, 'value');
|
|
31
|
-
//
|
|
32
|
-
// - Execute the getter
|
|
33
|
-
// - Update _value (via getter's return)
|
|
34
|
-
// - Track dependencies for the getter (handled by watchEffect internals)
|
|
35
|
-
// - Set _dirty to false
|
|
37
|
+
// if dirty, recompute value by running the getter
|
|
36
38
|
if (_dirty) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
_dirty = false; // Mark as clean *after* successful run
|
|
39
|
+
_value = effectRunner.run();
|
|
40
|
+
_dirty = false; // mark as clean after successful run
|
|
40
41
|
}
|
|
41
|
-
// 3. Return the (now current) value.
|
|
42
42
|
return _value;
|
|
43
43
|
},
|
|
44
44
|
set value(newValue) {
|
|
45
|
-
|
|
45
|
+
if (setter) {
|
|
46
|
+
setter(newValue);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.warn('computed value is read-only');
|
|
50
|
+
}
|
|
46
51
|
},
|
|
47
|
-
//
|
|
48
|
-
// stop: stopHandle
|
|
52
|
+
// stop: stopHandle // potentially expose stop handle
|
|
49
53
|
};
|
|
50
|
-
// Initial computation is lazy, happens on first .value access.
|
|
51
|
-
// The scheduler ensures dependency changes only mark it dirty.
|
|
52
54
|
return computedRef;
|
|
53
55
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Checks if a value is a computed ref.
|
|
56
|
-
*/
|
|
57
56
|
export function isComputed(c) {
|
|
58
57
|
return !!(c && c[isComputedSymbol]);
|
|
59
58
|
}
|
package/dist/reactive.js
CHANGED
|
@@ -3,18 +3,22 @@ import { wrapArray } from './wrapArray';
|
|
|
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
|
+
// create a reactive proxy for an object
|
|
10
11
|
export function reactive(obj, emit, path = [], seen = globalSeen) {
|
|
12
|
+
// prevent infinite recursion with circular references
|
|
11
13
|
if (seen.has(obj))
|
|
12
14
|
return seen.get(obj);
|
|
15
|
+
// helper to wrap nested values recursively
|
|
13
16
|
function wrapValue(val, subPath) {
|
|
14
17
|
if (!isObject(val))
|
|
15
|
-
return val;
|
|
18
|
+
return val; // primitives are returned directly
|
|
16
19
|
if (seen.has(val))
|
|
17
|
-
return seen.get(val);
|
|
20
|
+
return seen.get(val); // handle cycles within nested structures
|
|
21
|
+
// delegate wrapping to specific functions based on type
|
|
18
22
|
if (Array.isArray(val))
|
|
19
23
|
return wrapArray(val, emit, subPath);
|
|
20
24
|
if (val instanceof Map)
|
|
@@ -22,43 +26,48 @@ export function reactive(obj, emit, path = [], seen = globalSeen) {
|
|
|
22
26
|
if (val instanceof Set)
|
|
23
27
|
return wrapSet(val, emit, subPath);
|
|
24
28
|
if (val instanceof Date)
|
|
25
|
-
return new Date(val.getTime());
|
|
29
|
+
return new Date(val.getTime()); // dates are not proxied, return copy
|
|
30
|
+
// default to reactive for plain objects
|
|
26
31
|
return reactive(val, emit, subPath, seen);
|
|
27
32
|
}
|
|
28
33
|
const proxy = new Proxy(obj, {
|
|
29
34
|
get(target, prop, receiver) {
|
|
30
35
|
const value = Reflect.get(target, prop, receiver);
|
|
31
|
-
//
|
|
36
|
+
// track property access for dependency tracking
|
|
32
37
|
track(target, prop);
|
|
33
|
-
//
|
|
38
|
+
// return non-objects directly without wrapping
|
|
34
39
|
if (!isObject(value))
|
|
35
40
|
return value;
|
|
36
|
-
//
|
|
37
|
-
const
|
|
41
|
+
// calculate the path for the nested property, using cache for performance
|
|
42
|
+
const propKey = String(prop);
|
|
43
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
|
|
38
44
|
let newPath = getPathConcat(pathKey);
|
|
39
45
|
if (newPath === undefined) {
|
|
40
|
-
newPath = path.concat(
|
|
46
|
+
newPath = path.concat(propKey);
|
|
41
47
|
setPathConcat(pathKey, newPath);
|
|
42
48
|
}
|
|
49
|
+
// wrap the nested value if it's an object/collection
|
|
43
50
|
return wrapValue(value, newPath);
|
|
44
51
|
},
|
|
45
52
|
set(target, prop, value, receiver) {
|
|
46
53
|
const oldValue = target[prop];
|
|
47
|
-
//
|
|
54
|
+
// avoid unnecessary triggers if the value hasn't changed
|
|
55
|
+
// fast path for primitives
|
|
48
56
|
if (oldValue === value)
|
|
49
57
|
return true;
|
|
50
|
-
//
|
|
51
|
-
if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value))
|
|
52
|
-
return true;
|
|
58
|
+
// deep equality check for objects/arrays
|
|
59
|
+
if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value, new WeakMap()))
|
|
60
|
+
return true; // use new WeakMap for deepEqual seen
|
|
53
61
|
const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
54
62
|
const result = Reflect.set(target, prop, value, receiver);
|
|
55
|
-
//
|
|
63
|
+
// only emit and trigger if the set was successful and wasn't intercepted by a setter
|
|
56
64
|
if (result && (!descriptor || !descriptor.set)) {
|
|
57
|
-
//
|
|
58
|
-
const
|
|
65
|
+
// calculate path, using cache
|
|
66
|
+
const propKey = String(prop);
|
|
67
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
|
|
59
68
|
let newPath = getPathConcat(pathKey);
|
|
60
69
|
if (newPath === undefined) {
|
|
61
|
-
newPath = path.concat(
|
|
70
|
+
newPath = path.concat(propKey);
|
|
62
71
|
setPathConcat(pathKey, newPath);
|
|
63
72
|
}
|
|
64
73
|
const event = {
|
|
@@ -68,32 +77,38 @@ export function reactive(obj, emit, path = [], seen = globalSeen) {
|
|
|
68
77
|
newValue: value
|
|
69
78
|
};
|
|
70
79
|
emit(event);
|
|
71
|
-
//
|
|
80
|
+
// notify effects watching this property
|
|
72
81
|
trigger(target, prop);
|
|
73
82
|
}
|
|
74
83
|
return result;
|
|
75
84
|
},
|
|
76
85
|
deleteProperty(target, prop) {
|
|
77
86
|
const oldValue = target[prop];
|
|
87
|
+
const hadProperty = Object.prototype.hasOwnProperty.call(target, prop);
|
|
78
88
|
const result = Reflect.deleteProperty(target, prop);
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
// only emit and trigger if the property existed and deletion was successful
|
|
90
|
+
if (hadProperty && result) {
|
|
91
|
+
// calculate path, using cache
|
|
92
|
+
const propKey = String(prop);
|
|
93
|
+
const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
|
|
94
|
+
let newPath = getPathConcat(pathKey);
|
|
95
|
+
if (newPath === undefined) {
|
|
96
|
+
newPath = path.concat(propKey);
|
|
97
|
+
setPathConcat(pathKey, newPath);
|
|
98
|
+
}
|
|
99
|
+
const event = {
|
|
100
|
+
action: 'delete',
|
|
101
|
+
path: newPath,
|
|
102
|
+
oldValue
|
|
103
|
+
};
|
|
104
|
+
emit(event);
|
|
105
|
+
// notify effects watching this property
|
|
106
|
+
trigger(target, prop);
|
|
85
107
|
}
|
|
86
|
-
const event = {
|
|
87
|
-
action: 'delete',
|
|
88
|
-
path: newPath,
|
|
89
|
-
oldValue
|
|
90
|
-
};
|
|
91
|
-
emit(event);
|
|
92
|
-
// Trigger effects
|
|
93
|
-
trigger(target, prop);
|
|
94
108
|
return result;
|
|
95
109
|
}
|
|
96
110
|
});
|
|
111
|
+
// cache the proxy to handle circular references and improve performance
|
|
97
112
|
seen.set(obj, proxy);
|
|
98
113
|
return proxy;
|
|
99
114
|
}
|
package/dist/ref.d.ts
CHANGED
|
@@ -4,19 +4,20 @@ export interface Ref<T = any> {
|
|
|
4
4
|
readonly [isRefSymbol]: true;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* creates a reactive reference object.
|
|
8
|
+
* the object has a single `.value` property.
|
|
9
|
+
* reactivity is tracked on access and mutation of the `.value` property.
|
|
10
|
+
* if an object is passed as the initial value, the object itself is *not* made deeply reactive.
|
|
11
|
+
* only the assignment to `.value` is tracked.
|
|
11
12
|
*/
|
|
12
13
|
export declare function ref<T>(value: T): Ref<T>;
|
|
13
14
|
export declare function ref<T = undefined>(): Ref<T | undefined>;
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* checks if a value is a ref object.
|
|
16
17
|
*/
|
|
17
18
|
export declare function isRef<T>(r: any): r is Ref<T>;
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
-
* argument itself.
|
|
20
|
+
* returns the inner value if the argument is a ref,
|
|
21
|
+
* otherwise returns the argument itself. this is a sugar for `isRef(val) ? val.value : val`.
|
|
21
22
|
*/
|
|
22
23
|
export declare function unref<T>(refValue: T | Ref<T>): T;
|
package/dist/ref.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
import { track, trigger } from './watchEffect';
|
|
2
2
|
// Removed reactive import as ref doesn't automatically make contained objects reactive
|
|
3
3
|
// import { reactive } from './reactive';
|
|
4
|
-
//
|
|
5
|
-
export const isRefSymbol = Symbol('isRef');
|
|
4
|
+
// symbol used to identify refs internally and via isRef()
|
|
5
|
+
export const isRefSymbol = Symbol('isRef');
|
|
6
6
|
export function ref(value) {
|
|
7
7
|
return createRef(value);
|
|
8
8
|
}
|
|
9
|
-
//
|
|
9
|
+
// internal factory for creating ref objects
|
|
10
10
|
function createRef(rawValue) {
|
|
11
|
-
//
|
|
11
|
+
// avoid wrapping if the value is already a ref
|
|
12
12
|
if (isRef(rawValue)) {
|
|
13
|
-
// Cast rawValue back to Ref<T> after type guard
|
|
14
13
|
return rawValue;
|
|
15
14
|
}
|
|
16
|
-
//
|
|
17
|
-
let
|
|
18
|
-
//
|
|
15
|
+
// store the inner value
|
|
16
|
+
let _value = rawValue;
|
|
17
|
+
// create the ref object with a getter/setter on `.value`
|
|
19
18
|
const r = {
|
|
20
|
-
[isRefSymbol]: true, //
|
|
19
|
+
[isRefSymbol]: true, // mark as a ref using the symbol
|
|
21
20
|
get value() {
|
|
22
|
-
//
|
|
23
|
-
//
|
|
21
|
+
// track dependency when `.value` is accessed
|
|
22
|
+
// `r` (the ref object itself) is the target for tracking
|
|
24
23
|
track(r, 'value');
|
|
25
|
-
return
|
|
24
|
+
return _value;
|
|
26
25
|
},
|
|
27
26
|
set value(newValue) {
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
//
|
|
27
|
+
// only update and trigger if the value has actually changed
|
|
28
|
+
// this uses strict equality (===), so for objects, it checks reference equality
|
|
29
|
+
if (_value !== newValue) {
|
|
30
|
+
_value = newValue;
|
|
31
|
+
// trigger effects when `.value` is assigned a new value
|
|
32
|
+
// `r` (the ref object itself) is the target for triggering
|
|
34
33
|
trigger(r, 'value');
|
|
35
34
|
}
|
|
36
35
|
},
|
|
37
|
-
}; //
|
|
36
|
+
}; // cast to ensure the object conforms to the Ref interface
|
|
38
37
|
return r;
|
|
39
38
|
}
|
|
40
39
|
/**
|
|
41
|
-
*
|
|
40
|
+
* checks if a value is a ref object.
|
|
42
41
|
*/
|
|
43
42
|
export function isRef(r) {
|
|
43
|
+
// check for the presence of the internal symbol
|
|
44
44
|
return !!(r && r[isRefSymbol]);
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
* argument itself.
|
|
47
|
+
* returns the inner value if the argument is a ref,
|
|
48
|
+
* otherwise returns the argument itself. this is a sugar for `isRef(val) ? val.value : val`.
|
|
49
49
|
*/
|
|
50
50
|
export function unref(refValue) {
|
|
51
51
|
return isRef(refValue) ? refValue.value : refValue;
|