-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.tsx
139 lines (116 loc) · 4.15 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* @file index.ts
* @author 余聪
*
*/
import { useMemo } from 'react'
import * as React from 'react'
import useFetcher, { TFetcher, TFetcherResult } from '@rcp/use.fetcher'
import { TFetcherOptions, suspense, suspenseForwardRef } from '@rcp/use.fetcher'
import useForceUpdate from '@rcp/use.forceupdate'
import usePersistFn from '@rcp/use.persistfn'
export { suspense, suspenseForwardRef }
export type TSharedProviderOptions<T extends (a: any, opts: TFetcherOptions, ...args: any[]) => TFetcherResult<any>> =
Parameters<T>[1] & {
useFetcher?: T
}
export function useSharedProvider<T, F extends (a: any, opts: TFetcherOptions, ...args: any[]) => TFetcherResult<any>>(
CreateFetcherSymbol: TFetcher<T>,
{ key, ...opts }: TSharedProviderOptions<F> = {},
deps?: Parameters<F>[2]
) {
const useLoader = React.useMemo(() => opts.useFetcher ?? useFetcher, [])
// @ts-expect-error
const result = useLoader<T>(CreateFetcherSymbol, { disableDependencyCollect: true, ...opts, key }, deps)
const [, , entity] = result
const map = useSharedMap()
const updateMap = useSharedUpdateMap()
// eslint-disable-next-line react-hooks/exhaustive-deps
const fetcherKey = useMemo(() => key || CreateFetcherSymbol, [key || CreateFetcherSymbol])
// 同步写
map.set(fetcherKey, result)
React.useLayoutEffect(
() => () => {
map.delete(fetcherKey)
},
[fetcherKey, map]
)
React.useEffect(() => {
// 异步 batch 更新使用的地方
const updateList = updateMap.get(fetcherKey)
if (updateList) {
updateList.forEach((fn) => fn())
}
}, [fetcherKey, entity.res, entity.loading, entity.error, entity.initialized, updateMap])
return result
}
export function useShared<T>(
valueSymbol: TFetcher<T>
): [T, (newValue: T) => void, TFetcherResult<T>] | [undefined, undefined, {}] {
const map = useSharedMap()
const updateMap = useSharedUpdateMap()
const [_forceUpdate] = useForceUpdate()
const prevRef = React.useRef()
prevRef.current = map.get(valueSymbol)
const forceUpdate = usePersistFn(() => {
if (prevRef.current !== map.get(valueSymbol)) {
_forceUpdate()
}
})
React.useMemo(() => {
const prevValue = updateMap.get(valueSymbol)
if (!prevValue) {
updateMap.set(valueSymbol, [forceUpdate])
} else if (!prevValue.includes(forceUpdate)) {
prevValue.push(forceUpdate)
}
}, [valueSymbol, updateMap, forceUpdate])
React.useEffect(
() => () => {
const prevValue = updateMap.get(valueSymbol)
if (prevValue) {
const index = prevValue.indexOf(forceUpdate)
prevValue.splice(index, 1)
}
},
[forceUpdate, valueSymbol, updateMap]
)
return prevRef.current || [undefined, undefined, {}]
}
export function useSharedFetcher<T, F extends (a: any, opts: TFetcherOptions, ...args: any[]) => TFetcherResult<any>>(
createFetcherSymbol: TFetcher<T>,
{ key, ...opts }: TSharedProviderOptions<F> = {},
deps?: Parameters<F>[2]
): TFetcherResult<T> {
const fetcherKey = React.useMemo(() => {
return key || createFetcherSymbol
}, [key || createFetcherSymbol])
const map = useSharedMap()
const useHook = React.useMemo(() => {
if (map.get(fetcherKey)) {
return () => useShared(fetcherKey)
}
return () => {
return useSharedProvider(createFetcherSymbol, { key, ...opts }, deps)
}
}, [])
return useHook() as any
}
const SharedContext = React.createContext<Map<TFetcher<any>, any>>(new Map())
const SharedUpdateContext = React.createContext<Map<TFetcher<any>, Array<() => void>>>(new Map())
export const useSharedMap = () => React.useContext(SharedContext)
export const useSharedUpdateMap = () => React.useContext(SharedUpdateContext)
export const SharedProvider: React.FC<{
_internal?: {
valuesMap: Map<any, any>
updateMap: Map<any, any>
}
}> = React.memo(({ children, _internal }) => {
const [map] = React.useState(() => _internal?.valuesMap || new Map())
const [updateMap] = React.useState(() => _internal?.updateMap || new Map())
return (
<SharedContext.Provider value={map}>
<SharedUpdateContext.Provider value={updateMap}>{children}</SharedUpdateContext.Provider>
</SharedContext.Provider>
)
})