公司項目中有不少table頁面,並且不少業務都很相似,CURD操做是不可避免的,這部分的操做邏輯很繁瑣,頁面維護形成極大的不方便,所以便想到使用hooks對table進行封裝一波,抽離這些重複的邏輯。javascript
一般表格須要一份params搜索數據,params數據會根據不一樣的業務邏輯而不一樣,所以咱們做爲prop傳入組件內部, 除此而外咱們還需表格的列數據owncolumns,與antd-table組件原始支持的baseProps。在數據層面咱們須要在組件內部維護遠程數據源datasource,根據不一樣的業務傳入不一樣的查詢方法queryAction,除此而外,咱們還須要一個loading狀態,在請求數據的時候去顯示loading動畫,這個小東西對用戶體驗的影響很大。最後,咱們的組件的props與state就很清晰明瞭了java
const { owncolumns, queryAction, params, baseProps } = props
複製代碼
const paginationInitial: paginationInitialType = {
current: 1,
pageSize: 10,
total: 0,
}
複製代碼
最後咱們合併這些statereact
const initialState: initialStateType = {
loading: false,
pagination: paginationInitial,
dataSource: []
}
複製代碼
頁面中須要維護的state都須要對應的操做去修改它,咱們之因此不用useState是由於,useState對不一樣操做的細粒度不是很高,雖然能夠合併state,可是對於不一樣的操做,咱們須要更清晰的知道咱們的代碼作了什麼,好比說觸發一個action,使用redux的思想,所以咱們天然而然想到了useState的代替方案-------useReducer。redux
const reducer = (state: initialStateType, action: actionType) => {
const { payload } = action
switch (action.type) {
case 'TOGGLE_LOADING': //更改loading狀態
return { ...state, loading: !state.loading }
case 'SET_PAGINATION': //設置分頁數據
return { ...state, pagination: payload.pagination }
case 'SET_DATA_SOURCE': //設置遠程數據源
return { ...state, dataSource: payload.dataSource }
default:
return state
}
}
const [state, dispatch] = useReducer(reducer, initialState)
複製代碼
此時咱們直接調用dispatch傳入同的action就能夠進入reducer進行處理,而且返回咱們想要的新的state後端
頁面中數據須要經過傳入的queryAction進行查詢,同時經過dispatch不一樣的action去處理組件的狀態緩存
async function fetchData() {
dispatch({
type: 'TOGGLE_LOADING'
})
// 分頁字段名稱轉換
const { current: indexfrom, pageSize: counts } = state.pagination
let res = await queryAction({ indexfrom, counts, ...params }).catch(err => {
dispatch({ type: 'TOGGLE_LOADING' })
return {}
})
// 關閉loading
dispatch({
type: 'TOGGLE_LOADING'
})
if (res.result === 200) {
const { totalcounts, list } = res
// 這邊根據不一樣的後端接口去作處理
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: { ...state.pagination, total: totalcounts }
}
})
// 回填list數據
dispatch({
type: 'SET_DATA_SOURCE',
payload: {
dataSource: list
}
})
}
}
複製代碼
接着咱們確定會想到使用useEffect去爲組件添加反作用,在組件mount,update時候去拉取數據,可是由於們的函數被定義在組件內部,組件每次更新都會從新生成該方法,對於useEffect來講,他的依賴項每次組件update都會從新定義,依賴變化了,進而又會執行方法->組件更新->進而又會執行方法......,所以會形成死循環。有兩種辦法能夠解決這個問題。bash
第一種在這裏很少說了,這裏咱們採用第二種方式進行優化。antd
const fetchDataWarp = useCallback(
fetchData,
[params, state.pagination.current, owncolumns, queryAction],
)
useEffect(() => {
fetchDataWarp()
}, [fetchDataWarp])
複製代碼
目前咱們僅封裝了分頁的功能,所以只須要維護頁面改變事件就能夠async
// 改變頁碼
function handleTableChange(payload: any) {
if (payload) {
const { current } = payload
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: {
...state.pagination,
current
}
}
})
}
}
複製代碼
<Table
columns={owncolumns(fetchData)}
pagination={state.pagination}
dataSource={state.dataSource}
loading={state.loading}
onChange={handleTableChange}
{...baseProps}
/>
複製代碼
import { Columns } from '../../types/types'
import { TableProps } from 'antd/lib/table/interface'
interface queryActionType {
(arg: any): Promise<any>
}
interface ColumnFunc {
(updateMethod: queryActionType): Array<Columns>
}
export interface ArgTableProps {
baseProps?: TableProps<any>
owncolumns: ColumnFunc
queryAction: queryActionType
params: any
listName?: string
}
export interface paginationInitialType {
current: number
pageSize: number
total: number
}
export interface initialStateType {
loading: boolean
pagination: paginationInitialType
dataSource: Array<any>
}
export interface actionType {
type: string
payload?: any
}
複製代碼
import React, { useEffect, useReducer, useCallback } from 'react'
import { Table } from 'antd';
import { ArgTableProps, paginationInitialType, initialStateType, actionType } from './type'
const useAsyncTable: React.FC<ArgTableProps> = props => {
const { owncolumns, queryAction, params, baseProps } = props
// 分頁數據
const paginationInitial: paginationInitialType = {
current: 1,
pageSize: 10,
total: 0,
}
// table組件全量數據
const initialState: initialStateType = {
loading: false,
pagination: paginationInitial,
dataSource: []
}
const reducer = (state: initialStateType, action: actionType) => {
const { payload } = action
switch (action.type) {
case 'TOGGLE_LOADING':
return { ...state, loading: !state.loading }
case 'SET_PAGINATION':
return { ...state, pagination: payload.pagination }
case 'SET_DATA_SOURCE':
return { ...state, dataSource: payload.dataSource }
default:
return state
}
}
const [state, dispatch] = useReducer(reducer, initialState)
// 改變頁碼
function handleTableChange(payload: any) {
if (payload) {
const { current } = payload
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: {
...state.pagination,
current
}
}
})
}
}
// useCallback包裝請求,緩存依賴,優化組件性能
const fetchDataWarp = useCallback(
fetchData,
[params, state.pagination.current, owncolumns, queryAction],
)
async function fetchData() {
dispatch({
type: 'TOGGLE_LOADING'
})
// 分頁字段名稱轉換
const { current: indexfrom, pageSize: counts } = state.pagination
let res = await queryAction({ indexfrom, counts, ...params }).catch(err => {
dispatch({ type: 'TOGGLE_LOADING' })
return {}
})
// 關閉loading
dispatch({
type: 'TOGGLE_LOADING'
})
if (res.result === 200) {
const { totalcounts, list } = res
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: { ...state.pagination, total: totalcounts }
}
})
// 回填list數據
dispatch({
type: 'SET_DATA_SOURCE',
payload: {
dataSource: list
}
})
}
}
useEffect(() => {
fetchDataWarp()
}, [fetchDataWarp])
return (
<Table columns={owncolumns(fetchData)} pagination={state.pagination} dataSource={state.dataSource} loading={state.loading} onChange={handleTableChange} {...baseProps} /> ) } export default useAsyncTable 複製代碼
屬性 | 類型 | 默認值 | 備註 |
---|---|---|---|
owncolumns | (updatefunc:Function) : columns | 必選參數 | updatefunc用於刷新列表 |
queryAction | (payload):Promise | 必選參數 | 用於列表數據獲取 |
baseProps | TableProps from antd | 任選 | antd的基礎props |
params | object | {} | 請求附加參數 |
const getColumn: getColumnType = updateMethod => {
return [
{
title: "項目名稱",
dataIndex: "project_name",
key: "project_name",
},
{
title: '操做',
key: 'setting',
width: 200,
render: (text: any, record: any, index: number) => {
return (
<div> <Button type="primary" style={{ marginRight: '5px' }}>查看</Button> <Popconfirm title="此操做將永久刪除該項目, 是否繼續?" okText="肯定" cancelText="取消" onConfirm={() => { updateMethod() }} > <Button type="danger">刪除</Button> </Popconfirm> </div>
)
}
}
];
}
render(){
return (
<ArgTable owncolumns={updatefunc => getColumn(updatefunc)} queryAction={API.http_getProjectList} baseProps={{ rowKey: record => record.project_id }} params={searchData} /> ) } 複製代碼