1- import { useCallback , useEffect , useRef , useState , ReactNode } from 'react' ;
1+ import { useEffect , useRef , useState , ReactNode , useCallback } from 'react' ;
22import { LinkedListResponse , LinkedPaginationProps } from '../common/fetch/type' ;
33import { Box , BoxProps } from '@chakra-ui/react' ;
44import { useTranslation } from 'next-i18next' ;
55import { useScroll , useMemoizedFn , useDebounceEffect } from 'ahooks' ;
66import MyBox from '../components/common/MyBox' ;
77import { useRequest2 } from './useRequest' ;
8+ import { delay } from '../../global/common/system/utils' ;
89
910const threshold = 100 ;
1011
@@ -14,92 +15,95 @@ export function useLinkedScroll<
1415> (
1516 api : ( data : TParams ) => Promise < TData > ,
1617 {
17- refreshDeps = [ ] ,
1818 pageSize = 15 ,
1919 params = { } ,
20- initialId,
21- initialIndex,
22- canLoadData = false
20+ currentData
2321 } : {
24- refreshDeps ?: any [ ] ;
2522 pageSize ?: number ;
2623 params ?: Record < string , any > ;
27- initialId ?: string ;
28- initialIndex ?: number ;
29- canLoadData ?: boolean ;
24+ currentData ?: { id : string ; index : number } ;
3025 }
3126) {
3227 const { t } = useTranslation ( ) ;
3328 const [ dataList , setDataList ] = useState < TData [ 'list' ] > ( [ ] ) ;
3429 const [ hasMorePrev , setHasMorePrev ] = useState ( true ) ;
3530 const [ hasMoreNext , setHasMoreNext ] = useState ( true ) ;
36- const [ initialLoadDone , setInitialLoadDone ] = useState ( false ) ;
37- const hasScrolledToInitial = useRef ( false ) ;
3831
3932 const anchorRef = useRef ( {
4033 top : null as { _id : string ; index : number } | null ,
4134 bottom : null as { _id : string ; index : number } | null
4235 } ) ;
43-
4436 const containerRef = useRef < HTMLDivElement > ( null ) ;
4537 const itemRefs = useRef < ( HTMLElement | null ) [ ] > ( [ ] ) ;
4638
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 ) ;
5249 }
50+ return ;
5351 }
54- ) ;
5552
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+ }
6775
6876 const response = await callApi ( {
69- initialId : id ,
70- initialIndex : index ,
77+ initialId : currentData . id ,
78+ initialIndex : currentData . index ,
7179 pageSize,
72- isInitialLoad,
7380 ...params
7481 } as TParams ) ;
7582
76- if ( ! response ) return null ;
77-
7883 setHasMorePrev ( response . hasMorePrev ) ;
7984 setHasMoreNext ( response . hasMoreNext ) ;
85+
86+ scroolSign . current = scrollWhenFinish ;
8087 setDataList ( response . list ) ;
8188
8289 if ( response . list . length > 0 ) {
8390 anchorRef . current . top = response . list [ 0 ] ;
8491 anchorRef . current . bottom = response . list [ response . list . length - 1 ] ;
8592 }
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 ;
9893 } ,
99- [ callApi , params , dataList , hasMorePrev , hasMoreNext , isLoading ]
94+ {
95+ refreshDeps : [ currentData ] ,
96+ manual : false
97+ }
10098 ) ;
99+ useEffect ( ( ) => {
100+ if ( scroolSign . current && currentData ) {
101+ scroolSign . current = false ;
102+ scrollToItem ( currentData . id ) ;
103+ }
104+ } , [ dataList ] ) ;
101105
102- const loadPrevData = useCallback (
106+ const { runAsync : loadPrevData , loading : prevLoading } = useRequest2 (
103107 async ( scrollRef = containerRef ) => {
104108 if ( ! anchorRef . current . top || ! hasMorePrev || isLoading ) return ;
105109
@@ -132,10 +136,12 @@ export function useLinkedScroll<
132136
133137 return response ;
134138 } ,
135- [ callApi , hasMorePrev , isLoading , params , pageSize ]
139+ {
140+ refreshDeps : [ hasMorePrev , isLoading , params , pageSize ]
141+ }
136142 ) ;
137143
138- const loadNextData = useCallback (
144+ const { runAsync : loadNextData , loading : nextLoading } = useRequest2 (
139145 async ( scrollRef = containerRef ) => {
140146 if ( ! anchorRef . current . bottom || ! hasMoreNext || isLoading ) return ;
141147
@@ -165,85 +171,17 @@ export function useLinkedScroll<
165171
166172 return response ;
167173 } ,
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 ]
236176 }
237- } , [ initialLoadDone , ... refreshDeps ] ) ;
177+ ) ;
238178
239179 const ScrollData = useMemoizedFn (
240180 ( {
241181 children,
242182 ScrollContainerRef,
243- isLoading : externalLoading ,
244183 ...props
245184 } : {
246- isLoading ?: boolean ;
247185 children : ReactNode ;
248186 ScrollContainerRef ?: React . RefObject < HTMLDivElement > ;
249187 } & BoxProps ) => {
@@ -252,17 +190,17 @@ export function useLinkedScroll<
252190
253191 useDebounceEffect (
254192 ( ) => {
255- if ( ! ref ?. current || isLoading || ! initialLoadDone ) return ;
193+ if ( ! ref ?. current || isLoading ) return ;
256194
257195 const { scrollTop, scrollHeight, clientHeight } = ref . current ;
258196
259197 // 滚动到底部附近,加载更多下方数据
260- if ( scrollTop + clientHeight >= scrollHeight - threshold && hasMoreNext ) {
198+ if ( scrollTop + clientHeight >= scrollHeight - threshold ) {
261199 loadNextData ( ref ) ;
262200 }
263201
264202 // 滚动到顶部附近,加载更多上方数据
265- if ( scrollTop <= threshold && hasMorePrev ) {
203+ if ( scrollTop <= threshold ) {
266204 loadPrevData ( ref ) ;
267205 }
268206 } ,
@@ -271,20 +209,14 @@ export function useLinkedScroll<
271209 ) ;
272210
273211 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 && (
282214 < Box mt = { 2 } fontSize = { 'xs' } color = { 'blackAlpha.500' } textAlign = { 'center' } >
283215 { t ( 'common:common.is_requesting' ) }
284216 </ Box >
285217 ) }
286218 { children }
287- { hasMoreNext && isLoading && initialLoadDone && (
219+ { hasMoreNext && nextLoading && (
288220 < Box mt = { 2 } fontSize = { 'xs' } color = { 'blackAlpha.500' } textAlign = { 'center' } >
289221 { t ( 'common:common.is_requesting' ) }
290222 </ Box >
@@ -298,7 +230,7 @@ export function useLinkedScroll<
298230 dataList,
299231 setDataList,
300232 isLoading,
301- loadData ,
233+ loadInitData ,
302234 ScrollData,
303235 itemRefs,
304236 scrollToItem
0 commit comments