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

先看最終代碼:

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

interface PageAbout {
  current: number
  pageSize: number
}
// 配合antd的table旱物,生產(chǎn)table所需函數(shù)方法及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 | {}
  // 調(diào)用接口的方法遥缕,接受setDatasource作為參數(shù), 在獲取數(shù)據(jù)后,通過其將數(shù)據(jù)注回useTableData
  pullData: (
    params: TableParams,
    setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
  ) => void
  // 從結(jié)果中獲取數(shù)據(jù)總數(shù)
  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)
  //表格接口依賴變動(dòng)宵呛,調(diào)用接口单匣,拉取新的數(shù)據(jù)
  useEffect(() => {
    const res = pullData(tableParams, setDatasource)
    setTotal(getTotal(res))
  }, [tableParams])
  // 搜索時(shí),將搜索state set至表格接口依賴宝穗,表格接口依賴變化户秤,自動(dòng)觸發(fā)接口調(diào)用
  const handleSearch = () => {
    setTableParams((s) => ({ ...s, current: 1, ...searchData }))
  }
  // 重置,將搜索依賴及表格接口依賴重置為init
  const handleReset = () => {
    setTableParams(tableParamsInit)
    setSearchData(searchDataInit)
  }
  // 頁碼變更
  const handlePageChange = (v: number) => {
    setTableParams((s) => ({ ...s, current: v }))
  }
  // 執(zhí)行刪除操作逮矛,數(shù)據(jù)源出現(xiàn)變動(dòng)鸡号,如果刪除的時(shí)當(dāng)前頁最后一條數(shù)據(jù),則退回有數(shù)據(jù)的頁數(shù)须鼎,當(dāng)前頁為第一頁則只刷新
  // count刪除的數(shù)據(jù)量
  const handleAfterDel = (count?: number) => {
    // 第一頁時(shí)鲸伴,直接獲取新數(shù)據(jù)
    if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
      const res = pullData(tableParams, setDatasource)
      setTotal(getTotal(res))
    } else {
      // 被刪除的頁數(shù),用于計(jì)算需要返回的頁數(shù)
      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 {
    // 數(shù)據(jù)總數(shù)
    total,
    setTotal,
    // 表格數(shù)據(jù)源
    datasource,
    setDatasource,
    // 表格接口依賴
    tableParams,
    setTableParams,
    // 搜索依賴state
    searchData,
    setSearchData,
    // 搜索操作
    handleSearch,
    // 重置操作
    handleReset,
    // 頁面變化
    handlePageChange,
    // 刪除數(shù)據(jù)后執(zhí)行,參數(shù)為刪除的條數(shù)
    handleAfterDel,
  }
}

封裝思路

hooks提供了以往react以往風(fēng)格不方便實(shí)現(xiàn)的邏輯復(fù)用能力莉兰,筆者使用hooks于生產(chǎn)實(shí)踐中也有近倆月了挑围,寫的后臺(tái)項(xiàng)目中非常多的表格,遂基于antd的table組件糖荒,封裝了配套的可復(fù)用的邏輯杉辙。

  • 參數(shù)部分

一般來說,所有表格將會(huì)出現(xiàn)的不同部分需要作為參數(shù)傳入hooks中捶朵。
1. get表格數(shù)據(jù)所需的params參數(shù)初始值:tableParamsInit (默認(rèn)需要翻頁操作)
2. 查詢蜘矢、篩選、搜索等功能所依賴的參數(shù)初始值:searchDataInit
3. 調(diào)用接口综看,獲取表格數(shù)據(jù)的方法
4. 獲取數(shù)據(jù)總數(shù)的方法(如果后臺(tái)的接口格式幾乎一樣品腹,這個(gè)也可以寫死,不用傳入)

{
  tableParamsInit = { pageSize: 10, current: 1 } as any,
  searchDataInit = {},
  pullData = () => {
    // console.log('pullData')
  },
  getTotal = () => {
    return 1
  },
}: {
  tableParamsInit?: TableParams
  searchDataInit?: SearchData | {}
  // 調(diào)用接口的方法红碑,接受setDatasource作為參數(shù), 在獲取數(shù)據(jù)后舞吭,通過其將數(shù)據(jù)注回useTableData
  pullData: (
    params: TableParams,
    setDatasource: React.Dispatch<React.SetStateAction<Datasource[] | []>>
  ) => void
  // 從結(jié)果中獲取數(shù)據(jù)總數(shù)
  getTotal?: (res: any) => number
}
  • 狀態(tài)state
    1.dataSource用來存儲(chǔ)表格的數(shù)據(jù)
    2.tableParams是用于發(fā)送接口請(qǐng)求的最終參數(shù)
    3.searchData是查詢狀態(tài)的state
  const [datasource, setDatasource] = useState<Datasource[] | []>([])
  const [tableParams, setTableParams] = useState<TableParams>(tableParamsInit)
  const [searchData, setSearchData] = useState<SearchData | {}>(searchDataInit)
  • 監(jiān)聽effect
    • 監(jiān)聽tableParams即接口參數(shù)的變化泡垃,接口依賴的參數(shù)變了就請(qǐng)求接口獲取新的數(shù)據(jù),這種調(diào)用接口的時(shí)機(jī)是實(shí)際生產(chǎn)當(dāng)中筆者總結(jié)出的邏輯比較清晰易懂不容易出錯(cuò)的一種羡鸥。
    • 值得注意的是蔑穴,用于查詢搜索的那些參數(shù),只有當(dāng)用戶點(diǎn)擊查詢或者搜索按鈕時(shí)惧浴,才會(huì)被添加到tableParams中存和,觸發(fā)接口獲取數(shù)據(jù)。如果讀者在生產(chǎn)中衷旅,產(chǎn)品要求搜索內(nèi)容變化就要立即獲取數(shù)據(jù)的話捐腿,則應(yīng)該把搜索所依賴的參數(shù)直接收納于tableParams中,而不應(yīng)當(dāng)再作為searchData中的內(nèi)容柿顶。
 useEffect(() => {
   const res = pullData(tableParams, setDatasource)
   setTotal(getTotal(res))
 }, [tableParams])
  • 方法handler
    1. handleSearch 這個(gè)方法一般被筆者直接添加到查詢的按鈕上茄袖,通過把searchData的值賦值到tableParams中,改變tableParams嘁锯,觸發(fā)pullData獲取新的數(shù)據(jù)绞佩。
      注意:current也被筆者重新賦值為1,是因?yàn)橹砼ィ樵兓蚝Y選后的數(shù)據(jù)一般會(huì)少于總數(shù)據(jù)。這樣的話如果不把頁數(shù)重置為1則會(huì)出現(xiàn)查詢到的結(jié)果不符合預(yù)期的情況胆建。
  // 搜索時(shí)烤低,將搜索state set至表格接口依賴,表格接口依賴變化笆载,自動(dòng)觸發(fā)接口調(diào)用
  const handleSearch = () => {
    setTableParams((s) => ({ ...s, current: 1, ...searchData }))
  }

    1. handleReset 這個(gè)方法一般被筆者放在重置按鈕上扑馁,通過把tableParams及searchData的值重新賦值為init的值,觸發(fā)pullData凉驻,覆蓋原有的數(shù)據(jù)腻要。
  // 重置,將搜索依賴及表格接口依賴重置為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 當(dāng)表格具有刪除數(shù)據(jù)的功能時(shí)雄家,不得不考慮刪除數(shù)據(jù)后的一些處理邏輯。
    • 第一頁時(shí)胀滚,用當(dāng)前參數(shù)直接獲取新數(shù)據(jù)
    • 當(dāng)前數(shù)據(jù)量大于被刪除的數(shù)據(jù)量趟济,用當(dāng)前參數(shù)直接獲取新數(shù)據(jù)
    • 計(jì)算出被刪除的頁數(shù),以及應(yīng)該具有數(shù)據(jù)的最后一頁咽笼,出現(xiàn)0或者負(fù)則應(yīng)該為第一頁
  // 執(zhí)行刪除操作顷编,數(shù)據(jù)源出現(xiàn)變動(dòng),如果刪除的時(shí)當(dāng)前頁最后一條數(shù)據(jù)剑刑,則退回有數(shù)據(jù)的頁數(shù)媳纬,當(dāng)前頁為第一頁則只刷新
// count刪除的數(shù)據(jù)量
  const handleAfterDel = (count?: number) => {
    // 第一頁時(shí),直接獲取新數(shù)據(jù)
    if (datasource?.length > (count ?? 1) || tableParams.current === 1) {
      const res = pullData(tableParams, setDatasource)
      setTotal(getTotal(res))
    } else {
      // 被刪除的頁數(shù),用于計(jì)算需要返回的頁數(shù)
      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,
    // 表格數(shù)據(jù)源
    datasource,
    setDatasource,
    // 表格接口依賴
    tableParams,
    setTableParams,
    // 搜索依賴state
    searchData,
    setSearchData,
    // 搜索操作
    handleSearch,
    // 重置操作
    handleReset,
    // 頁面變化
    handlePageChange,
    // 刪除數(shù)據(jù)后執(zhí)行,參數(shù)為刪除的條數(shù)
    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="請(qǐng)輸入通用碼名稱"
              value={searchData.keywords}
              onPressEnter={handleSearch}
              onChange={({ target: { value } }) => {
                setSearchData((s) => ({ ...s, keywords: value }))
              }}
            ></Input>
          </Form.Item>
          <Form.Item label="優(yōu)惠方式">
            <div style={{ width: 180 }}>
              <Select
                className={`w100`}
                placeholder="請(qǐng)選擇優(yōu)惠方式"
                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}條數(shù)據(jù)`,
              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: '優(yōu)惠碼', dataIndex: 'code' },
            { title: '優(yōu)惠方式', align: 'center', dataIndex: 'path' },
            { title: '可用數(shù)量', align: 'center', dataIndex: 'count' },
            {
              title: '有效期',
              dataIndex: 'time',
              render(time) {
                return (
                  <div>
                    <div>起:{time[0]}</div>
                    <div>止:{time[1]}</div>
                  </div>
                )
              },
            },
            {
              title: '狀態(tài)',
              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>
              ),
            },
          ]}
       / >
  </>
...

總結(jié):類型檢查等處還有待優(yōu)化钮惠。筆者水平有限茅糜,還請(qǐng)各位讀者大佬斧正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萌腿,一起剝皮案震驚了整個(gè)濱河市限匣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毁菱,老刑警劉巖米死,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贮庞,居然都是意外死亡峦筒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門窗慎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來物喷,“玉大人,你說我怎么就攤上這事遮斥÷褪В” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵术吗,是天一觀的道長(zhǎng)尉辑。 經(jīng)常有香客問我,道長(zhǎng)较屿,這世上最難降的妖魔是什么隧魄? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮隘蝎,結(jié)果婚禮上购啄,老公的妹妹穿的比我還像新娘。我一直安慰自己嘱么,他們只是感情好狮含,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著曼振,像睡著了一般辉川。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拴测,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天乓旗,我揣著相機(jī)與錄音,去河邊找鬼集索。 笑死屿愚,一個(gè)胖子當(dāng)著我的面吹牛汇跨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妆距,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼穷遂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了娱据?” 一聲冷哼從身側(cè)響起蚪黑,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎中剩,沒想到半個(gè)月后忌穿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡结啼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年掠剑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郊愧。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朴译,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出属铁,到底是詐尸還是另有隱情眠寿,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布焦蘑,位于F島的核電站澜公,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏喇肋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一迹辐、第九天 我趴在偏房一處隱蔽的房頂上張望蝶防。 院中可真熱鬧,春花似錦明吩、人聲如沸间学。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽低葫。三九已至,卻和暖如春仍律,著一層夾襖步出監(jiān)牢的瞬間嘿悬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工水泉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留善涨,地道東北人窒盐。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像钢拧,于是被迫代替她去往敵國(guó)和親蟹漓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361