react-state-custom 1.0.9 → 1.0.11
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/index.d.ts +2 -1
- package/dist/index.es.js +336 -335
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +5 -4
- package/dist/index.umd.js.map +1 -0
- package/dist/state-utils/createAutoCtx.d.ts +47 -0
- package/dist/state-utils/createRootCtx.d.ts +37 -0
- package/dist/state-utils/ctx.d.ts +1 -1
- package/dist/state-utils/useArrayHash.d.ts +15 -0
- package/dist/state-utils/useObjectHash.d.ts +15 -1
- package/dist/state-utils/useQuickSubscribe.d.ts +17 -0
- package/package.json +6 -4
- package/src/index.ts +2 -1
- package/src/state-utils/createAutoCtx.tsx +51 -2
- package/src/state-utils/createRootCtx.tsx +38 -1
- package/src/state-utils/ctx.ts +11 -9
- package/src/state-utils/useArrayHash.ts +53 -0
- package/src/state-utils/useObjectHash.ts +24 -1
- package/src/state-utils/useQuickSubscribe.ts +60 -65
- package/vite.config.ts +8 -2
- package/dist/components/MyComponent.d.ts +0 -7
- package/dist/state-utils/useQuickSubscribeV2.d.ts +0 -2
- package/src/components/MyComponent.tsx +0 -17
- package/src/state-utils/useQuickSubscribeV2.ts +0 -93
|
@@ -3,6 +3,35 @@ import { useDataContext, useDataSourceMultiple, type Context } from "./ctx"
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* createRootCtx
|
|
8
|
+
*
|
|
9
|
+
* Factory that creates a headless "Root" component and companion hooks for a context namespace.
|
|
10
|
+
* It derives a unique context name from a base `name` and a props object `U`, then publishes
|
|
11
|
+
* a computed state `V` (from `useFn`) to that context.
|
|
12
|
+
*
|
|
13
|
+
* Usage (manual mounting):
|
|
14
|
+
* ```
|
|
15
|
+
* const { Root, useCtxState } = createRootCtx('user-state', useUserState)
|
|
16
|
+
* ...
|
|
17
|
+
* // Mount exactly one Root per unique props combination
|
|
18
|
+
* <Root userId={id} />
|
|
19
|
+
* ...
|
|
20
|
+
* // Read anywhere ,using the same props shape
|
|
21
|
+
* const user = useCtxState({ userId: id })
|
|
22
|
+
*```
|
|
23
|
+
* Strict vs lenient consumers:
|
|
24
|
+
* - useCtxStateStrict(props) throws if a matching Root is not mounted.
|
|
25
|
+
* - useCtxState(props) logs an error (after 1s) instead of throwing.
|
|
26
|
+
*
|
|
27
|
+
* Multiple instances safety:
|
|
28
|
+
* - Mounting more than one Root with the same resolved context name throws (guards accidental duplicates).
|
|
29
|
+
*
|
|
30
|
+
* Name resolution notes:
|
|
31
|
+
* - The context name is built from `name` + sorted key/value pairs of `props` (U), joined by "-".
|
|
32
|
+
* - Prefer stable, primitive props to avoid collisions; if you need automation, pair with `createAutoCtx` and
|
|
33
|
+
* mount a single <AutoRootCtx Wrapper={ErrorBoundary} /> at the app root so you don't manually mount `Root`.
|
|
34
|
+
*/
|
|
6
35
|
export const createRootCtx = <U extends object, V extends object>(name: string, useFn: (e: U) => V) => {
|
|
7
36
|
|
|
8
37
|
const resolveCtxName = (e: U) => [
|
|
@@ -17,9 +46,9 @@ export const createRootCtx = <U extends object, V extends object>(name: string,
|
|
|
17
46
|
|
|
18
47
|
|
|
19
48
|
const RootState: React.FC<U> = (e: U) => {
|
|
49
|
+
const state = useFn(e)
|
|
20
50
|
const ctxName = resolveCtxName(e)
|
|
21
51
|
const ctx = useDataContext<V>(ctxName)
|
|
22
|
-
const state = useFn(e)
|
|
23
52
|
const stack = useMemo(() => new Error().stack, [])
|
|
24
53
|
|
|
25
54
|
useDataSourceMultiple(
|
|
@@ -45,6 +74,10 @@ export const createRootCtx = <U extends object, V extends object>(name: string,
|
|
|
45
74
|
return {
|
|
46
75
|
resolveCtxName,
|
|
47
76
|
Root: RootState,
|
|
77
|
+
/**
|
|
78
|
+
* Strict consumer: throws if the corresponding Root for these props isn't mounted.
|
|
79
|
+
* Use in development/tests to fail fast when wiring is incorrect.
|
|
80
|
+
*/
|
|
48
81
|
useCtxStateStrict: (e: U): Context<V> => {
|
|
49
82
|
const ctxName = resolveCtxName(e)
|
|
50
83
|
|
|
@@ -60,6 +93,10 @@ export const createRootCtx = <U extends object, V extends object>(name: string,
|
|
|
60
93
|
|
|
61
94
|
return useDataContext<V>(ctxName)
|
|
62
95
|
},
|
|
96
|
+
/**
|
|
97
|
+
* Lenient consumer: schedules a console.error if the Root isn't mounted instead of throwing.
|
|
98
|
+
* Useful in production to avoid hard crashes while still surfacing misconfiguration.
|
|
99
|
+
*/
|
|
63
100
|
useCtxState: (e: U): Context<V> => {
|
|
64
101
|
const ctxName = resolveCtxName(e)
|
|
65
102
|
|
package/src/state-utils/ctx.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { debounce, memoize
|
|
1
|
+
import { EventEmitter } from "events"
|
|
2
|
+
import { debounce, memoize } from "lodash-es"
|
|
3
3
|
import { useEffect, useMemo, useState } from "react"
|
|
4
|
-
import {
|
|
4
|
+
import { useArrayHash } from "./useArrayHash"
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
@@ -19,7 +19,7 @@ export class Context<D> {
|
|
|
19
19
|
this.event.setMaxListeners(100)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
private event = new
|
|
22
|
+
private event = new EventEmitter()
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* The current data held by the context.
|
|
@@ -150,7 +150,9 @@ export const useDataSubscribe = <D, K extends keyof D>(ctx: Context<D> | undefin
|
|
|
150
150
|
|
|
151
151
|
useEffect(() => {
|
|
152
152
|
if (ctx) {
|
|
153
|
-
let callback = debounceTime == 0
|
|
153
|
+
let callback = debounceTime == 0
|
|
154
|
+
? (value: any) => setState({ value } as any)
|
|
155
|
+
: debounce((value: any) => setState({ value } as any), debounceTime)
|
|
154
156
|
let unsub = ctx.subscribe(key, callback)
|
|
155
157
|
value != ctx.data[key] && setState({ value: ctx.data[key] })
|
|
156
158
|
return () => {
|
|
@@ -211,7 +213,7 @@ export const useDataSourceMultiple = <D, T extends readonly (keyof D)[]>(
|
|
|
211
213
|
ctx.data[key] != value && ctx.publish(key, value)
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
|
-
}, [ctx,
|
|
216
|
+
}, [ctx, useArrayHash(entries.flat())])
|
|
215
217
|
|
|
216
218
|
useRegistryChecker(ctx, ...entries.map(e => e[0]) as any)
|
|
217
219
|
|
|
@@ -264,13 +266,13 @@ export const useDataSubscribeMultiple = <D, K extends keyof D>(
|
|
|
264
266
|
/**
|
|
265
267
|
* React hook to subscribe to multiple context values with throttling.
|
|
266
268
|
* @param ctx - The context instance.
|
|
267
|
-
* @param debounceTime -
|
|
269
|
+
* @param debounceTime - Debounce time in ms (default 50).
|
|
268
270
|
* @param keys - Keys to subscribe to.
|
|
269
271
|
* @returns Array of current values for the keys.
|
|
270
272
|
*/
|
|
271
273
|
export const useDataSubscribeMultipleWithDebounce = <D, K extends (keyof D)[]>(
|
|
272
274
|
ctx: Context<D> | undefined,
|
|
273
|
-
debounceTime =
|
|
275
|
+
debounceTime = 50,
|
|
274
276
|
...keys: K
|
|
275
277
|
): { [i in keyof K]: D[K[i]] | undefined } => {
|
|
276
278
|
//@ts-check
|
|
@@ -281,7 +283,7 @@ export const useDataSubscribeMultipleWithDebounce = <D, K extends (keyof D)[]>(
|
|
|
281
283
|
useEffect(() => {
|
|
282
284
|
if (ctx) {
|
|
283
285
|
let prevValues = returnValues
|
|
284
|
-
const callback =
|
|
286
|
+
const callback = debounce(() => {
|
|
285
287
|
let currentValues = keys.map(key => ctx?.data?.[key])
|
|
286
288
|
if (keys.some((key, i) => prevValues[i] != currentValues[i])) {
|
|
287
289
|
prevValues = currentValues
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useRef } from "react"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const randomHash = () => Math.random().toString().slice(2)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* useArrayHash
|
|
8
|
+
*
|
|
9
|
+
* A custom hook that computes a stable hash for an array of values.
|
|
10
|
+
* The hash changes only when the array's contents differ from the previous call.
|
|
11
|
+
*
|
|
12
|
+
* @param e - The input array to hash.
|
|
13
|
+
* @returns A string hash that updates when the array changes.
|
|
14
|
+
*
|
|
15
|
+
* How it works:
|
|
16
|
+
* - Tracks the previous array and its hash using a `useRef`.
|
|
17
|
+
* - Compares the new array to the previous one by length and element equality.
|
|
18
|
+
* - If any difference is detected, generates a new random hash.
|
|
19
|
+
*/
|
|
20
|
+
export const useArrayHash = (e: any[]): string => {
|
|
21
|
+
|
|
22
|
+
const { current: { computedHash } } = useRef({
|
|
23
|
+
/**
|
|
24
|
+
* Getter for the computed hash function.
|
|
25
|
+
*
|
|
26
|
+
* - Initializes with an empty array and a random hash.
|
|
27
|
+
* - Returns a function that compares the current array to the previous one.
|
|
28
|
+
* - Updates the hash if any difference is detected.
|
|
29
|
+
*/
|
|
30
|
+
get computedHash() {
|
|
31
|
+
let currentValues: any[] = []
|
|
32
|
+
let currentHash = randomHash()
|
|
33
|
+
return (e: any[]) => {
|
|
34
|
+
let isDiff = false
|
|
35
|
+
|
|
36
|
+
// Check for differences in array existence, length, or elements.
|
|
37
|
+
isDiff = isDiff || ((!e) != (!currentValues))
|
|
38
|
+
isDiff = isDiff || (e?.length != currentValues?.length);
|
|
39
|
+
isDiff = isDiff || (e.some((f, i) => f != currentValues[i]));
|
|
40
|
+
|
|
41
|
+
// Update the hash if differences are found.
|
|
42
|
+
currentValues = e;
|
|
43
|
+
if (isDiff) {
|
|
44
|
+
currentHash = randomHash()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return currentHash
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return computedHash(e)
|
|
53
|
+
}
|
|
@@ -3,19 +3,42 @@ import { useRef } from "react"
|
|
|
3
3
|
|
|
4
4
|
const randomHash = () => Math.random().toString().slice(2)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* useArrayHash
|
|
8
|
+
*
|
|
9
|
+
* A custom hook that computes a stable hash for an array of values.
|
|
10
|
+
* The hash changes only when the array's contents differ from the previous call.
|
|
11
|
+
*
|
|
12
|
+
* @param e - The input array to hash.
|
|
13
|
+
* @returns A string hash that updates when the array changes.
|
|
14
|
+
*
|
|
15
|
+
* How it works:
|
|
16
|
+
* - Tracks the previous array and its hash using a `useRef`.
|
|
17
|
+
* - Compares the new array to the previous one by length and element equality.
|
|
18
|
+
* - If any difference is detected, generates a new random hash.
|
|
19
|
+
*/
|
|
20
|
+
export const useArrayHash = (e: any[]): string => {
|
|
7
21
|
|
|
8
22
|
const { current: { computedHash } } = useRef({
|
|
23
|
+
/**
|
|
24
|
+
* Getter for the computed hash function.
|
|
25
|
+
*
|
|
26
|
+
* - Initializes with an empty array and a random hash.
|
|
27
|
+
* - Returns a function that compares the current array to the previous one.
|
|
28
|
+
* - Updates the hash if any difference is detected.
|
|
29
|
+
*/
|
|
9
30
|
get computedHash() {
|
|
10
31
|
let currentValues: any[] = []
|
|
11
32
|
let currentHash = randomHash()
|
|
12
33
|
return (e: any[]) => {
|
|
13
34
|
let isDiff = false
|
|
14
35
|
|
|
36
|
+
// Check for differences in array existence, length, or elements.
|
|
15
37
|
isDiff = isDiff || ((!e) != (!currentValues))
|
|
16
38
|
isDiff = isDiff || (e?.length != currentValues?.length);
|
|
17
39
|
isDiff = isDiff || (e.some((f, i) => f != currentValues[i]));
|
|
18
40
|
|
|
41
|
+
// Update the hash if differences are found.
|
|
19
42
|
currentValues = e;
|
|
20
43
|
if (isDiff) {
|
|
21
44
|
currentHash = randomHash()
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
import { debounce } from "lodash-es";
|
|
3
|
+
import { useState, useMemo, useEffect } from "react";
|
|
4
|
+
import type { Context } from "./ctx";
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* useQuickSubscribe is a custom React hook for efficiently subscribing to specific properties of a context's data object.
|
|
3
8
|
*
|
|
@@ -16,11 +21,6 @@
|
|
|
16
21
|
* return <div>{name}</div>;
|
|
17
22
|
*/
|
|
18
23
|
|
|
19
|
-
import { debounce } from "lodash-es";
|
|
20
|
-
import { useState, useMemo, useEffect } from "react";
|
|
21
|
-
import type { Context } from "./ctx";
|
|
22
|
-
|
|
23
|
-
|
|
24
24
|
export const useQuickSubscribe = <D>(
|
|
25
25
|
ctx: Context<D> | undefined
|
|
26
26
|
): {
|
|
@@ -29,85 +29,80 @@ export const useQuickSubscribe = <D>(
|
|
|
29
29
|
|
|
30
30
|
const [, setCounter] = useState(0);
|
|
31
31
|
|
|
32
|
-
const { proxy,
|
|
32
|
+
const { proxy, finalGetter, openGetter, clean } = useMemo(
|
|
33
33
|
() => {
|
|
34
34
|
|
|
35
35
|
const allKeys = new Set<keyof D>()
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
const handleOnChange = debounce(() => {
|
|
40
|
-
console.log("handleOnChange",allCompareValue)
|
|
41
|
-
if (ctx && Object
|
|
42
|
-
.keys(allCompareValue)
|
|
43
|
-
.some((i: any) => allCompareValue[i as keyof D] != ctx?.data[i as keyof D])) {
|
|
44
|
-
setCounter(c => c + 1);
|
|
45
|
-
}
|
|
46
|
-
}, 1)
|
|
47
|
-
|
|
48
|
-
const handleChangeKey = debounce(() => {
|
|
49
|
-
if (ctx) {
|
|
50
|
-
console.log("handleChangeKey")
|
|
51
|
-
let shouldUpdate = false;
|
|
52
|
-
let keyToDelete: (keyof D)[] = []
|
|
53
|
-
|
|
54
|
-
for (let [k, unsub] of allUnsubInstance) {
|
|
55
|
-
if (!allKeys.has(k)) {
|
|
56
|
-
console.log("Remove", k)
|
|
57
|
-
unsub?.();
|
|
58
|
-
keyToDelete.push(k)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
keyToDelete.forEach(k => {
|
|
62
|
-
allUnsubInstance.delete(k);
|
|
63
|
-
delete allCompareValue[k];
|
|
64
|
-
})
|
|
65
|
-
for (let k of allKeys) {
|
|
66
|
-
if (!allUnsubInstance.has(k)) {
|
|
67
|
-
console.log("Add ", k)
|
|
68
|
-
const sub = ctx.subscribe(k, handleOnChange);
|
|
69
|
-
allUnsubInstance.set(k, sub);
|
|
70
|
-
shouldUpdate = true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
allKeys.clear()
|
|
74
|
-
if (shouldUpdate) handleOnChange?.();
|
|
75
|
-
}
|
|
76
|
-
}, 0)
|
|
77
|
-
|
|
78
|
-
const handleAddKey = (p: keyof D) => {
|
|
79
|
-
allKeys.add(p);
|
|
80
|
-
handleChangeKey();
|
|
81
|
-
}
|
|
36
|
+
const allCompareValue: { [P in keyof D]?: D[P] | undefined; } = {}
|
|
37
|
+
const allUnsub = new Map()
|
|
82
38
|
|
|
83
39
|
const proxy = new Proxy(
|
|
84
40
|
ctx?.data as any,
|
|
85
41
|
{
|
|
86
42
|
get(target, p) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
43
|
+
if (isOpenGetter) {
|
|
44
|
+
allKeys.add(p as keyof D)
|
|
45
|
+
return allCompareValue[p as keyof D] = target[p];
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error("now allow here")
|
|
48
|
+
}
|
|
90
49
|
}
|
|
91
50
|
}
|
|
92
51
|
) as any
|
|
93
52
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
53
|
+
let isOpenGetter = true;
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
let onChange = debounce(() => {
|
|
57
|
+
if ([...allKeys.values()]
|
|
58
|
+
.some(k => allCompareValue[k] != ctx?.data?.[k])) {
|
|
59
|
+
setCounter(c => c + 1)
|
|
60
|
+
}
|
|
61
|
+
}, 0)
|
|
62
|
+
|
|
63
|
+
let openGetter = () => {
|
|
64
|
+
isOpenGetter = true
|
|
65
|
+
allKeys.clear()
|
|
97
66
|
}
|
|
98
67
|
|
|
99
|
-
|
|
100
|
-
|
|
68
|
+
let finalGetter = () => {
|
|
69
|
+
isOpenGetter = false;
|
|
70
|
+
|
|
71
|
+
[...allKeys.values()]
|
|
72
|
+
.filter(k => !allUnsub.has(k))
|
|
73
|
+
.forEach(k => {
|
|
74
|
+
allUnsub.set(k, ctx?.subscribe(k, onChange))
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
[...allUnsub.keys()]
|
|
78
|
+
.filter(k => !allKeys.has(k))
|
|
79
|
+
.forEach(k => {
|
|
80
|
+
let unsub = allUnsub.get(k)
|
|
81
|
+
unsub?.();
|
|
82
|
+
allUnsub.delete(k);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let clean = () => {
|
|
88
|
+
openGetter();
|
|
89
|
+
finalGetter();
|
|
90
|
+
setCounter(c => c + 1)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { proxy, finalGetter, openGetter, clean }
|
|
101
94
|
},
|
|
102
95
|
[ctx]
|
|
103
96
|
)
|
|
104
97
|
|
|
105
|
-
|
|
98
|
+
openGetter();
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
setTimeout(finalGetter, 0)
|
|
101
|
+
|
|
102
|
+
useEffect(
|
|
103
|
+
() => () => clean(),
|
|
104
|
+
[clean]
|
|
105
|
+
)
|
|
111
106
|
|
|
112
107
|
return proxy;
|
|
113
108
|
|
package/vite.config.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { defineConfig } from 'vite';
|
|
2
2
|
import react from '@vitejs/plugin-react';
|
|
3
3
|
import dts from 'vite-plugin-dts'
|
|
4
|
+
import { analyzer } from 'vite-bundle-analyzer'
|
|
4
5
|
|
|
5
6
|
export default defineConfig({
|
|
6
7
|
plugins: [
|
|
7
8
|
react(),
|
|
8
9
|
dts({
|
|
9
10
|
include: ['src'],
|
|
10
|
-
})
|
|
11
|
+
}),
|
|
12
|
+
analyzer({
|
|
13
|
+
analyzerMode: "server",
|
|
14
|
+
openAnalyzer: true
|
|
15
|
+
}),
|
|
11
16
|
],
|
|
12
17
|
build: {
|
|
13
18
|
lib: {
|
|
@@ -21,11 +26,12 @@ export default defineConfig({
|
|
|
21
26
|
external: ['react', 'react-dom'],
|
|
22
27
|
output: {
|
|
23
28
|
globals: {
|
|
24
|
-
react: 'React',
|
|
29
|
+
'react': 'React',
|
|
25
30
|
'react-dom': 'ReactDOM',
|
|
26
31
|
},
|
|
27
32
|
},
|
|
28
33
|
},
|
|
34
|
+
sourcemap:true
|
|
29
35
|
},
|
|
30
36
|
server: {
|
|
31
37
|
port: 3000,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
interface MyComponentProps {
|
|
4
|
-
title: string;
|
|
5
|
-
description?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const MyComponent: React.FC<MyComponentProps> = ({ title, description }) => {
|
|
9
|
-
return (
|
|
10
|
-
<div>
|
|
11
|
-
<h1>{title}</h1>
|
|
12
|
-
{description && <p>{description}</p>}
|
|
13
|
-
</div>
|
|
14
|
-
);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default MyComponent;
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { debounce } from "lodash-es";
|
|
3
|
-
import { useState, useMemo, useEffect } from "react";
|
|
4
|
-
import type { Context } from "./ctx";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const useQuickSubscribeV2 = <D>(
|
|
8
|
-
ctx: Context<D> | undefined
|
|
9
|
-
): {
|
|
10
|
-
[P in keyof D]?: D[P] | undefined;
|
|
11
|
-
} => {
|
|
12
|
-
|
|
13
|
-
const [, setCounter] = useState(0);
|
|
14
|
-
|
|
15
|
-
const { proxy, finalGetter, openGetter, clean } = useMemo(
|
|
16
|
-
() => {
|
|
17
|
-
|
|
18
|
-
const allKeys = new Set<keyof D>()
|
|
19
|
-
const allCompareValue: { [P in keyof D]?: D[P] | undefined; } = {}
|
|
20
|
-
const allUnsub = new Map()
|
|
21
|
-
|
|
22
|
-
const proxy = new Proxy(
|
|
23
|
-
ctx?.data as any,
|
|
24
|
-
{
|
|
25
|
-
get(target, p) {
|
|
26
|
-
if (isOpenGetter) {
|
|
27
|
-
allKeys.add(p as keyof D)
|
|
28
|
-
return allCompareValue[p as keyof D] = target[p];
|
|
29
|
-
} else {
|
|
30
|
-
throw new Error("now allow here")
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
) as any
|
|
35
|
-
|
|
36
|
-
let isOpenGetter = true;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let onChange = debounce(() => {
|
|
40
|
-
if ([...allKeys.values()]
|
|
41
|
-
.some(k => allCompareValue[k] != ctx?.data?.[k])) {
|
|
42
|
-
setCounter(c => c + 1)
|
|
43
|
-
}
|
|
44
|
-
}, 0)
|
|
45
|
-
|
|
46
|
-
let openGetter = () => {
|
|
47
|
-
isOpenGetter = true
|
|
48
|
-
allKeys.clear()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let finalGetter = () => {
|
|
52
|
-
isOpenGetter = false;
|
|
53
|
-
|
|
54
|
-
[...allKeys.values()]
|
|
55
|
-
.filter(k => !allUnsub.has(k))
|
|
56
|
-
.forEach(k => {
|
|
57
|
-
allUnsub.set(k, ctx?.subscribe(k, onChange))
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
[...allUnsub.keys()]
|
|
61
|
-
.filter(k => !allKeys.has(k))
|
|
62
|
-
.forEach(k => {
|
|
63
|
-
let unsub = allUnsub.get(k)
|
|
64
|
-
unsub?.();
|
|
65
|
-
allUnsub.delete(k);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let clean = () => {
|
|
71
|
-
openGetter();
|
|
72
|
-
finalGetter();
|
|
73
|
-
setCounter(c => c + 1)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return { proxy, finalGetter, openGetter, clean }
|
|
77
|
-
},
|
|
78
|
-
[ctx]
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
openGetter();
|
|
82
|
-
|
|
83
|
-
setTimeout(finalGetter, 0)
|
|
84
|
-
|
|
85
|
-
useEffect(
|
|
86
|
-
() => () => clean(),
|
|
87
|
-
[clean]
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
return proxy;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|