react-state-custom 1.0.0
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/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +8 -0
- package/.yarnrc.yml +1 -0
- package/README.md +57 -0
- package/dist/components/MyComponent.d.ts +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +1013 -0
- package/dist/index.umd.js +22 -0
- package/dist/state-utils/createRootCtx.d.ts +5 -0
- package/dist/state-utils/ctx.d.ts +94 -0
- package/dist/state-utils/useQuickSubscribe.d.ts +2 -0
- package/dist/state-utils/useQuickSubscribeV2.d.ts +2 -0
- package/dist/state-utils/useRefValue.d.ts +1 -0
- package/fix-vscode-yarn-pnp.sh +26 -0
- package/package.json +35 -0
- package/src/components/MyComponent.tsx +17 -0
- package/src/index.ts +19 -0
- package/src/state-utils/createRootCtx.tsx +56 -0
- package/src/state-utils/ctx.ts +308 -0
- package/src/state-utils/useQuickSubscribe.ts +115 -0
- package/src/state-utils/useQuickSubscribeV2.ts +93 -0
- package/src/state-utils/useRefValue.ts +8 -0
- package/tsconfig.json +18 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useQuickSubscribe is a custom React hook for efficiently subscribing to specific properties of a context's data object.
|
|
3
|
+
*
|
|
4
|
+
* @template D - The shape of the context data.
|
|
5
|
+
* @param {Context<D> | undefined} ctx - The context object containing data and a subscribe method.
|
|
6
|
+
* @returns {Partial<D>} A proxy object that mirrors the context data, automatically subscribing to properties as they are accessed.
|
|
7
|
+
*
|
|
8
|
+
* This hook tracks which properties of the context data are accessed by the component and subscribes to updates for only those properties.
|
|
9
|
+
* When any of the subscribed properties change, the hook triggers a re-render. Subscriptions are managed and cleaned up automatically
|
|
10
|
+
* when the component unmounts or the context changes. This approach minimizes unnecessary re-renders and resource usage by only
|
|
11
|
+
* subscribing to the data that the component actually uses.
|
|
12
|
+
*
|
|
13
|
+
* Example usage:
|
|
14
|
+
* const {name} = useQuickSubscribe(userContext);
|
|
15
|
+
* // Accessing name will subscribe to changes in 'name' only
|
|
16
|
+
* return <div>{name}</div>;
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { debounce } from "lodash-es";
|
|
20
|
+
import { useState, useMemo, useEffect } from "react";
|
|
21
|
+
import type { Context } from "./ctx";
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export const useQuickSubscribe = <D>(
|
|
25
|
+
ctx: Context<D> | undefined
|
|
26
|
+
): {
|
|
27
|
+
[P in keyof D]?: D[P] | undefined;
|
|
28
|
+
} => {
|
|
29
|
+
|
|
30
|
+
const [, setCounter] = useState(0);
|
|
31
|
+
|
|
32
|
+
const { proxy, clean, handleOnChange } = useMemo(
|
|
33
|
+
() => {
|
|
34
|
+
|
|
35
|
+
const allKeys = new Set<keyof D>()
|
|
36
|
+
const allUnsubInstance = new Map<keyof D, Function>()
|
|
37
|
+
const allCompareValue = ({} as Partial<D>)
|
|
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
|
+
}
|
|
82
|
+
|
|
83
|
+
const proxy = new Proxy(
|
|
84
|
+
ctx?.data as any,
|
|
85
|
+
{
|
|
86
|
+
get(target, p) {
|
|
87
|
+
handleAddKey(p as keyof D);
|
|
88
|
+
console.log({ [p]: target[p] });
|
|
89
|
+
return allCompareValue[p as keyof D] = target[p];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
) as any
|
|
93
|
+
|
|
94
|
+
const clean = () => {
|
|
95
|
+
console.log("Clean", allKeys)
|
|
96
|
+
handleChangeKey?.()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log("NEW")
|
|
100
|
+
return { proxy, clean, handleOnChange }
|
|
101
|
+
},
|
|
102
|
+
[ctx]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
useEffect(() => () => clean?.(), [clean])
|
|
106
|
+
|
|
107
|
+
// useEffect(() => {
|
|
108
|
+
// let i = setInterval(handleOnChange, 5000);
|
|
109
|
+
// return () => clearInterval(i);
|
|
110
|
+
// },[handleOnChange])
|
|
111
|
+
|
|
112
|
+
return proxy;
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
[]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
openGetter();
|
|
82
|
+
|
|
83
|
+
setTimeout(finalGetter, 0)
|
|
84
|
+
|
|
85
|
+
useEffect(
|
|
86
|
+
() => () => clean(),
|
|
87
|
+
[clean]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return proxy;
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"lib": ["dom", "esnext"],
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"outDir": "./dist",
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"sourceMap": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import dts from 'vite-plugin-dts'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
dts({
|
|
9
|
+
include: ['src'],
|
|
10
|
+
})
|
|
11
|
+
],
|
|
12
|
+
build: {
|
|
13
|
+
lib: {
|
|
14
|
+
entry: 'src/index.ts',
|
|
15
|
+
name: 'RState',
|
|
16
|
+
fileName: (format) => `index.${format}.js`,
|
|
17
|
+
formats: ['es', 'umd'],
|
|
18
|
+
},
|
|
19
|
+
rollupOptions: {
|
|
20
|
+
// Ensure to externalize deps that shouldn't be bundled
|
|
21
|
+
external: ['react', 'react-dom'],
|
|
22
|
+
output: {
|
|
23
|
+
globals: {
|
|
24
|
+
react: 'React',
|
|
25
|
+
'react-dom': 'ReactDOM',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
server: {
|
|
31
|
+
port: 3000,
|
|
32
|
+
},
|
|
33
|
+
});
|