Skip to content

Commit cb1e4e7

Browse files
authored
refactor(pagination): data abstraction via hooks (#2988)
* refactor: data abstraction via hooks * fix: review
1 parent 076808e commit cb1e4e7

File tree

3 files changed

+202
-130
lines changed

3 files changed

+202
-130
lines changed

src/hooks/use-pagination.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
interface PaginationOptions {
2+
// 当前页码, 从 1 开始
3+
current: number
4+
// 数据总条数
5+
total: number
6+
// 每页显示的条目数
7+
itemsPerPage: number
8+
// 指示器条目数量
9+
displayCount: number
10+
// 省略符号
11+
ellipse: boolean
12+
}
13+
14+
export type PaginationNode = {
15+
number: number
16+
text: string
17+
selected?: boolean
18+
}
19+
20+
type PaginationResult = [
21+
// 指示器
22+
PaginationNodes: Array<PaginationNode>,
23+
// 指示器的总数量
24+
PaginationNodesCount: number,
25+
]
26+
27+
const defaultPaginationOptions: Partial<PaginationOptions> = {
28+
current: 0,
29+
itemsPerPage: 10,
30+
displayCount: 5,
31+
ellipse: false,
32+
}
33+
34+
function human2Machine(number: number) {
35+
return --number
36+
}
37+
38+
function calculateNodes(options: PaginationOptions, nodesCount: number) {
39+
// 分页器内部的索引从 0 开始,用户使用的索引从 1 开始
40+
const halfIndex = Math.floor(options.displayCount / 2)
41+
const buttonsCountIndex = human2Machine(nodesCount)
42+
const displayCountIndex = human2Machine(options.displayCount)
43+
const currentIndex = human2Machine(options.current)
44+
let start
45+
let end
46+
if (buttonsCountIndex <= displayCountIndex) {
47+
start = 0
48+
end = buttonsCountIndex
49+
} else {
50+
start = Math.max(0, currentIndex - halfIndex)
51+
end = Math.min(buttonsCountIndex, currentIndex + halfIndex)
52+
if (end - start < displayCountIndex) {
53+
if (start === 0) {
54+
end = Math.min(start + displayCountIndex, buttonsCountIndex)
55+
} else if (end === buttonsCountIndex) {
56+
start = Math.max(end - displayCountIndex, 1)
57+
}
58+
} else if (end - start > displayCountIndex) {
59+
end = start + displayCountIndex
60+
}
61+
}
62+
63+
const buttons = []
64+
for (let i = start; i <= end; i++) {
65+
const humanPageNumber = i + 1
66+
buttons.push({
67+
number: humanPageNumber,
68+
text: humanPageNumber.toString(),
69+
selected: options.current === humanPageNumber,
70+
})
71+
}
72+
73+
return addEllipses(buttons, {
74+
nodesCount,
75+
ellipse: options.ellipse,
76+
displayCount: options.displayCount,
77+
})
78+
}
79+
80+
function addEllipses(
81+
nodes: Array<PaginationNode>,
82+
{
83+
displayCount,
84+
nodesCount,
85+
ellipse,
86+
}: { displayCount: number; nodesCount: number; ellipse: boolean }
87+
) {
88+
if (nodesCount <= displayCount || !ellipse) return nodes
89+
const start = nodes[0]
90+
const end = nodes[nodes.length - 1]
91+
92+
const leftEllipse = start.number > 1
93+
const rightEllipse = end.number < nodesCount
94+
if (leftEllipse) {
95+
nodes.unshift({ number: start.number - 1, text: '...' })
96+
}
97+
if (rightEllipse) {
98+
nodes.push({ number: end.number + 1, text: '...' })
99+
}
100+
return nodes
101+
}
102+
103+
export const usePagination = (options: PaginationOptions): PaginationResult => {
104+
const mergedOptions = {
105+
...defaultPaginationOptions,
106+
...options,
107+
}
108+
const { total, itemsPerPage } = mergedOptions
109+
const nodesCount = Math.ceil((total || 0) / itemsPerPage) || 1
110+
111+
return [calculateNodes(mergedOptions, nodesCount), nodesCount]
112+
}

src/packages/pagination/pagination.taro.tsx

Lines changed: 46 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import React, { FunctionComponent, useMemo } from 'react'
1+
import React, { FunctionComponent } from 'react'
22
import classNames from 'classnames'
33
import { View } from '@tarojs/components'
44
import { useConfig } from '@/packages/configprovider/index.taro'
55
import { usePropsValue } from '@/hooks/use-props-value'
66
import { ComponentDefaults } from '@/utils/typings'
77
import addColorForHarmony from '@/utils/add-color-for-harmony'
8-
import { WebPaginationProps } from '@/types'
8+
import { PaginationNode, usePagination } from '@/hooks/use-pagination'
9+
import { TaroPaginationProps } from '@/types'
910

1011
const defaultProps = {
1112
...ComponentDefaults,
@@ -17,9 +18,9 @@ const defaultProps = {
1718
pageSize: 10,
1819
itemSize: 5,
1920
ellipse: false,
20-
} as WebPaginationProps
21+
} as TaroPaginationProps
2122
export const Pagination: FunctionComponent<
22-
Partial<WebPaginationProps> &
23+
Partial<TaroPaginationProps> &
2324
Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>
2425
> = (props) => {
2526
const { locale } = useConfig()
@@ -44,70 +45,50 @@ export const Pagination: FunctionComponent<
4445
}
4546

4647
const classPrefix = 'nut-pagination'
47-
const [currentPage, setCurrentPage] = usePropsValue<number>({
48+
const [current, setCurrent] = usePropsValue<number>({
4849
value,
4950
defaultValue,
5051
finalValue: 1,
5152
onChange,
5253
})
5354

54-
// (total + pageSize) => pageCount 计算页面的数量
55-
const pageCount = useMemo(() => {
56-
const num = Math.ceil(total / pageSize)
57-
return Number.isNaN(num) ? 1 : Math.max(1, num)
58-
}, [total, pageSize])
59-
60-
// (currentPage + itemSize + pageCount) => pages 显示的 item 列表
61-
const pages = useMemo(() => {
62-
const items = [] as Array<any>
63-
let startPage = 1
64-
let endPage = pageCount
65-
const partialShow = pageCount > itemSize
66-
if (partialShow) {
67-
// 选中的 page 放在中间位置
68-
startPage = Math.max(currentPage - Math.floor(itemSize / 2), 1)
69-
endPage = startPage + itemSize - 1
70-
if (endPage > pageCount) {
71-
endPage = pageCount
72-
startPage = endPage - itemSize + 1
73-
}
74-
}
75-
// 遍历生成数组
76-
for (let i = startPage; i <= endPage; i++) {
77-
items.push({ number: i, text: i })
78-
}
79-
// 判断是否有折叠
80-
if (partialShow && itemSize > 0 && ellipse) {
81-
if (startPage > 1) {
82-
items.unshift({ number: startPage - 1, text: '...' })
83-
}
84-
if (endPage < pageCount) {
85-
items.push({ number: endPage + 1, text: '...' })
86-
}
87-
}
88-
return items
89-
}, [currentPage, itemSize, pageCount])
55+
const [pages, pageCount] = usePagination({
56+
total,
57+
ellipse,
58+
current,
59+
displayCount: itemSize,
60+
itemsPerPage: pageSize,
61+
})
9062

91-
const handleSelectPage = (curPage: number) => {
92-
if (curPage > pageCount || curPage < 1) return
93-
setCurrentPage(curPage)
63+
const handleClick = (item: PaginationNode) => {
64+
if (item.selected) return
65+
if (item.number > pageCount || item.number < 1) return
66+
setCurrent(item.number)
67+
}
68+
const prevPage = () => {
69+
const prev = current - 1
70+
prev >= 1 && setCurrent(prev)
71+
}
72+
const nextPage = () => {
73+
const next = current + 1
74+
next <= pageCount && setCurrent(next)
9475
}
9576

9677
return (
9778
<View className={classNames(classPrefix, className)} style={style}>
9879
{(mode === 'multi' || mode === 'simple') && (
9980
<>
10081
<View
101-
className={classNames(
102-
`${classPrefix}-prev`,
103-
mode === 'multi' ? '' : `${classPrefix}-simple-border`,
104-
currentPage === 1 ? `${classPrefix}-prev-disabled` : ''
105-
)}
106-
onClick={(e) => handleSelectPage(currentPage - 1)}
82+
className={classNames({
83+
[`${classPrefix}-prev`]: true,
84+
[`${classPrefix}-simple-border`]: mode !== 'multi',
85+
[`${classPrefix}-prev-disabled`]: current === 1,
86+
})}
87+
onClick={() => prevPage()}
10788
>
10889
{addColorForHarmony(
10990
prev || locale.pagination.prev,
110-
currentPage === 1 ? '#c2c4cc' : '#ff0f23'
91+
current === 1 ? '#c2c4cc' : '#ff0f23'
11192
)}
11293
</View>
11394
{mode === 'multi' && (
@@ -116,16 +97,15 @@ export const Pagination: FunctionComponent<
11697
return (
11798
<View
11899
key={`${index}pagination`}
119-
className={classNames(`${classPrefix}-item`, {
120-
[`${classPrefix}-item-active`]:
121-
item.number === currentPage,
100+
className={classNames({
101+
[`${classPrefix}-item`]: true,
102+
[`${classPrefix}-item-active`]: item.selected,
122103
})}
123-
onClick={(e) => {
124-
item.number !== currentPage &&
125-
handleSelectPage(item.number)
104+
onClick={() => {
105+
handleClick(item)
126106
}}
127107
>
128-
{itemRender ? itemRender(item, currentPage) : item.text}
108+
{itemRender ? itemRender(item, current) : item.text}
129109
</View>
130110
)
131111
})}
@@ -134,27 +114,27 @@ export const Pagination: FunctionComponent<
134114
{mode === 'simple' && (
135115
<View className={`${classPrefix}-contain`}>
136116
<View className={`${classPrefix}-simple`}>
137-
{`${currentPage}/${pageCount}`}
117+
{`${current}/${pageCount}`}
138118
</View>
139119
</View>
140120
)}
141121
<View
142-
className={classNames(
143-
`${classPrefix}-next`,
144-
currentPage >= pageCount ? `${classPrefix}-next-disabled` : ''
145-
)}
146-
onClick={(e) => handleSelectPage(currentPage + 1)}
122+
className={classNames({
123+
[`${classPrefix}-next`]: true,
124+
[`${classPrefix}-next-disabled`]: current >= pageCount,
125+
})}
126+
onClick={() => nextPage()}
147127
>
148128
{addColorForHarmony(
149129
next || locale.pagination.next,
150-
currentPage >= pageCount ? '#c2c4cc' : '#ff0f23'
130+
current >= pageCount ? '#c2c4cc' : '#ff0f23'
151131
)}
152132
</View>
153133
</>
154134
)}
155135
{mode === 'lite' && (
156136
<View className={`${classPrefix}-lite`}>
157-
<View className={`${classPrefix}-lite-active`}>{currentPage}</View>
137+
<View className={`${classPrefix}-lite-active`}>{current}</View>
158138
<View className={`${classPrefix}-lite-spliterator`}>/</View>
159139
<View className={`${classPrefix}-lite-default`}>{pageCount}</View>
160140
</View>

0 commit comments

Comments
 (0)