基於react-hooks+Typescript二次封裝Antd-Table

公司項目中有不少table頁面,並且不少業務都很相似,CURD操做是不可避免的,這部分的操做邏輯很繁瑣,頁面維護形成極大的不方便,所以便想到使用hooks對table進行封裝一波,抽離這些重複的邏輯。javascript

組件狀態設計(props+state)

一般表格須要一份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

  1. 將函數提取到組件外部,同時將dispatch函數做爲參數傳入
  2. 使用useCallback函數優化,將方法進行緩存,只有當callback的依賴發生變化了纔會再次執行fetchData方法。

第一種在這裏很少說了,這裏咱們採用第二種方式進行優化。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
            }
        }
    })
}
}
複製代碼

render

<Table
    columns={owncolumns(fetchData)}
    pagination={state.pagination}
    dataSource={state.dataSource}
    loading={state.loading}
    onChange={handleTableChange}
    {...baseProps}
/>
複製代碼

TS類型

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的基礎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} /> ) } 複製代碼
相關文章
相關標籤/搜索