在data.d.ts中定义父组件所需要传的值
import { ReactNode } from 'react'
type PullStatus = 'pulling' | 'canRelease' | 'refreshing' | 'complete'
interface InfiniteProps {
isNavBar?: boolean // 是否有头部
TopNoData?: number // 无数据的时候图片距离顶部的距离
InitialNoData?: {
img: string // 初始进入无数据的图片
content: ReactNode | string // 内容区域 可以是文本或者reactNode
} // 初始进入无数据传入的
searchNoData?: {
img: string // 搜索无数据的img
content: ReactNode | string // 内容区域 可以是文本或者reactNode
} // 搜索无数据的
scrollMap: {
isNull?: boolean
lastPage: boolean | undefined
isLoading: boolean | undefined
setSize: (num: number) => void
size: number | undefined
refresh: (obj?: object) => void
} // 传入的滚动组件所需要的值, 这是一个map调用request的时候返回的scrollMap
skeletionData?: string // 传入的列表骨架屏图片
TopDistance?: number // 滚动组件距离顶部的距离
isBottomPadding?: boolean //是否给底部没有数据提示加padding
pageSize?: number // 接受传入的每页的数据量
data: Array<[]> // 接收传入的每页的数据
height?: number | string // 滚动组件高度 可以传入可以不传
backgroundColor?: string | '#fff' // 传入的背景颜色
isScrollRecovery?: boolean // 是否执行滚动位置恢复
childrenContent?: ReactNode // 内容
// eslint-disable-next-line @typescript-eslint/ban-types
handlerRefresh?: () => void // 触发刷新时的处理函数(可以不用)
pullingText?: ReactNode // 下拉的提示文案
canReleaseText?: ReactNode // 释放的提示文案
refreshingText?: ReactNode // 刷新时的提示文案
completeText?: ReactNode // 完成时的提示文案
completeDelay?: number // 完成后延迟消失的时间,单位为 ms
headHeight?: number // 头部提示内容区的高度,单位为 px
threshold?: number // 触发刷新需要下拉多少距离,单位为 px
dropText?: (status: PullStatus) => ReactNode // 根据下拉状态,自定义下拉提示文案
}
export { InfiniteProps }
在index.tsx中实现此功能
import React, { useRef, useEffect } from 'react'
import { Main } from './styled'
import { InfiniteProps } from './data'
import { Loading, PullToRefresh, Image } from 'antd-mobile'
import { useLocation } from 'react-router-dom'
import { useInViewport, useSafeState, useUpdateEffect } from 'ahooks'
import { chunk } from 'lodash-es'
// 列表进入详情,从详情返回到列表保留滚动条位置
import { useScrollToRestore } from '@/hooks/scrollToRestore'
import EmptyState from '../EmptyState' // 空状态组件引入
const InfiniteScroll: React.FC<InfiniteProps> = (props) => {
const {
height = 0,
data,
pageSize = 15,
backgroundColor='#ffffff',
isNavBar = false,
scrollMap,
skeletionData = '',
TopDistance,
isBottomPadding= false,
childrenContent,
isScrollRecovery= true,
handlerRefresh,
pullingText = '下拉刷新',
canReleaseText = '释放立即刷新',
refreshingText = '加载中……',
completeText = '刷新成功',
completeDelay = 500,
headHeight = 40,
threshold = 60,
TopNoData, // 无数据的时候图片距离顶部的距离
InitialNoData, // 初始进入无数据的状态
searchNoData, // 搜索无数据的状态
} = props
const {
refresh,
lastPage,
size = 0,
isLoading,
setSize,
isNull,
} = scrollMap
// 判断是否是显示加载中
const [isDisplayLoad, setIsDisplayLoad] = useSafeState(false)
// 判断是否是最后一条数据
const [isLastData, setIsLastData] = useSafeState(false)
// 加载中的ref
const loadingRef = useRef<HTMLDivElement>(null)
// 监听加载中是否显示到可视窗口
const [inViewport] = useInViewport(loadingRef)
// 获取当前路由地址
const { pathname } = useLocation()
// 是否显示出来加载中 如果是true 则加载下一页
useUpdateEffect(() => {
if (inViewport && setSize) {
setSize(size + 1)
}
}, [inViewport])
// 设置是否显示加载中以及判断是否是最后一页
useEffect(() => {
const displayLoad = data?.length === 0
const initData = chunk(data, pageSize)
const lastData = displayLoad || (initData && initData[initData.length - 1]?.length < pageSize)
setIsLastData(!dataLastPage || lastData )
setIsDisplayLoad(displayLoad)
}, [data, dataLastPage])
// 调用滚动位置恢复hooks
useScrollToRestore(isScroll, 'roolDom')
// 底部渲染
const handlerBottom = () => {
let content = ''
if (isDisplayLoad) {
content = ''
} else if (!isLastData && isDisplayLoad) {
content = ''
} else {
content = '没有更多了~'
}
return content
}
return (
<>
<InfiniteMain
isNavBar={isNavBar || import.meta.env.MODE.includes('lcoal') }
TopDistance={TopDistance}
isAddPadding={isBottomPadding}
dataNall={data.length === 0}
className="roolDom"
height={height}
backgroundColor={backgroundColor}
>
{!isLoading ? (
<>
{/* 初始进来页面无数据 */}
{InitialNoData && !isNull && data.length === 0 && (
<EmptyState
isNavBar={isNavBar}
defaultImg={InitialNoData?.img}
top={top || 0}
title={InitialNoData?.content|| ''}
/>
)}
{/* 页面有数据 搜索无数据 */}
{searchNoData && isNull && data.length === 0 && (
<EmptyState
isNavBar={isNavBar}
defaultImg={searchNoData?.img}
top={TopNoData || 0}
title={searchNoData?.content || ''}
/>
)}
<PullToRefresh
onRefresh={async () => {
await (handlerRefresh && handlerRefresh())
refresh && refresh()
sessionStorage.removeItem(pathname)
}}
pullingText={pullingText}
canReleaseText={canReleaseText}
refreshingText={refreshingText}
completeText={completeText}
completeDelay={completeDelay}
headHeight={headHeight}
threshold={threshold}
>
<div className="mainBox">{childrenContent}</div>
{!isLastData ? (
<div ref={isLoadingRef} className="base_load">
<div>加载中</div>
<Loading />
</div>
) : (
handlerBottom() && <div className="botm">{handlerBottom()}</div>
)}
</PullToRefresh>
</>
) : (
skeletionData && <Image src={skeletionData} />
)}
</InfiniteMain>
</>
)
}
export default React.memo(InfiniteScroll)
在styled.ts中实现样式
import styled from 'ns-styled'
const InfiniteMain = styled.div<{
TopDistance?: number
isAddPadding?: boolean
height: number | string
backgroundColor?: string | '#fff'
dataNall: boolean
isNavBar: boolean
}>`
background: ${(p) => p.dataNall && '#f5f5f5'};
&::-webkit-scrollbar {
display: none;
}
.adm-pull-to-refresh-head-content {
font-size: 14px;
}
.base_load {
height: 36px;
background: #f7f7f7;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
div {
display: inline-block;
}
}
.botm {
font-size: 12px;
color: #8c8c8c;
text-align: center;
height: 24px;
line-height: 24px;
margin-top: 32px;
margin-bottom: ${(props) => props.isAddPadding && '56px'};
}
height: ${(p) =>
p.TopDistance
? `calc(100vh - ${(p.TopDistance || 0) + (p.isNavBar ? 52 : 0)}px)`
: Number(p.height)
? `${p.height}px`
: p.height};
overflow: auto;
-webkit-overflow-scrolling: touch;
background: ${(props) => props.backgroundColor};
`
export { InfiniteMain }