Вторичная инкапсуляция Antd-Table на основе react-hooks+Typescript

React.js

В проекте компании много страниц таблиц, и многие бизнесы очень похожи, операция 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 для добавления побочных эффектов к компонентам и извлечения данных при монтировании и обновлении компонентов, но, поскольку их функции определены внутри компонента, метод будет регенерироваться каждый раз при обновлении компонента. зависимости будут переопределяться каждый раз, когда компонент обновляется, зависимости будут меняться, а затем будет выполняться метод -> обновление компонента -> и затем будет выполняться метод..., поэтому это вызовет бесконечный цикл. Есть два способа решить эту проблему.

  1. Извлеките функцию за пределы компонента и передайте функцию отправки в качестве параметра.
  2. Используйте функцию 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} 
            />
      )
  }