There are many table pages in the company project, and many businesses are very similar. CURD operation is inevitable, and the operation logic of this part is very complicated, which causes great inconvenience in page maintenance. Therefore, IT occurred to me to use hooks to encapsulate the table in a wave and remove these repeated logic.
Component State Design (Props + State)
In general, the table needs a params search data. Params data will be different according to different business logic, so we pass it into the component as prop. In addition, we also need the table column data OWnColumns, and the baseProps originally supported by antD-Table component. In terms of data, we need to maintain the remote datasource inside the component and import different query methods queryAction according to different services. In addition, we also need a loading state to display loading animation when requesting data, which has a great impact on user experience. Finally, the props and state of our component are clear
const { owncolumns, queryAction, params, baseProps } = props
Copy the code
const paginationInitial: paginationInitialType = {
current: 1.pageSize: 10.total: 0,}Copy the code
Finally, we merge these states
const initialState: initialStateType = {
loading: false.pagination: paginationInitial,
dataSource: []}Copy the code
Logic design
All the states that need to be maintained in the page need corresponding operations to modify them. The reason why we use useState is that useState is not very fine-grained for different operations. Although we can combine states, we need to know more clearly what our code does for different operations. For example, to trigger an action, use the idea of Redux, so we naturally thought of the useState alternative ——-useReducer.
const reducer = (state: initialStateType, action: actionType) = > {
const { payload } = action
switch (action.type) {
case 'TOGGLE_LOADING': // Change the loading state
return { ...state, loading: !state.loading }
case 'SET_PAGINATION': // Set paging data
return { ...state, pagination: payload.pagination }
case 'SET_DATA_SOURCE': // Set the remote data source
return { ...state, dataSource: payload.dataSource }
default:
return state
}
}
const [state, dispatch] = useReducer(reducer, initialState)
Copy the code
At this point, we directly call Dispatch and pass in the same action. Then we can enter reducer for processing and return the new state we want
Encapsulation of interfaces
The data in the page needs to be queried through the queryAction passed in, while the component state is handled through the dispatch of different actions
async function fetchData() {
dispatch({
type: 'TOGGLE_LOADING'
})
// Paging field name conversion
const { current: indexfrom, pageSize: counts } = state.pagination
let res = awaitqueryAction({ indexfrom, counts, ... params }).catch(err= > {
dispatch({ type: 'TOGGLE_LOADING' })
return{}})/ / close the loading
dispatch({
type: 'TOGGLE_LOADING'
})
if (res.result === 200) {
const { totalcounts, list } = res
// This side is processed according to the different back-end interface
dispatch({
type: 'SET_PAGINATION'.payload: {
pagination: { ...state.pagination, total: totalcounts }
}
})
// Backfill the list data
dispatch({
type: 'SET_DATA_SOURCE'.payload: {
dataSource: list
}
})
}
}
Copy the code
The next logical step is to use useEffect to add side effects to the component, pull data when the component is mounted or updated, but since the function is defined inside the component, the component regenerates this method every time it is updated. For useEffect, His dependencies are redefined each time the component update changes, which in turn executes the method -> component update -> executes the method…… , thus creating an endless loop. There are two ways to solve this problem.
- Pull the function out of the component, passing in the Dispatch function as a parameter
- Using the useCallback function optimization, the method is cached, and the fetchData method is executed again only when the callback’s dependency changes.
I won’t talk about the first one here, but here we use the second way to optimize.
const fetchDataWarp = useCallback(
fetchData,
[params, state.pagination.current, owncolumns, queryAction],
)
useEffect((a)= > {
fetchDataWarp()
}, [fetchDataWarp])
Copy the code
Component event handling
So far we’ve only encapsulated paging, so you only need to maintain page change events
// Change the page numberfunction handleTableChange(payload: any) {
if (payload) {
const { current } = payload
dispatch({
type: 'SET_PAGINATION', payload: { pagination: { ... state.pagination, current } } }) } }Copy the code
render
<Table columns={owncolumns(fetchData)} pagination={state.pagination} dataSource={state.dataSource} loading={state.loading} onChange={handleTableChange} {... baseProps} />Copy the code
TS type
import { Columns } from '.. /.. /types/types'
import { TableProps } from 'antd/lib/table/interface'
interface queryActionType {
(arg: any): Promise<any>
}
interface ColumnFunc {
(updateMethod: queryActionType): Array<Columns>
}
exportinterface 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 }Copy the code
Component complete code
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
// Paging data
const paginationInitial: paginationInitialType = {
current: 1.pageSize: 10.total: 0,}// Full data of the table component
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)
// Change the page number
function handleTableChange(payload: any) {
if (payload) {
const { current } = payload
dispatch({
type: 'SET_PAGINATION'.payload: {
pagination: {
...state.pagination,
current
}
}
})
}
}
// useCallback wraps requests, caches dependencies and optimizes component performance
const fetchDataWarp = useCallback(
fetchData,
[params, state.pagination.current, owncolumns, queryAction],
)
async function fetchData() {
dispatch({
type: 'TOGGLE_LOADING'
})
// Paging field name conversion
const { current: indexfrom, pageSize: counts } = state.pagination
let res = awaitqueryAction({ indexfrom, counts, ... params }).catch(err= > {
dispatch({ type: 'TOGGLE_LOADING' })
return{}})/ / close the loading
dispatch({
type: 'TOGGLE_LOADING'
})
if (res.result === 200) {
const { totalcounts, list } = res
dispatch({
type: 'SET_PAGINATION'.payload: {
pagination: { ...state.pagination, total: totalcounts }
}
})
// Backfill the list data
dispatch({
type: 'SET_DATA_SOURCE'.payload: {
dataSource: list
}
})
}
}
useEffect((a)= > {
fetchDataWarp()
}, [fetchDataWarp])
return (
<Table
columns={owncolumns(fetchData)}
pagination={state.pagination}
dataSource={state.dataSource}
loading={state.loading}
onChange={handleTableChange}
{. baseProps} / >
)
}
export default useAsyncTable
Copy the code
README.md
Prop
attribute | type | The default value | note |
---|---|---|---|
owncolumns | (updatefunc:Function) : columns | Will choose parameters | Updatefunc is used to refresh the list |
queryAction | (payload):Promise | Will choose parameters | Used for list data retrieval |
baseProps | TableProps from antd | You can choose any | The basic props of ANTD |
params | object | {} | Request additional parameters |
Using the example
const getColumn: getColumnType = updateMethod= > {
return[{title: "Project Name".dataIndex: "project_name".key: "project_name"}, {title: 'operation'.key: 'setting'.width: 200.render: (text: any, record: any, index: number) = > {
return (
<div>
<Button type="primary" style={{ marginRight: '5px' }}>To view</Button>
<Popconfirm
title="This operation will permanently delete the item. Do you want to continue?"
okText="Sure"
cancelText="Cancel"
onConfirm={()= > {
updateMethod()
}}
>
<Button type="danger">delete</Button>
</Popconfirm>
</div>)}}]; } render(){return (
<ArgTable
owncolumns={updatefunc= > getColumn(updatefunc)}
queryAction={API.http_getProjectList}
baseProps={{ rowKey: record => record.project_id }}
params={searchData}
/>
)
}
Copy the code