先看最終代碼:
import React, { useState, useMemo, useEffect } from 'react'
interface PageAbout {
current: number
pageSize: number
}
// 配合antd的table旱物,生產(chǎn)table所需函數(shù)方法及state
export function useTableData<
TableParams extends SearchData & PageAbout = any,
SearchData extends object = any,
Datasource = any
>({
tableParamsInit = { pageSize: 10, current: 1 } as any,
searchDataInit = {},
pullData = () => {
// console.log('pullData')
},
getTotal = () => {
return 1
},
}: {
tableParamsInit?: TableParams
searchDataInit?: SearchData | {}
// 調(diào)用接口的方法遥缕,接受setDatasource作為參數(shù), 在獲取數(shù)據(jù)后,通過其將數(shù)據(jù)注回useTableData
pullData: (
params: TableParams,
setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
) => void
// 從結(jié)果中獲取數(shù)據(jù)總數(shù)
getTotal?: (res: any) => number
}) {
const [datasource, setDatasource] = useState<Datasource[] | []>([])
const [tableParams, setTableParams] = useState<TableParams>(tableParamsInit)
const [searchData, setSearchData] = useState<SearchData | {}>(searchDataInit)
const [total, setTotal] = useState(1)
//表格接口依賴變動(dòng)宵呛,調(diào)用接口单匣,拉取新的數(shù)據(jù)
useEffect(() => {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
}, [tableParams])
// 搜索時(shí),將搜索state set至表格接口依賴宝穗,表格接口依賴變化户秤,自動(dòng)觸發(fā)接口調(diào)用
const handleSearch = () => {
setTableParams((s) => ({ ...s, current: 1, ...searchData }))
}
// 重置,將搜索依賴及表格接口依賴重置為init
const handleReset = () => {
setTableParams(tableParamsInit)
setSearchData(searchDataInit)
}
// 頁碼變更
const handlePageChange = (v: number) => {
setTableParams((s) => ({ ...s, current: v }))
}
// 執(zhí)行刪除操作逮矛,數(shù)據(jù)源出現(xiàn)變動(dòng)鸡号,如果刪除的時(shí)當(dāng)前頁最后一條數(shù)據(jù),則退回有數(shù)據(jù)的頁數(shù)须鼎,當(dāng)前頁為第一頁則只刷新
// count刪除的數(shù)據(jù)量
const handleAfterDel = (count?: number) => {
// 第一頁時(shí)鲸伴,直接獲取新數(shù)據(jù)
if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
} else {
// 被刪除的頁數(shù),用于計(jì)算需要返回的頁數(shù)
const deletedPage = Math.floor(
Number(
(
((count ?? 1) - (datasource?.length ?? 0)) /
tableParams.pageSize
).toFixed(0)
)
)
setTableParams((s) => ({
...s,
current: s.current - deletedPage > 1 ? s.current - deletedPage : 1,
}))
}
}
return {
// 數(shù)據(jù)總數(shù)
total,
setTotal,
// 表格數(shù)據(jù)源
datasource,
setDatasource,
// 表格接口依賴
tableParams,
setTableParams,
// 搜索依賴state
searchData,
setSearchData,
// 搜索操作
handleSearch,
// 重置操作
handleReset,
// 頁面變化
handlePageChange,
// 刪除數(shù)據(jù)后執(zhí)行,參數(shù)為刪除的條數(shù)
handleAfterDel,
}
}
封裝思路
hooks提供了以往react以往風(fēng)格不方便實(shí)現(xiàn)的邏輯復(fù)用能力莉兰,筆者使用hooks于生產(chǎn)實(shí)踐中也有近倆月了挑围,寫的后臺(tái)項(xiàng)目中非常多的表格,遂基于antd的table組件糖荒,封裝了配套的可復(fù)用的邏輯杉辙。
-
參數(shù)部分
一般來說,所有表格將會(huì)出現(xiàn)的不同部分需要作為參數(shù)傳入hooks中捶朵。
1. get表格數(shù)據(jù)所需的params參數(shù)初始值:tableParamsInit (默認(rèn)需要翻頁操作)
2. 查詢蜘矢、篩選、搜索等功能所依賴的參數(shù)初始值:searchDataInit
3. 調(diào)用接口综看,獲取表格數(shù)據(jù)的方法
4. 獲取數(shù)據(jù)總數(shù)的方法(如果后臺(tái)的接口格式幾乎一樣品腹,這個(gè)也可以寫死,不用傳入)
{
tableParamsInit = { pageSize: 10, current: 1 } as any,
searchDataInit = {},
pullData = () => {
// console.log('pullData')
},
getTotal = () => {
return 1
},
}: {
tableParamsInit?: TableParams
searchDataInit?: SearchData | {}
// 調(diào)用接口的方法红碑,接受setDatasource作為參數(shù), 在獲取數(shù)據(jù)后舞吭,通過其將數(shù)據(jù)注回useTableData
pullData: (
params: TableParams,
setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
) => void
// 從結(jié)果中獲取數(shù)據(jù)總數(shù)
getTotal?: (res: any) => number
}
-
狀態(tài)state
1.dataSource用來存儲(chǔ)表格的數(shù)據(jù)
2.tableParams是用于發(fā)送接口請(qǐng)求的最終參數(shù)
3.searchData是查詢狀態(tài)的state
const [datasource, setDatasource] = useState<Datasource[] | []>([])
const [tableParams, setTableParams] = useState<TableParams>(tableParamsInit)
const [searchData, setSearchData] = useState<SearchData | {}>(searchDataInit)
-
監(jiān)聽effect
- 監(jiān)聽tableParams即接口參數(shù)的變化泡垃,接口依賴的參數(shù)變了就請(qǐng)求接口獲取新的數(shù)據(jù),這種調(diào)用接口的時(shí)機(jī)是實(shí)際生產(chǎn)當(dāng)中筆者總結(jié)出的邏輯比較清晰易懂不容易出錯(cuò)的一種羡鸥。
- 值得注意的是蔑穴,用于查詢搜索的那些參數(shù),只有當(dāng)用戶點(diǎn)擊查詢或者搜索按鈕時(shí)惧浴,才會(huì)被添加到tableParams中存和,觸發(fā)接口獲取數(shù)據(jù)。如果讀者在生產(chǎn)中衷旅,產(chǎn)品要求搜索內(nèi)容變化就要立即獲取數(shù)據(jù)的話捐腿,則應(yīng)該把搜索所依賴的參數(shù)直接收納于tableParams中,而不應(yīng)當(dāng)再作為searchData中的內(nèi)容柿顶。
useEffect(() => {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
}, [tableParams])
-
方法handler
- handleSearch 這個(gè)方法一般被筆者直接添加到查詢的按鈕上茄袖,通過把searchData的值賦值到tableParams中,改變tableParams嘁锯,觸發(fā)pullData獲取新的數(shù)據(jù)绞佩。
注意:current也被筆者重新賦值為1,是因?yàn)橹砼ィ樵兓蚝Y選后的數(shù)據(jù)一般會(huì)少于總數(shù)據(jù)。這樣的話如果不把頁數(shù)重置為1則會(huì)出現(xiàn)查詢到的結(jié)果不符合預(yù)期的情況胆建。
- handleSearch 這個(gè)方法一般被筆者直接添加到查詢的按鈕上茄袖,通過把searchData的值賦值到tableParams中,改變tableParams嘁锯,觸發(fā)pullData獲取新的數(shù)據(jù)绞佩。
// 搜索時(shí)烤低,將搜索state set至表格接口依賴,表格接口依賴變化笆载,自動(dòng)觸發(fā)接口調(diào)用
const handleSearch = () => {
setTableParams((s) => ({ ...s, current: 1, ...searchData }))
}
- handleReset 這個(gè)方法一般被筆者放在重置按鈕上扑馁,通過把tableParams及searchData的值重新賦值為init的值,觸發(fā)pullData凉驻,覆蓋原有的數(shù)據(jù)腻要。
// 重置,將搜索依賴及表格接口依賴重置為init
const handleReset = () => {
setTableParams(tableParamsInit)
setSearchData(searchDataInit)
}
- handlePageChange 翻頁涝登,直接給antD的Table組件的pagination 的onChange上就ok
// 頁碼變更
const handlePageChange = (v: number) => {
setTableParams((s) => ({ ...s, current: v }))
}
-
- handleAfterDel 當(dāng)表格具有刪除數(shù)據(jù)的功能時(shí)雄家,不得不考慮刪除數(shù)據(jù)后的一些處理邏輯。
- 第一頁時(shí)胀滚,用當(dāng)前參數(shù)直接獲取新數(shù)據(jù)
- 當(dāng)前數(shù)據(jù)量大于被刪除的數(shù)據(jù)量趟济,用當(dāng)前參數(shù)直接獲取新數(shù)據(jù)
- 計(jì)算出被刪除的頁數(shù),以及應(yīng)該具有數(shù)據(jù)的最后一頁咽笼,出現(xiàn)0或者負(fù)則應(yīng)該為第一頁
// 執(zhí)行刪除操作顷编,數(shù)據(jù)源出現(xiàn)變動(dòng),如果刪除的時(shí)當(dāng)前頁最后一條數(shù)據(jù)剑刑,則退回有數(shù)據(jù)的頁數(shù)媳纬,當(dāng)前頁為第一頁則只刷新
// count刪除的數(shù)據(jù)量
const handleAfterDel = (count?: number) => {
// 第一頁時(shí),直接獲取新數(shù)據(jù)
if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
} else {
// 被刪除的頁數(shù),用于計(jì)算需要返回的頁數(shù)
const deletedPage = Math.floor(
Number(
(
((count ?? 1) - (datasource?.length ?? 0)) /
tableParams.pageSize
).toFixed(0)
)
)
setTableParams((s) => ({
...s,
current: s.current - deletedPage > 1 ? s.current - deletedPage : 1,
}))
}
}
-
使用
...
const {
total,
setTotal,
// 表格數(shù)據(jù)源
datasource,
setDatasource,
// 表格接口依賴
tableParams,
setTableParams,
// 搜索依賴state
searchData,
setSearchData,
// 搜索操作
handleSearch,
// 重置操作
handleReset,
// 頁面變化
handlePageChange,
// 刪除數(shù)據(jù)后執(zhí)行,參數(shù)為刪除的條數(shù)
handleAfterDel,
} = useTableData<any, any, any>({
tableParamsInit: { pageSize: 10, current: 1 },
searchDataInit: { discount_type: 0 },
getTotal() {
return 100
},
pullData({ pageSize: limit, current: page, ...rest }, setData) {
dispatch({
type: `${namespace}/${ActionTypes.publicCodeGet}`,
payload: {
params: {
limit,
page,
...rest,
},
onSuccess(res) {
setData(res?.data)
},
},
})
setData(MOCK.publicCodeTableData)
},
})
return <>
<Form className={`${styles.search} mt20`} layout="inline">
<Form.Item label="通用碼名稱">
<Input
placeholder="請(qǐng)輸入通用碼名稱"
value={searchData.keywords}
onPressEnter={handleSearch}
onChange={({ target: { value } }) => {
setSearchData((s) => ({ ...s, keywords: value }))
}}
></Input>
</Form.Item>
<Form.Item label="優(yōu)惠方式">
<div style={{ width: 180 }}>
<Select
className={`w100`}
placeholder="請(qǐng)選擇優(yōu)惠方式"
options={[{ label: '全部', value: 0 }, ...types.discount.list]}
value={searchData.discount_type}
onChange={(v) => {
setSearchData((s) => ({ ...s, discount_type: v }))
}}
></Select>
</div>
</Form.Item>
<Form.Item>
<Button onClick={handleSearch} type="primary">
查詢
</Button>
</Form.Item>
<Form.Item>
<Button type="ghost" onClick={handleReset}>
重置
</Button>
</Form.Item>
</Form>
<Table
loading={tableLoading}
dataSource={datasource}
rowKey="id"
pagination={{
total,
pageSize: tableParams.pageSize,
current: tableParams.current,
showTotal: (total) => `共有${total}條數(shù)據(jù)`,
showSizeChanger: false,
onChange: handlePageChange,
}}
columns={[
{ title: '通用碼名稱', dataIndex: 'name' },
{
title: '適用范圍',
dataIndex: 'range',
render(_, record) {
return (
<div>
<div className={`p16`}>
{types.goods.data[record.range.goods_type]}
</div>
<div className={`p12g`}>{record.range.name}</div>
</div>
)
},
},
{ title: '優(yōu)惠碼', dataIndex: 'code' },
{ title: '優(yōu)惠方式', align: 'center', dataIndex: 'path' },
{ title: '可用數(shù)量', align: 'center', dataIndex: 'count' },
{
title: '有效期',
dataIndex: 'time',
render(time) {
return (
<div>
<div>起:{time[0]}</div>
<div>止:{time[1]}</div>
</div>
)
},
},
{
title: '狀態(tài)',
align: 'center',
dataIndex: 'is_enable',
render(text) {
return text === 1 ? '啟用' : '禁用'
},
},
{
title: '操作',
dataIndex: 'option',
align: 'center',
key: 'option',
width: 120,
render: (text: any, record: any, index) => (
<span className={styles.operate}>
<a
onClick={() => {
router.push({
pathname: '/marketing/publicCode/form',
query: { id: record.id ?? '' },
})
}}
>
編輯
</a>
<Divider type="vertical" />
<Popover
placement="bottom"
content={
<div>
<p
className="hoverActive cp pb4"
onClick={() => {
router.push({
pathname: '/marketing/publicCode/detail',
query: { id: record.id },
})
}}
>
使用情況
</p>
<p
className="hoverActive cp pb4"
onClick={() => {
// handleDel(record.id);
handleDel(record.id, index)
}}
>
刪除
</p>
</div>
}
trigger="hover"
>
<a onClick={() => {}}>更多</a>
</Popover>
</span>
),
},
]}
/ >
</>
...
總結(jié):類型檢查等處還有待優(yōu)化钮惠。筆者水平有限茅糜,還請(qǐng)各位讀者大佬斧正。