表格Table常用邏輯hooks封裝,自用,基于AntD-React

先看最終代碼:

import React, { useState, useMemo, useEffect } from 'react'

interface PageAbout {
  current: number
  pageSize: number
}
// 配合antd的table,生產table所需函數方法及state
export function useTableData<
  TableParams extends SearchData & PageAbout = any,
  SearchData extends object = any,
  Datasource = any
>({
  tableParamsInit = { pageSize: 10, current: 1 } as any,
  searchDataInit = {},
  pullData = () => {
    // console.log('pullData')
  },
  getTotal = () => {
    return 1
  },
}: {
  tableParamsInit?: TableParams
  searchDataInit?: SearchData | {}
  // 調用接口的方法,接受setDatasource作為參數, 在獲取數據后,通過其將數據注回useTableData
  pullData: (
    params: TableParams,
    setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
  ) => void
  // 從結果中獲取數據總數
  getTotal?: (res: any) => number
}) {
  const [datasource, setDatasource] = useState<Datasource[] | []>([])
  const [tableParams, setTableParams] = useState<TableParams>(tableParamsInit)
  const [searchData, setSearchData] = useState<SearchData | {}>(searchDataInit)
  const [total, setTotal] = useState(1)
  //表格接口依賴變動,調用接口,拉取新的數據
  useEffect(() => {
    const res = pullData(tableParams, setDatasource)
    setTotal(getTotal(res))
  }, [tableParams])
  // 搜索時,將搜索state set至表格接口依賴,表格接口依賴變化,自動觸發接口調用
  const handleSearch = () => {
    setTableParams((s) => ({ ...s, current: 1, ...searchData }))
  }
  // 重置,將搜索依賴及表格接口依賴重置為init
  const handleReset = () => {
    setTableParams(tableParamsInit)
    setSearchData(searchDataInit)
  }
  // 頁碼變更
  const handlePageChange = (v: number) => {
    setTableParams((s) => ({ ...s, current: v }))
  }
  // 執行刪除操作,數據源出現變動,如果刪除的時當前頁最后一條數據,則退回有數據的頁數,當前頁為第一頁則只刷新
  // count刪除的數據量
  const handleAfterDel = (count?: number) => {
    // 第一頁時,直接獲取新數據
    if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
      const res = pullData(tableParams, setDatasource)
      setTotal(getTotal(res))
    } else {
      // 被刪除的頁數,用于計算需要返回的頁數
      const deletedPage = Math.floor(
        Number(
          (
            ((count ?? 1) - (datasource?.length ?? 0)) /
            tableParams.pageSize
          ).toFixed(0)
        )
      )
      setTableParams((s) => ({
        ...s,
        current: s.current - deletedPage > 1 ? s.current - deletedPage : 1,
      }))
    }
  }
  return {
    // 數據總數
    total,
    setTotal,
    // 表格數據源
    datasource,
    setDatasource,
    // 表格接口依賴
    tableParams,
    setTableParams,
    // 搜索依賴state
    searchData,
    setSearchData,
    // 搜索操作
    handleSearch,
    // 重置操作
    handleReset,
    // 頁面變化
    handlePageChange,
    // 刪除數據后執行,參數為刪除的條數
    handleAfterDel,
  }
}

封裝思路

hooks提供了以往react以往風格不方便實現的邏輯復用能力,筆者使用hooks于生產實踐中也有近倆月了,寫的后臺項目中非常多的表格,遂基于antd的table組件,封裝了配套的可復用的邏輯。

  • 參數部分

一般來說,所有表格將會出現的不同部分需要作為參數傳入hooks中。
1. get表格數據所需的params參數初始值:tableParamsInit (默認需要翻頁操作)
2. 查詢、篩選、搜索等功能所依賴的參數初始值:searchDataInit
3. 調用接口,獲取表格數據的方法
4. 獲取數據總數的方法(如果后臺的接口格式幾乎一樣,這個也可以寫死,不用傳入)

{
  tableParamsInit = { pageSize: 10, current: 1 } as any,
  searchDataInit = {},
  pullData = () => {
    // console.log('pullData')
  },
  getTotal = () => {
    return 1
  },
}: {
  tableParamsInit?: TableParams
  searchDataInit?: SearchData | {}
  // 調用接口的方法,接受setDatasource作為參數, 在獲取數據后,通過其將數據注回useTableData
  pullData: (
    params: TableParams,
    setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
  ) => void
  // 從結果中獲取數據總數
  getTotal?: (res: any) => number
}
  • 狀態state
    1.dataSource用來存儲表格的數據
    2.tableParams是用于發送接口請求的最終參數
    3.searchData是查詢狀態的state
  const [datasource, setDatasource] = useState<Datasource[] | []>([])
  const [tableParams, setTableParams] = useState<TableParams>(tableParamsInit)
  const [searchData, setSearchData] = useState<SearchData | {}>(searchDataInit)
  • 監聽effect
    • 監聽tableParams即接口參數的變化,接口依賴的參數變了就請求接口獲取新的數據,這種調用接口的時機是實際生產當中筆者總結出的邏輯比較清晰易懂不容易出錯的一種。
    • 值得注意的是,用于查詢搜索的那些參數,只有當用戶點擊查詢或者搜索按鈕時,才會被添加到tableParams中,觸發接口獲取數據。如果讀者在生產中,產品要求搜索內容變化就要立即獲取數據的話,則應該把搜索所依賴的參數直接收納于tableParams中,而不應當再作為searchData中的內容。
 useEffect(() => {
   const res = pullData(tableParams, setDatasource)
   setTotal(getTotal(res))
 }, [tableParams])
  • 方法handler
    1. handleSearch 這個方法一般被筆者直接添加到查詢的按鈕上,通過把searchData的值賦值到tableParams中,改變tableParams,觸發pullData獲取新的數據。
      注意:current也被筆者重新賦值為1,是因為,查詢或篩選后的數據一般會少于總數據。這樣的話如果不把頁數重置為1則會出現查詢到的結果不符合預期的情況。
  // 搜索時,將搜索state set至表格接口依賴,表格接口依賴變化,自動觸發接口調用
  const handleSearch = () => {
    setTableParams((s) => ({ ...s, current: 1, ...searchData }))
  }

    1. handleReset 這個方法一般被筆者放在重置按鈕上,通過把tableParams及searchData的值重新賦值為init的值,觸發pullData,覆蓋原有的數據。
  // 重置,將搜索依賴及表格接口依賴重置為init
  const handleReset = () => {
    setTableParams(tableParamsInit)
    setSearchData(searchDataInit)
  }
    1. handlePageChange 翻頁,直接給antD的Table組件的pagination 的onChange上就ok
  // 頁碼變更
  const handlePageChange = (v: number) => {
    setTableParams((s) => ({ ...s, current: v }))
  }
    1. handleAfterDel 當表格具有刪除數據的功能時,不得不考慮刪除數據后的一些處理邏輯。
    • 第一頁時,用當前參數直接獲取新數據
    • 當前數據量大于被刪除的數據量,用當前參數直接獲取新數據
    • 計算出被刪除的頁數,以及應該具有數據的最后一頁,出現0或者負則應該為第一頁
  // 執行刪除操作,數據源出現變動,如果刪除的時當前頁最后一條數據,則退回有數據的頁數,當前頁為第一頁則只刷新
// count刪除的數據量
  const handleAfterDel = (count?: number) => {
    // 第一頁時,直接獲取新數據
    if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
      const res = pullData(tableParams, setDatasource)
      setTotal(getTotal(res))
    } else {
      // 被刪除的頁數,用于計算需要返回的頁數
      const deletedPage = Math.floor(
        Number(
          (
            ((count ?? 1) - (datasource?.length ?? 0)) /
            tableParams.pageSize
          ).toFixed(0)
        )
      )
      setTableParams((s) => ({
        ...s,
        current: s.current - deletedPage > 1 ? s.current - deletedPage : 1,
      }))
    }
  }
  • 使用
...

  const {
    total,
    setTotal,
    // 表格數據源
    datasource,
    setDatasource,
    // 表格接口依賴
    tableParams,
    setTableParams,
    // 搜索依賴state
    searchData,
    setSearchData,
    // 搜索操作
    handleSearch,
    // 重置操作
    handleReset,
    // 頁面變化
    handlePageChange,
    // 刪除數據后執行,參數為刪除的條數
    handleAfterDel,
  } = useTableData<any, any, any>({
    tableParamsInit: { pageSize: 10, current: 1 },
    searchDataInit: { discount_type: 0 },
    getTotal() {
      return 100
    },
    pullData({ pageSize: limit, current: page, ...rest }, setData) {
      dispatch({
        type: `${namespace}/${ActionTypes.publicCodeGet}`,
        payload: {
          params: {
            limit,
            page,
            ...rest,
          },
          onSuccess(res) {
            setData(res?.data)
          },
        },
      })
      setData(MOCK.publicCodeTableData)
    },
  })

return <>  
      <Form className={`${styles.search} mt20`} layout="inline">
          <Form.Item label="通用碼名稱">
            <Input
              placeholder="請輸入通用碼名稱"
              value={searchData.keywords}
              onPressEnter={handleSearch}
              onChange={({ target: { value } }) => {
                setSearchData((s) => ({ ...s, keywords: value }))
              }}
            ></Input>
          </Form.Item>
          <Form.Item label="優惠方式">
            <div style={{ width: 180 }}>
              <Select
                className={`w100`}
                placeholder="請選擇優惠方式"
                options={[{ label: '全部', value: 0 }, ...types.discount.list]}
                value={searchData.discount_type}
                onChange={(v) => {
                  setSearchData((s) => ({ ...s, discount_type: v }))
                }}
              ></Select>
            </div>
          </Form.Item>
          <Form.Item>
            <Button onClick={handleSearch} type="primary">
              查詢
            </Button>
          </Form.Item>
          <Form.Item>
            <Button type="ghost" onClick={handleReset}>
              重置
            </Button>
          </Form.Item>
        </Form>

        <Table
          loading={tableLoading}
          dataSource={datasource}
          rowKey="id"
          pagination={{
              total,
              pageSize: tableParams.pageSize,
              current: tableParams.current,
              showTotal: (total) => `共有${total}條數據`,
              showSizeChanger: false,
              onChange: handlePageChange,
          }}
          columns={[
            { title: '通用碼名稱', dataIndex: 'name' },
            {
              title: '適用范圍',
              dataIndex: 'range',
              render(_, record) {
                return (
                  <div>
                    <div className={`p16`}>
                      {types.goods.data[record.range.goods_type]}
                    </div>
                    <div className={`p12g`}>{record.range.name}</div>
                  </div>
                )
              },
            },
            { title: '優惠碼', dataIndex: 'code' },
            { title: '優惠方式', align: 'center', dataIndex: 'path' },
            { title: '可用數量', align: 'center', dataIndex: 'count' },
            {
              title: '有效期',
              dataIndex: 'time',
              render(time) {
                return (
                  <div>
                    <div>起:{time[0]}</div>
                    <div>止:{time[1]}</div>
                  </div>
                )
              },
            },
            {
              title: '狀態',
              align: 'center',
              dataIndex: 'is_enable',
              render(text) {
                return text === 1 ? '啟用' : '禁用'
              },
            },
            {
              title: '操作',
              dataIndex: 'option',
              align: 'center',
              key: 'option',
              width: 120,
              render: (text: any, record: any, index) => (
                <span className={styles.operate}>
                  <a
                    onClick={() => {
                      router.push({
                        pathname: '/marketing/publicCode/form',
                        query: { id: record.id ?? '' },
                      })
                    }}
                  >
                    編輯
                  </a>
                  <Divider type="vertical" />
                  <Popover
                    placement="bottom"
                    content={
                      <div>
                        <p
                          className="hoverActive cp pb4"
                          onClick={() => {
                            router.push({
                              pathname: '/marketing/publicCode/detail',
                              query: { id: record.id },
                            })
                          }}
                        >
                          使用情況
                        </p>
                        <p
                          className="hoverActive cp pb4"
                          onClick={() => {
                            // handleDel(record.id);
                            handleDel(record.id, index)
                          }}
                        >
                          刪除
                        </p>
                      </div>
                    }
                    trigger="hover"
                  >
                    <a onClick={() => {}}>更多</a>
                  </Popover>
                </span>
              ),
            },
          ]}
       / >
  </>
...

總結:類型檢查等處還有待優化。筆者水平有限,還請各位讀者大佬斧正。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373