先看最終代碼:
import React, { useState, useMemo, useEffect } from 'react'
interface PageAbout {
current: number
pageSize: number
}
// 配合antd的table,生產table所需函數方法及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 | {}
// 調用接口的方法,接受setDatasource作為參數, 在獲取數據后,通過其將數據注回useTableData
pullData: (
params: TableParams,
setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
) => void
// 從結果中獲取數據總數
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)
//表格接口依賴變動,調用接口,拉取新的數據
useEffect(() => {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
}, [tableParams])
// 搜索時,將搜索state set至表格接口依賴,表格接口依賴變化,自動觸發接口調用
const handleSearch = () => {
setTableParams((s) => ({ ...s, current: 1, ...searchData }))
}
// 重置,將搜索依賴及表格接口依賴重置為init
const handleReset = () => {
setTableParams(tableParamsInit)
setSearchData(searchDataInit)
}
// 頁碼變更
const handlePageChange = (v: number) => {
setTableParams((s) => ({ ...s, current: v }))
}
// 執行刪除操作,數據源出現變動,如果刪除的時當前頁最后一條數據,則退回有數據的頁數,當前頁為第一頁則只刷新
// count刪除的數據量
const handleAfterDel = (count?: number) => {
// 第一頁時,直接獲取新數據
if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
} else {
// 被刪除的頁數,用于計算需要返回的頁數
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 {
// 數據總數
total,
setTotal,
// 表格數據源
datasource,
setDatasource,
// 表格接口依賴
tableParams,
setTableParams,
// 搜索依賴state
searchData,
setSearchData,
// 搜索操作
handleSearch,
// 重置操作
handleReset,
// 頁面變化
handlePageChange,
// 刪除數據后執行,參數為刪除的條數
handleAfterDel,
}
}
封裝思路
hooks提供了以往react以往風格不方便實現的邏輯復用能力,筆者使用hooks于生產實踐中也有近倆月了,寫的后臺項目中非常多的表格,遂基于antd的table組件,封裝了配套的可復用的邏輯。
-
參數部分
一般來說,所有表格將會出現的不同部分需要作為參數傳入hooks中。
1. get表格數據所需的params參數初始值:tableParamsInit (默認需要翻頁操作)
2. 查詢、篩選、搜索等功能所依賴的參數初始值:searchDataInit
3. 調用接口,獲取表格數據的方法
4. 獲取數據總數的方法(如果后臺的接口格式幾乎一樣,這個也可以寫死,不用傳入)
{
tableParamsInit = { pageSize: 10, current: 1 } as any,
searchDataInit = {},
pullData = () => {
// console.log('pullData')
},
getTotal = () => {
return 1
},
}: {
tableParamsInit?: TableParams
searchDataInit?: SearchData | {}
// 調用接口的方法,接受setDatasource作為參數, 在獲取數據后,通過其將數據注回useTableData
pullData: (
params: TableParams,
setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
) => void
// 從結果中獲取數據總數
getTotal?: (res: any) => number
}
-
狀態state
1.dataSource用來存儲表格的數據
2.tableParams是用于發送接口請求的最終參數
3.searchData是查詢狀態的state
const [datasource, setDatasource] = useState<Datasource[] | []>([])
const [tableParams, setTableParams] = useState<TableParams>(tableParamsInit)
const [searchData, setSearchData] = useState<SearchData | {}>(searchDataInit)
-
監聽effect
- 監聽tableParams即接口參數的變化,接口依賴的參數變了就請求接口獲取新的數據,這種調用接口的時機是實際生產當中筆者總結出的邏輯比較清晰易懂不容易出錯的一種。
- 值得注意的是,用于查詢搜索的那些參數,只有當用戶點擊查詢或者搜索按鈕時,才會被添加到tableParams中,觸發接口獲取數據。如果讀者在生產中,產品要求搜索內容變化就要立即獲取數據的話,則應該把搜索所依賴的參數直接收納于tableParams中,而不應當再作為searchData中的內容。
useEffect(() => {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
}, [tableParams])
-
方法handler
- handleSearch 這個方法一般被筆者直接添加到查詢的按鈕上,通過把searchData的值賦值到tableParams中,改變tableParams,觸發pullData獲取新的數據。
注意:current也被筆者重新賦值為1,是因為,查詢或篩選后的數據一般會少于總數據。這樣的話如果不把頁數重置為1則會出現查詢到的結果不符合預期的情況。
- handleSearch 這個方法一般被筆者直接添加到查詢的按鈕上,通過把searchData的值賦值到tableParams中,改變tableParams,觸發pullData獲取新的數據。
// 搜索時,將搜索state set至表格接口依賴,表格接口依賴變化,自動觸發接口調用
const handleSearch = () => {
setTableParams((s) => ({ ...s, current: 1, ...searchData }))
}
- handleReset 這個方法一般被筆者放在重置按鈕上,通過把tableParams及searchData的值重新賦值為init的值,觸發pullData,覆蓋原有的數據。
// 重置,將搜索依賴及表格接口依賴重置為init
const handleReset = () => {
setTableParams(tableParamsInit)
setSearchData(searchDataInit)
}
- handlePageChange 翻頁,直接給antD的Table組件的pagination 的onChange上就ok
// 頁碼變更
const handlePageChange = (v: number) => {
setTableParams((s) => ({ ...s, current: v }))
}
-
- handleAfterDel 當表格具有刪除數據的功能時,不得不考慮刪除數據后的一些處理邏輯。
- 第一頁時,用當前參數直接獲取新數據
- 當前數據量大于被刪除的數據量,用當前參數直接獲取新數據
- 計算出被刪除的頁數,以及應該具有數據的最后一頁,出現0或者負則應該為第一頁
// 執行刪除操作,數據源出現變動,如果刪除的時當前頁最后一條數據,則退回有數據的頁數,當前頁為第一頁則只刷新
// count刪除的數據量
const handleAfterDel = (count?: number) => {
// 第一頁時,直接獲取新數據
if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
const res = pullData(tableParams, setDatasource)
setTotal(getTotal(res))
} else {
// 被刪除的頁數,用于計算需要返回的頁數
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,
// 表格數據源
datasource,
setDatasource,
// 表格接口依賴
tableParams,
setTableParams,
// 搜索依賴state
searchData,
setSearchData,
// 搜索操作
handleSearch,
// 重置操作
handleReset,
// 頁面變化
handlePageChange,
// 刪除數據后執行,參數為刪除的條數
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="請輸入通用碼名稱"
value={searchData.keywords}
onPressEnter={handleSearch}
onChange={({ target: { value } }) => {
setSearchData((s) => ({ ...s, keywords: value }))
}}
></Input>
</Form.Item>
<Form.Item label="優惠方式">
<div style={{ width: 180 }}>
<Select
className={`w100`}
placeholder="請選擇優惠方式"
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}條數據`,
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: '優惠碼', dataIndex: 'code' },
{ title: '優惠方式', align: 'center', dataIndex: 'path' },
{ title: '可用數量', align: 'center', dataIndex: 'count' },
{
title: '有效期',
dataIndex: 'time',
render(time) {
return (
<div>
<div>起:{time[0]}</div>
<div>止:{time[1]}</div>
</div>
)
},
},
{
title: '狀態',
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>
),
},
]}
/ >
</>
...
總結:類型檢查等處還有待優化。筆者水平有限,還請各位讀者大佬斧正。