React Hooks 封装 Table

3,229 阅读3分钟

自从 React Hooks 发布以来,对整个前端生态的冲击无疑是巨大的。对我而言,最主要的作用是能够简单的封装高可用的组件了,下面记录一下如何使用 hooks 封装一个 Table 组件。

首先我们看看封装之前的样子:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { formatDate } from '@/utils/format'
import { getTableDataActionAsync } from '@/store/file/actionCreators'
import { Card, Table } from 'antd'

export default function Table() {
  const dispatch = useDispatch()
  const id = useSelector((state) => state.id)
  let tableData = useSelector((state) => state.table.data)
	const tableCount = useSelector((state) => state.table.count)
  const isLoading = useSelector((state) => state.ui.isTableLoading)

  tableData =
    tableData.length &&
    tableData.map((item, idx) => ({
      ...item,
      date: formatDate(item.date),
      key: idx,
    }))

  const tableColums = [
    { title: 'name', dataIndex: 'name' },
    { title: 'age', dataIndex: 'age' },
		{ title: 'date', dataIndex: 'date' },
  ]

  const handlePageChange = (page) => {
    const offset = (page - 1) * 5
    getCacheFileActionAsync(id, offset)(dispatch)
  }

  return (
      <Table
        columns={tableColums}
        dataSource={tableCacheFiles}
        bordered
        pagination={{
          size: 'small',
          defaultPageSize: 5,
          total: cacheFilesCount,
          onChange: (page) => handlePageChange(page),
        }}
        loading={isLoading}
      />
  )
}

可以看到,上面是一个很简单的 antd table,只有最基础的功能,这样的 table,通常在我们的项目中会被大量用到,只有一些小小的区别:

  • 展示的数据不同
  • 获取数据的接口不同
  • 控制 loading 状态的值不同

如果没有加以封装的话,上面的 table,我们在什么地方用到,就得在什么地方写一遍,冗余代码相当多——作为一个

when(me.wake) me.code()

的搬砖机器,我们不应该忍受这样的代码,所以这里尝试对其进行封装。 思路很直接,我们将需要数据抽离出来即可:

import React, { useEffect } from 'react'
import { formatDate } from '@/utils/format'
import { useSelector, useDispatch } from 'react-redux'

export default function useTable(data, count, fn, loading) {
  const dispatch = useDispatch()
  const id = useSelector((state) => state.id)
  let tableData = useSelector((state) => state.table[data])
  const tableCount = useSelector((state) => state.table[count])
  const isLoading = useSelector((state) => state.ui[loading])

  useEffect(() => {
    fn(id)(dispatch)
  }, [id, dispatch, fn])

  tableData =
    tableData.length &&
    tableData.map((item, idx) => ({
      ...item,
      date: formatDate(item.date),
      key: idx,
    }))

  const tableColumns = [
    { title: 'name', dataIndex: 'name' },
    { title: 'age', dataIndex: 'age' },
		{ title: 'date', dataIndex: 'date' },
  ]

  return {
    dispatch,
    id,
    tableData,
    tableCount,
    tableColumns,
    isLoading,
  }
}

这样一来,我们在外部直接获取需要的 data 即可:

import React from 'react'
import { useTable } from '@/hooks'
import { getTableDataActionAsync } from '@/store/complete/actionCreators'

export default function Apps() {
  const TABLE_DATA = 'data'
  const TABLE_COUNT = 'count'
  const TABLE_ASYNC = getTableDataActionAsync
	const TABLE_LOADING = 'isTableLoading'
  const { dispatch, id, tableData, tableCount, tableColumns, isLoading } = useTable(
    TABLE_DATA,
    TABLE_COUNT,
    TABLE_ASYNC,
		TABLE_LOADING
  )

  const handlePageChange = (page) => {
    const offset = (page - 1) * 5
    TABLE_ASYNC(id, offset)(dispatch)
  }

  return (
    <Table
        columns={tableColumns}
        dataSource={tableData}
        bordered
        pagination={{
          size: 'small',
          defaultPageSize: 5,
          total: tableCount,
          onChange: (page) => handlePageChange(page),
        }}
        loading={isLoading}
      />
  )
}

当需要处理不同格式的数据,不同数据接口的时候,我们只需要在 useTable 里建好映射表即可:

import React, { useEffect } from 'react'
import { formatDate } from '@/utils/format'
import { useSelector, useDispatch } from 'react-redux'

const tableMap = {
  data1: {
    data: 'data1',
    count: 'data1Count',
    loading: 'isData1TableLoading',
  },
	data2: {
    data: 'data2',
    count: 'data2Count',
    loading: 'isData2TableLoading',
  },
}
const tableColumsMap = {
  data1: [
    { title: 'name', dataIndex: 'name' },
    { title: 'age', dataIndex: 'age' },
		{ title: 'date', dataIndex: 'date' },
  ]
  data2: [
    { title: 'name', dataIndex: 'name' },
    { title: 'age', dataIndex: 'age' },
		{ title: 'date', dataIndex: 'date' },
  ]
}
const tableFormatUtls = {
  data1: (data1, idx) => ({
    ...data1,
    date: formatDate(data1.date),
    key: idx,
  }),
  data2: (data2, idx) => ({
    ...data2,
    date: formatDate(data2.date),
    key: idx,
  }),
}

export default function useTable(model, sotre, fn) {
  const dispatch = useDispatch()
  const id = useSelector((state) => state.device.id)
  const tableStore = useSelector((state) => state[sotre])
  const tableDataOrg = tableStore[tableMap[model].data]
  const tableCount = tableStore[tableMap[model].count]
  const isLoading = useSelector((state) => state.ui[tableMap[model].loading])

  useEffect(() => {
    fn(id)(dispatch)
  }, [id, dispatch, getTableData])

  const tableData = tableDataOrg.length && tableDataOrg.map(tableFormatUtls[model])
  const tableColumns = tableColumsMap[model]

  return {
    dispatch,
    id,
    tableData,
    tableCount,
    tableColumns,
    isLoading,
  }
}

这样,我们调用的时候,传入 model 来取对应的值就可以了:

import React from 'react'
import { useTable } from '@/hooks'
import { getTableDataActionAsync } from '@/store/complete/actionCreators'

export default function Apps() {
  const TABLE_MODLE = 'data1'
  const TABLE_SOTRE = 'table'
  const TABLE_ASYNC = getTableDataActionAsync
  const { dispatch, id, tableData, tableCount, tableColumns, isLoading } = useTable(
    TABLE_MODLE,
    TABLE_SOTRE,
    TABLE_ASYNC
  )

  const handlePageChange = (page) => {
    const offset = (page - 1) * 5
    TABLE_ASYNC(id, offset)(dispatch)
  }

  return (
    <Table
        columns={tableColumns}
        dataSource={tableData}
        bordered
        pagination={{
          size: 'small',
          defaultPageSize: 5,
          total: tableCount,
          onChange: (page) => handlePageChange(page),
        }}
        loading={isLoading}
      />
  )
}