1
- import { useCallback , useEffect , useRef , useState , ReactNode } from 'react' ;
1
+ import { useEffect , useRef , useState , ReactNode , useCallback } from 'react' ;
2
2
import { LinkedListResponse , LinkedPaginationProps } from '../common/fetch/type' ;
3
3
import { Box , BoxProps } from '@chakra-ui/react' ;
4
4
import { useTranslation } from 'next-i18next' ;
5
5
import { useScroll , useMemoizedFn , useDebounceEffect } from 'ahooks' ;
6
6
import MyBox from '../components/common/MyBox' ;
7
7
import { useRequest2 } from './useRequest' ;
8
+ import { delay } from '../../global/common/system/utils' ;
8
9
9
10
const threshold = 100 ;
10
11
@@ -14,92 +15,95 @@ export function useLinkedScroll<
14
15
> (
15
16
api : ( data : TParams ) => Promise < TData > ,
16
17
{
17
- refreshDeps = [ ] ,
18
18
pageSize = 15 ,
19
19
params = { } ,
20
- initialId,
21
- initialIndex,
22
- canLoadData = false
20
+ currentData
23
21
} : {
24
- refreshDeps ?: any [ ] ;
25
22
pageSize ?: number ;
26
23
params ?: Record < string , any > ;
27
- initialId ?: string ;
28
- initialIndex ?: number ;
29
- canLoadData ?: boolean ;
24
+ currentData ?: { id : string ; index : number } ;
30
25
}
31
26
) {
32
27
const { t } = useTranslation ( ) ;
33
28
const [ dataList , setDataList ] = useState < TData [ 'list' ] > ( [ ] ) ;
34
29
const [ hasMorePrev , setHasMorePrev ] = useState ( true ) ;
35
30
const [ hasMoreNext , setHasMoreNext ] = useState ( true ) ;
36
- const [ initialLoadDone , setInitialLoadDone ] = useState ( false ) ;
37
- const hasScrolledToInitial = useRef ( false ) ;
38
31
39
32
const anchorRef = useRef ( {
40
33
top : null as { _id : string ; index : number } | null ,
41
34
bottom : null as { _id : string ; index : number } | null
42
35
} ) ;
43
-
44
36
const containerRef = useRef < HTMLDivElement > ( null ) ;
45
37
const itemRefs = useRef < ( HTMLElement | null ) [ ] > ( [ ] ) ;
46
38
47
- const { runAsync : callApi , loading : isLoading } = useRequest2 (
48
- async ( apiParams : TParams ) => await api ( apiParams ) ,
49
- {
50
- onError : ( error ) => {
51
- return Promise . reject ( error ) ;
39
+ const scrollToItem = async ( id : string , retry = 3 ) => {
40
+ const itemIndex = dataList . findIndex ( ( item ) => item . _id === id ) ;
41
+ if ( itemIndex === - 1 ) return ;
42
+
43
+ const element = itemRefs . current [ itemIndex ] ;
44
+
45
+ if ( ! element || ! containerRef . current ) {
46
+ if ( retry > 0 ) {
47
+ await delay ( 500 ) ;
48
+ return scrollToItem ( id , retry - 1 ) ;
52
49
}
50
+ return ;
53
51
}
54
- ) ;
55
52
56
- const loadData = useCallback (
57
- async ( {
58
- id,
59
- index,
60
- isInitialLoad = false
61
- } : {
62
- id : string ;
63
- index : number ;
64
- isInitialLoad ?: boolean ;
65
- } ) => {
66
- if ( isLoading ) return null ;
53
+ const elementRect = element . getBoundingClientRect ( ) ;
54
+ const containerRect = containerRef . current . getBoundingClientRect ( ) ;
55
+
56
+ const scrollTop = containerRef . current . scrollTop + elementRect . top - containerRect . top ;
57
+
58
+ containerRef . current . scrollTo ( {
59
+ top : scrollTop
60
+ } ) ;
61
+ } ;
62
+
63
+ const { runAsync : callApi , loading : isLoading } = useRequest2 ( api ) ;
64
+
65
+ let scroolSign = useRef ( false ) ;
66
+ const { runAsync : loadInitData } = useRequest2 (
67
+ async ( scrollWhenFinish = true ) => {
68
+ if ( ! currentData || isLoading ) return ;
69
+
70
+ const item = dataList . find ( ( item ) => item . _id === currentData . id ) ;
71
+ if ( item ) {
72
+ scrollToItem ( item . _id ) ;
73
+ return ;
74
+ }
67
75
68
76
const response = await callApi ( {
69
- initialId : id ,
70
- initialIndex : index ,
77
+ initialId : currentData . id ,
78
+ initialIndex : currentData . index ,
71
79
pageSize,
72
- isInitialLoad,
73
80
...params
74
81
} as TParams ) ;
75
82
76
- if ( ! response ) return null ;
77
-
78
83
setHasMorePrev ( response . hasMorePrev ) ;
79
84
setHasMoreNext ( response . hasMoreNext ) ;
85
+
86
+ scroolSign . current = scrollWhenFinish ;
80
87
setDataList ( response . list ) ;
81
88
82
89
if ( response . list . length > 0 ) {
83
90
anchorRef . current . top = response . list [ 0 ] ;
84
91
anchorRef . current . bottom = response . list [ response . list . length - 1 ] ;
85
92
}
86
-
87
- setInitialLoadDone ( true ) ;
88
-
89
- const scrollIndex = response . list . findIndex ( ( item ) => item . _id === id ) ;
90
-
91
- if ( scrollIndex !== - 1 && itemRefs . current ?. [ scrollIndex ] ) {
92
- setTimeout ( ( ) => {
93
- scrollToItem ( scrollIndex ) ;
94
- } , 100 ) ;
95
- }
96
-
97
- return response ;
98
93
} ,
99
- [ callApi , params , dataList , hasMorePrev , hasMoreNext , isLoading ]
94
+ {
95
+ refreshDeps : [ currentData ] ,
96
+ manual : false
97
+ }
100
98
) ;
99
+ useEffect ( ( ) => {
100
+ if ( scroolSign . current && currentData ) {
101
+ scroolSign . current = false ;
102
+ scrollToItem ( currentData . id ) ;
103
+ }
104
+ } , [ dataList ] ) ;
101
105
102
- const loadPrevData = useCallback (
106
+ const { runAsync : loadPrevData , loading : prevLoading } = useRequest2 (
103
107
async ( scrollRef = containerRef ) => {
104
108
if ( ! anchorRef . current . top || ! hasMorePrev || isLoading ) return ;
105
109
@@ -132,10 +136,12 @@ export function useLinkedScroll<
132
136
133
137
return response ;
134
138
} ,
135
- [ callApi , hasMorePrev , isLoading , params , pageSize ]
139
+ {
140
+ refreshDeps : [ hasMorePrev , isLoading , params , pageSize ]
141
+ }
136
142
) ;
137
143
138
- const loadNextData = useCallback (
144
+ const { runAsync : loadNextData , loading : nextLoading } = useRequest2 (
139
145
async ( scrollRef = containerRef ) => {
140
146
if ( ! anchorRef . current . bottom || ! hasMoreNext || isLoading ) return ;
141
147
@@ -165,85 +171,17 @@ export function useLinkedScroll<
165
171
166
172
return response ;
167
173
} ,
168
- [ callApi , hasMoreNext , isLoading , params , pageSize ]
169
- ) ;
170
-
171
- const scrollToItem = useCallback (
172
- ( itemIndex : number ) => {
173
- if ( itemIndex >= 0 && itemIndex < dataList . length && itemRefs . current ?. [ itemIndex ] ) {
174
- try {
175
- const element = itemRefs . current [ itemIndex ] ;
176
- if ( ! element ) {
177
- return false ;
178
- }
179
-
180
- setTimeout ( ( ) => {
181
- if ( element && containerRef . current ) {
182
- const elementRect = element . getBoundingClientRect ( ) ;
183
- const containerRect = containerRef . current . getBoundingClientRect ( ) ;
184
-
185
- const relativeTop = elementRect . top - containerRect . top ;
186
-
187
- const scrollTop =
188
- containerRef . current . scrollTop +
189
- relativeTop -
190
- containerRect . height / 2 +
191
- elementRect . height / 2 ;
192
-
193
- containerRef . current . scrollTo ( {
194
- top : scrollTop ,
195
- behavior : 'smooth'
196
- } ) ;
197
- }
198
- } , 50 ) ;
199
-
200
- return true ;
201
- } catch ( error ) {
202
- console . error ( 'Error scrolling to item:' , error ) ;
203
- return false ;
204
- }
205
- }
206
- return false ;
207
- } ,
208
- [ dataList . length ]
209
- ) ;
210
-
211
- // 初始加载
212
- useEffect ( ( ) => {
213
- if ( canLoadData ) {
214
- setInitialLoadDone ( false ) ;
215
- hasScrolledToInitial . current = false ;
216
-
217
- loadData ( {
218
- id : initialId || '' ,
219
- index : initialIndex || 0 ,
220
- isInitialLoad : true
221
- } ) ;
222
- }
223
- } , [ canLoadData , ...refreshDeps ] ) ;
224
-
225
- // 监听初始加载完成,执行初始滚动
226
- useEffect ( ( ) => {
227
- if ( initialLoadDone && dataList . length > 0 && ! hasScrolledToInitial . current ) {
228
- const foundIndex = dataList . findIndex ( ( item ) => item . _id === initialId ) ;
229
-
230
- if ( foundIndex >= 0 ) {
231
- hasScrolledToInitial . current = true ;
232
- setTimeout ( ( ) => {
233
- scrollToItem ( foundIndex ) ;
234
- } , 200 ) ;
235
- }
174
+ {
175
+ refreshDeps : [ hasMoreNext , isLoading , params , pageSize ]
236
176
}
237
- } , [ initialLoadDone , ... refreshDeps ] ) ;
177
+ ) ;
238
178
239
179
const ScrollData = useMemoizedFn (
240
180
( {
241
181
children,
242
182
ScrollContainerRef,
243
- isLoading : externalLoading ,
244
183
...props
245
184
} : {
246
- isLoading ?: boolean ;
247
185
children : ReactNode ;
248
186
ScrollContainerRef ?: React . RefObject < HTMLDivElement > ;
249
187
} & BoxProps ) => {
@@ -252,17 +190,17 @@ export function useLinkedScroll<
252
190
253
191
useDebounceEffect (
254
192
( ) => {
255
- if ( ! ref ?. current || isLoading || ! initialLoadDone ) return ;
193
+ if ( ! ref ?. current || isLoading ) return ;
256
194
257
195
const { scrollTop, scrollHeight, clientHeight } = ref . current ;
258
196
259
197
// 滚动到底部附近,加载更多下方数据
260
- if ( scrollTop + clientHeight >= scrollHeight - threshold && hasMoreNext ) {
198
+ if ( scrollTop + clientHeight >= scrollHeight - threshold ) {
261
199
loadNextData ( ref ) ;
262
200
}
263
201
264
202
// 滚动到顶部附近,加载更多上方数据
265
- if ( scrollTop <= threshold && hasMorePrev ) {
203
+ if ( scrollTop <= threshold ) {
266
204
loadPrevData ( ref ) ;
267
205
}
268
206
} ,
@@ -271,20 +209,14 @@ export function useLinkedScroll<
271
209
) ;
272
210
273
211
return (
274
- < MyBox
275
- ref = { ref }
276
- h = { '100%' }
277
- overflow = { 'auto' }
278
- isLoading = { externalLoading || isLoading }
279
- { ...props }
280
- >
281
- { hasMorePrev && isLoading && initialLoadDone && (
212
+ < MyBox ref = { ref } h = { '100%' } overflow = { 'auto' } isLoading = { isLoading } { ...props } >
213
+ { hasMorePrev && prevLoading && (
282
214
< Box mt = { 2 } fontSize = { 'xs' } color = { 'blackAlpha.500' } textAlign = { 'center' } >
283
215
{ t ( 'common:common.is_requesting' ) }
284
216
</ Box >
285
217
) }
286
218
{ children }
287
- { hasMoreNext && isLoading && initialLoadDone && (
219
+ { hasMoreNext && nextLoading && (
288
220
< Box mt = { 2 } fontSize = { 'xs' } color = { 'blackAlpha.500' } textAlign = { 'center' } >
289
221
{ t ( 'common:common.is_requesting' ) }
290
222
</ Box >
@@ -298,7 +230,7 @@ export function useLinkedScroll<
298
230
dataList,
299
231
setDataList,
300
232
isLoading,
301
- loadData ,
233
+ loadInitData ,
302
234
ScrollData,
303
235
itemRefs,
304
236
scrollToItem
0 commit comments