В проекте компании много страниц таблиц, и многие бизнесы очень похожи, операция CURD неизбежна, логика работы этой части очень громоздка, а обслуживание страницы доставляет большие неудобства, поэтому я подумал об использовании хуков для инкапсуляции таблицы. , чтобы избавиться от этой повторяющейся логики.
Дизайн состояния компонента (реквизит+состояние)
Обычно таблица нуждается в данных поиска параметров, данные параметров будут разными в соответствии с разной бизнес-логикой, поэтому мы передаем их в компонент как реквизит, Кроме того, нам также нужны данные столбца owncolumns таблицы и baseProps, изначально поддерживаемые компонентом antd-table. На уровне данных нам нужно поддерживать источник данных удаленного источника данных внутри компонента и передавать различные методы запроса queryAction в соответствии с различными предприятиями. Кроме того, нам также нужно состояние загрузки для отображения анимации загрузки при запросе данных.Этот небольшой Вещи оказывают огромное влияние на пользовательский опыт. Наконец, свойства и состояние нашего компонента очень понятны.
const { owncolumns, queryAction, params, baseProps } = props
const paginationInitial: paginationInitialType = {
current: 1,
pageSize: 10,
total: 0,
}
Наконец, мы объединяем эти состояния
const initialState: initialStateType = {
loading: false,
pagination: paginationInitial,
dataSource: []
}
логический дизайн
Состояние, которое необходимо поддерживать на странице, требует соответствующих операций для его изменения. Причина, по которой мы не используем useState, заключается в том, что useState не очень детализирован для различных операций. Хотя состояние можно объединить, нам нужно быть более четким. о различных операциях.Зная, что делает наш код, например запускает действие, используя идею редукции, поэтому мы, естественно, думаем об альтернативе useState — useReducer.
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)
На этом этапе мы можем напрямую вызвать диспетчеризацию и передать одно и то же действие, чтобы ввести редьюсер для обработки и вернуть новое состояние, которое мы хотим.
Инкапсуляция интерфейса
Данные на странице необходимо запрашивать через входящее действие 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 для добавления побочных эффектов к компонентам и извлечения данных при монтировании и обновлении компонентов, но, поскольку их функции определены внутри компонента, метод будет регенерироваться каждый раз при обновлении компонента. зависимости будут переопределяться каждый раз, когда компонент обновляется, зависимости будут меняться, а затем будет выполняться метод -> обновление компонента -> и затем будет выполняться метод..., поэтому это вызовет бесконечный цикл. Есть два способа решить эту проблему.
- Извлеките функцию за пределы компонента и передайте функцию отправки в качестве параметра.
- Используйте функцию useCallback для оптимизации, кэширования метода и повторного выполнения метода fetchData только при изменении зависимости обратного вызова.
Первый здесь обсуждаться не будет, а здесь мы используем второй метод для оптимизации.
const fetchDataWarp = useCallback(
fetchData,
[params, state.pagination.current, owncolumns, queryAction],
)
useEffect(() => {
fetchDataWarp()
}, [fetchDataWarp])
Обработка событий компонента
В настоящее время мы инкапсулируем только функцию пейджинга, поэтому нам нужно только поддерживать событие смены страницы.
// 改变页码
function handleTableChange(payload: any) {
if (payload) {
const { current } = payload
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: {
...state.pagination,
current
}
}
})
}
}
render
<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
README.md
Prop
Атрибуты | Типы | По умолчанию | Примечание |
---|---|---|---|
owncolumns | (updatefunc:Function) : columns | Обязательный параметр | updatefunc используется для обновления списка |
queryAction | (payload):Promise | Обязательный параметр | Для получения списка данных |
baseProps | TableProps from antd | необязательный | основной реквизит antd |
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}
/>
)
}