@hanzogui/use-store 2.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/LICENSE +21 -0
- package/README.md +91 -0
- package/dist/cjs/comparators.cjs +37 -0
- package/dist/cjs/comparators.native.js +46 -0
- package/dist/cjs/comparators.native.js.map +1 -0
- package/dist/cjs/configureUseStore.cjs +30 -0
- package/dist/cjs/configureUseStore.native.js +33 -0
- package/dist/cjs/configureUseStore.native.js.map +1 -0
- package/dist/cjs/constants.cjs +31 -0
- package/dist/cjs/constants.native.js +34 -0
- package/dist/cjs/constants.native.js.map +1 -0
- package/dist/cjs/decorators.cjs +30 -0
- package/dist/cjs/decorators.native.js +33 -0
- package/dist/cjs/decorators.native.js.map +1 -0
- package/dist/cjs/helpers.cjs +55 -0
- package/dist/cjs/helpers.native.js +65 -0
- package/dist/cjs/helpers.native.js.map +1 -0
- package/dist/cjs/index.cjs +40 -0
- package/dist/cjs/index.native.js +54 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/interfaces.cjs +16 -0
- package/dist/cjs/interfaces.native.js +19 -0
- package/dist/cjs/interfaces.native.js.map +1 -0
- package/dist/cjs/observe.cjs +120 -0
- package/dist/cjs/observe.native.js +186 -0
- package/dist/cjs/observe.native.js.map +1 -0
- package/dist/cjs/useStore.cjs +390 -0
- package/dist/cjs/useStore.native.js +544 -0
- package/dist/cjs/useStore.native.js.map +1 -0
- package/dist/cjs/useStoreDebug.cjs +56 -0
- package/dist/cjs/useStoreDebug.native.js +65 -0
- package/dist/cjs/useStoreDebug.native.js.map +1 -0
- package/dist/esm/comparators.mjs +14 -0
- package/dist/esm/comparators.mjs.map +1 -0
- package/dist/esm/comparators.native.js +20 -0
- package/dist/esm/comparators.native.js.map +1 -0
- package/dist/esm/configureUseStore.mjs +6 -0
- package/dist/esm/configureUseStore.mjs.map +1 -0
- package/dist/esm/configureUseStore.native.js +6 -0
- package/dist/esm/configureUseStore.native.js.map +1 -0
- package/dist/esm/constants.mjs +7 -0
- package/dist/esm/constants.mjs.map +1 -0
- package/dist/esm/constants.native.js +7 -0
- package/dist/esm/constants.native.js.map +1 -0
- package/dist/esm/decorators.mjs +7 -0
- package/dist/esm/decorators.mjs.map +1 -0
- package/dist/esm/decorators.native.js +7 -0
- package/dist/esm/decorators.native.js.map +1 -0
- package/dist/esm/helpers.mjs +26 -0
- package/dist/esm/helpers.mjs.map +1 -0
- package/dist/esm/helpers.native.js +33 -0
- package/dist/esm/helpers.native.js.map +1 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.mjs +14 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +25 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/interfaces.mjs +2 -0
- package/dist/esm/interfaces.mjs.map +1 -0
- package/dist/esm/interfaces.native.js +2 -0
- package/dist/esm/interfaces.native.js.map +1 -0
- package/dist/esm/observe.mjs +85 -0
- package/dist/esm/observe.mjs.map +1 -0
- package/dist/esm/observe.native.js +148 -0
- package/dist/esm/observe.native.js.map +1 -0
- package/dist/esm/useStore.mjs +342 -0
- package/dist/esm/useStore.mjs.map +1 -0
- package/dist/esm/useStore.native.js +493 -0
- package/dist/esm/useStore.native.js.map +1 -0
- package/dist/esm/useStoreDebug.mjs +18 -0
- package/dist/esm/useStoreDebug.mjs.map +1 -0
- package/dist/esm/useStoreDebug.native.js +24 -0
- package/dist/esm/useStoreDebug.native.js.map +1 -0
- package/package.json +47 -0
- package/src/comparators.tsx +18 -0
- package/src/configureUseStore.tsx +7 -0
- package/src/constants.tsx +6 -0
- package/src/decorators.tsx +8 -0
- package/src/helpers.tsx +56 -0
- package/src/index.ts +12 -0
- package/src/interfaces.tsx +46 -0
- package/src/observe.tsx +160 -0
- package/src/useStore.tsx +705 -0
- package/src/useStoreDebug.tsx +41 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ComparisonFn = (a: any, b: any) => boolean
|
|
2
|
+
|
|
3
|
+
export function compare(comparator: ComparisonFn) {
|
|
4
|
+
return (target: any, propertyKey: string): any => {
|
|
5
|
+
target['_comparators'] = target['_comparators'] || {}
|
|
6
|
+
target['_comparators'][propertyKey] = comparator
|
|
7
|
+
}
|
|
8
|
+
}
|
package/src/helpers.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { simpleHash } from '@hanzogui/simple-hash'
|
|
2
|
+
|
|
3
|
+
import type { StoreInfo } from './interfaces'
|
|
4
|
+
|
|
5
|
+
export function getStoreUid(Constructor: any, props: string | object | void) {
|
|
6
|
+
return simpleHash(
|
|
7
|
+
`${Constructor}${
|
|
8
|
+
!props ? '' : typeof props === 'string' ? props : JSON.stringify(props)
|
|
9
|
+
}`,
|
|
10
|
+
'strict'
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const UNWRAP_STORE_INFO = Symbol('UNWRAP_STORE_INFO')
|
|
15
|
+
export const cache = new Map<string, StoreInfo>()
|
|
16
|
+
|
|
17
|
+
export function getStoreDescriptors(storeInstance: any) {
|
|
18
|
+
const proto = Object.getPrototypeOf(storeInstance)
|
|
19
|
+
const instanceDescriptors = Object.getOwnPropertyDescriptors(storeInstance)
|
|
20
|
+
const protoDescriptors = Object.getOwnPropertyDescriptors(proto)
|
|
21
|
+
const descriptors = {
|
|
22
|
+
...protoDescriptors,
|
|
23
|
+
...instanceDescriptors,
|
|
24
|
+
}
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
delete descriptors.constructor
|
|
27
|
+
return descriptors
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function get<A>(_: A, b?: any): A extends new (props?: any) => infer B ? B : A {
|
|
31
|
+
return _ as any
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function simpleStr(arg: any) {
|
|
35
|
+
if (process.env.NODE_ENV === 'development') {
|
|
36
|
+
return typeof arg === 'function'
|
|
37
|
+
? 'fn'
|
|
38
|
+
: typeof arg === 'string'
|
|
39
|
+
? `"${arg}"`
|
|
40
|
+
: !arg
|
|
41
|
+
? arg
|
|
42
|
+
: typeof arg !== 'object'
|
|
43
|
+
? arg
|
|
44
|
+
: Array.isArray(arg)
|
|
45
|
+
? '[...]'
|
|
46
|
+
: `{...}`
|
|
47
|
+
}
|
|
48
|
+
return arg
|
|
49
|
+
}
|
|
50
|
+
// helper for debugging
|
|
51
|
+
|
|
52
|
+
export function getStoreDebugInfo(store: any) {
|
|
53
|
+
return (
|
|
54
|
+
store[UNWRAP_STORE_INFO] ?? cache.get(getStoreUid(store.constructor, store.props))
|
|
55
|
+
)
|
|
56
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './useStore'
|
|
2
|
+
export { configureUseStore } from './configureUseStore'
|
|
3
|
+
export * from './interfaces'
|
|
4
|
+
export * from './observe'
|
|
5
|
+
export { UNWRAP_PROXY } from './constants'
|
|
6
|
+
export * from './comparators'
|
|
7
|
+
export * from './decorators'
|
|
8
|
+
|
|
9
|
+
// to extend for prop types
|
|
10
|
+
export class Store<Props extends Record<string, any>> {
|
|
11
|
+
constructor(public props: Props) {}
|
|
12
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { StoreTracker } from './useStore'
|
|
2
|
+
|
|
3
|
+
export type Selector<A = unknown, B = unknown> = (x: A) => B
|
|
4
|
+
|
|
5
|
+
export type UseStoreSelector<Store, Res> = (store: Store) => Res
|
|
6
|
+
export type UseStoreOptions<Store = any, SelectorRes = any> = {
|
|
7
|
+
debug?: boolean
|
|
8
|
+
selector?: UseStoreSelector<Store, SelectorRes>
|
|
9
|
+
once?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Store<Props = Record<string, any> | null | undefined> {
|
|
13
|
+
new (...args: any[]): any
|
|
14
|
+
props: Props
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type StoreInfo<A = Store> = {
|
|
18
|
+
uid: string
|
|
19
|
+
keyComparators?: {
|
|
20
|
+
[key: string]: (a: any, b: any) => boolean
|
|
21
|
+
}
|
|
22
|
+
// proxied store
|
|
23
|
+
store: A
|
|
24
|
+
props: Record<string, any> | null
|
|
25
|
+
storeInstance: any
|
|
26
|
+
getters: { [key: string]: any }
|
|
27
|
+
actions: any
|
|
28
|
+
stateKeys: Set<string>
|
|
29
|
+
debug?: boolean
|
|
30
|
+
gettersState: {
|
|
31
|
+
getCache: Map<string, any>
|
|
32
|
+
depsToGetter: Map<string, Set<string>>
|
|
33
|
+
curGetKeys: Set<string>
|
|
34
|
+
isGetting: boolean
|
|
35
|
+
}
|
|
36
|
+
listeners: Set<Function>
|
|
37
|
+
trackers: Set<StoreTracker>
|
|
38
|
+
version: number
|
|
39
|
+
subscribe: (onChanged: () => void) => () => void
|
|
40
|
+
triggerUpdate: Function
|
|
41
|
+
disableTracking: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type UseStoreConfig = {
|
|
45
|
+
logLevel?: 'debug' | 'info' | 'error'
|
|
46
|
+
}
|
package/src/observe.tsx
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { isEqualSubsetShallow } from './comparators'
|
|
4
|
+
import { UNWRAP_PROXY } from './constants'
|
|
5
|
+
import type { StoreInfo } from './interfaces'
|
|
6
|
+
import { trackStoresAccess } from './useStore'
|
|
7
|
+
|
|
8
|
+
const logUpdate =
|
|
9
|
+
process.env.NODE_ENV === 'development'
|
|
10
|
+
? (fn: any, stores: any[], last: any, next: any) => {
|
|
11
|
+
const getStoreLogName = (store: any) => {
|
|
12
|
+
const str = store[UNWRAP_PROXY] ?? store
|
|
13
|
+
return `${str.constructor.name}${store.props?.id ? `:${store.props.id}` : ''}`
|
|
14
|
+
}
|
|
15
|
+
const storeNames = stores.map(getStoreLogName).join(', ')
|
|
16
|
+
const name = `🌑 ▶️ %c${fn.name} ${storeNames} () ${last} => ${next}`
|
|
17
|
+
console.groupCollapsed(name, 'color: tomato;')
|
|
18
|
+
console.groupCollapsed('trace >')
|
|
19
|
+
console.trace()
|
|
20
|
+
console.groupEnd()
|
|
21
|
+
console.info(' next', next)
|
|
22
|
+
console.groupEnd()
|
|
23
|
+
}
|
|
24
|
+
: null
|
|
25
|
+
|
|
26
|
+
export function observe(fn: () => any) {
|
|
27
|
+
let prev = getObserverValueAndStoresAccessed(fn)
|
|
28
|
+
let disposeValue: Function | null = null
|
|
29
|
+
|
|
30
|
+
const subscribe = () => {
|
|
31
|
+
const stores = [...prev.storeInfos]
|
|
32
|
+
return subscribeToStores(stores, () => {
|
|
33
|
+
disposeValue?.()
|
|
34
|
+
const next = getObserverValueAndStoresAccessed(fn)
|
|
35
|
+
|
|
36
|
+
if (typeof next.value === 'function') {
|
|
37
|
+
disposeValue = next.value
|
|
38
|
+
if (process.env.NODE_ENV === 'development') {
|
|
39
|
+
logUpdate!(fn, [...next.storeInfos], '(fn)', '(fn)')
|
|
40
|
+
}
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
isEqualSubsetShallow(prev.storeInfos, next.storeInfos) &&
|
|
45
|
+
isEqualSubsetShallow(prev.value, next.value)
|
|
46
|
+
) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
if (process.env.NODE_ENV === 'development') {
|
|
50
|
+
logUpdate!(fn, [...next.storeInfos], prev.value, next.value)
|
|
51
|
+
}
|
|
52
|
+
prev = next
|
|
53
|
+
dispose()
|
|
54
|
+
dispose = subscribe()
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let dispose = subscribe()
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
dispose: () => {
|
|
62
|
+
dispose()
|
|
63
|
+
disposeValue?.()
|
|
64
|
+
},
|
|
65
|
+
getValue: () => prev.value,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function useObserve<A>(fn: () => A): A {
|
|
70
|
+
const [state, setState] = React.useState(() => {
|
|
71
|
+
return getObserverValueAndStoresAccessed(fn)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
let dispose
|
|
76
|
+
const unsub = subscribeToStores([...state.storeInfos], () => {
|
|
77
|
+
dispose?.()
|
|
78
|
+
const next = getObserverValueAndStoresAccessed(fn)
|
|
79
|
+
|
|
80
|
+
const nextStoreInfos = [...next.storeInfos]
|
|
81
|
+
const prevStoreInfos = [...state.storeInfos]
|
|
82
|
+
|
|
83
|
+
// return function === return disposable
|
|
84
|
+
if (typeof next.value === 'function') {
|
|
85
|
+
if (process.env.NODE_ENV === 'development') {
|
|
86
|
+
logUpdate!(fn, nextStoreInfos, '(fn)', '(fn)')
|
|
87
|
+
}
|
|
88
|
+
dispose = next.value
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setState((prev) => {
|
|
93
|
+
if (
|
|
94
|
+
isEqualSubsetShallow(prevStoreInfos, nextStoreInfos) &&
|
|
95
|
+
isEqualSubsetShallow(prev.value, next.value)
|
|
96
|
+
) {
|
|
97
|
+
return prev
|
|
98
|
+
}
|
|
99
|
+
if (process.env.NODE_ENV === 'development') {
|
|
100
|
+
logUpdate!(fn, nextStoreInfos, prev.value, next.value)
|
|
101
|
+
}
|
|
102
|
+
return next
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return () => {
|
|
107
|
+
unsub()
|
|
108
|
+
dispose?.()
|
|
109
|
+
}
|
|
110
|
+
}, [[...state.storeInfos].map((i) => i.uid).join(',')])
|
|
111
|
+
|
|
112
|
+
return state.value
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getObserverValueAndStoresAccessed<A>(selector: () => A): {
|
|
116
|
+
value: A
|
|
117
|
+
storeInfos: Set<StoreInfo>
|
|
118
|
+
} {
|
|
119
|
+
const storeInfos = new Set<StoreInfo>()
|
|
120
|
+
const dispose = trackStoresAccess((storeInfo) => {
|
|
121
|
+
storeInfos.add(storeInfo)
|
|
122
|
+
})
|
|
123
|
+
const value = selector()
|
|
124
|
+
dispose()
|
|
125
|
+
return {
|
|
126
|
+
value,
|
|
127
|
+
storeInfos,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function subscribeToStores(storeInfos: StoreInfo[], onUpdate: () => any) {
|
|
132
|
+
const disposes: Function[] = []
|
|
133
|
+
|
|
134
|
+
// wrap onUpdate to avoid waterfall calls + avoid tracking during onUpdate
|
|
135
|
+
let isUpdating = false
|
|
136
|
+
const onUpdateDebouncedWithoutTracking = () => {
|
|
137
|
+
if (isUpdating) return
|
|
138
|
+
isUpdating = true
|
|
139
|
+
queueMicrotask(() => {
|
|
140
|
+
try {
|
|
141
|
+
for (const storeInfo of storeInfos) {
|
|
142
|
+
storeInfo.disableTracking = true
|
|
143
|
+
}
|
|
144
|
+
onUpdate()
|
|
145
|
+
} finally {
|
|
146
|
+
isUpdating = false
|
|
147
|
+
for (const storeInfo of storeInfos) {
|
|
148
|
+
storeInfo.disableTracking = false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const storeInfo of storeInfos) {
|
|
155
|
+
disposes.push(storeInfo.subscribe(onUpdateDebouncedWithoutTracking))
|
|
156
|
+
}
|
|
157
|
+
return () => {
|
|
158
|
+
disposes.forEach((x) => x())
|
|
159
|
+
}
|
|
160
|
+
}
|