React Table 表格组件使用教程 排序、分页、搜索过滤筛选功能实战开发

A kitten
4Ark
前端工程师
最近更新 2022年10月25日

React Table 表格组件使用教程 排序、分页、搜索过滤筛选功能实战开发

在日常开发中,特别是内部使用的后台系统时,我们常常会需要用表格来展示数据,同时提供一些操作用于操作表格内的数据。简单的表格直接用原生 HTML table 就好,但如果要在 React 中实现一个功能丰富的表格,其实是非常不容易的。

在本站之前的文章《最好的 6 个 React Table 组件详细亲测推荐》 中有提到过 react-table 这个库,如果对这个库不太了解的同学可以先了解一下,这里不再赘述。

简而言之,react-table 是一个非常强大的库,它与常见的表格组件不同,它不负责渲染 HTML 和 CSS,而是提供了一系列的 hooks 让我们可以灵活地构建功能强大的表格组件。

因此使用 react-table 进行开发具有一定的难度,而本文将由浅入深地讲解如何在 React 项目中使用 react-table 实现各种常见的需求,例如:排序、分页、搜索过滤筛选等;同时还会结合一个完整的案例给大家讲解如何搭配使用 Material-UI 以及模拟从后端获取数据进行分页等功能。

如果你正在搭建后台管理工具,又不想处理前端问题,推荐使用卡拉云 ,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API,内置表格等常见的前端组件,无需懂前端,仅需拖拽即可快速搭建属于你自己的后台管理工具,一周工作量缩减至一天,详见本文文末。

跟随本文你将学到

  • 如何使用 react-table 在 React 中搭建表格组件
  • 如何使用 react-table 表格组件进行数据的分页、排序、搜索过滤筛选
  • react-table 实战案例:手把手教你使用 react-table 表格组件实战分页、排序、搜索过滤筛选

react-table-all-demo

扩展阅读:《顶级好用的 React 表单设计生成器,可拖拽生成表单

react-table 安装和使用

首先,让我们先来创建一个 React 项目:

npx create-react-app react-table-demo

cd react-table-demo

然后我们安装一下 react-table:

npm i react-table # npm

接下来我们通过一个简单的示例,讲解如何在 React 项目中使用 react-table。

假设我们有一个订单表:

订单编号姓名收货地址下单日期
1596694478675759682蒋铁柱北京市海淀区西三环中路19号2022-07-01
1448752212249399810陈成功湖北武汉武昌区天子家园2022-06-27
1171859737495400477宋阿美湖北武汉武昌区天子家园2022-06-21
1096242976523544343张小乐北京市海淀区北航南门2022-06-30
1344783976877111376马国庆北京市海淀区花园桥东南2022-06-12
1505069508845600364小果广州天河机场西侧停车场2022-06-07

我们使用 react-table 时,需要通过一个叫做 useTable 的 hooks 来构建表格。

import { useTable } from 'react-table'

useTable 接收两个必填的参数:

  1. data:表格的数据
  2. columns:表格的列

所以让我们先来定义这个订单表的 data 和 columns:

import React, { useMemo } from 'react'

function App() {
  const data = useMemo(
    () => [
      {
        name: '蒋铁柱',
        address: '北京市海淀区西三环中路19号',
        date: '2022-07-01',
        order: '1596694478675759682'
      },
      {
        name: '陈成功',
        address: '湖北武汉武昌区天子家园',
        date: '2022-06-27',
        order: '1448752212249399810'
      },
      {
        name: '宋阿美',
        address: '湖北武汉武昌区天子家园',
        date: '2022-06-21',
        order: '1171859737495400477'
      },
      {
        name: '张小乐',
        address: '北京市海淀区北航南门',
        date: '2022-06-30',
        order: '1096242976523544343'
      },
      {
        name: '马国庆',
        address: '北京市海淀区花园桥东南',
        date: '2022-06-12',
        order: '1344783976877111376'
      },
      {
        name: '小果',
        address: '广州天河机场西侧停车场',
        date: '2022-06-07',
        order: '1505069508845600364'
      }
    ],
    []
  )

  const columns = useMemo(
    () => [
      {
        Header: '订单编号',
        accessor: 'order'
      },
      {
        Header: '姓名',
        accessor: 'name'
      },
      {
        Header: '收货地址',
        accessor: 'address'
      },
      {
        Header: '下单日期',
        accessor: 'date'
      }
    ],
    []
  )

  return (
    <div>
      <h1>React Table Demo —— 卡拉云(https://kalacloud.com)</h1>
      <Table columns={columns} data={data}></Table>
    </div>
  )
}

你可能会注意到这里我们使用 useMeno 来声明数据,这是因为 react-table 文档中说明传入的 data 和 columns 必须是 memoized 的,简单来说就是可以缓存的,仅当依赖项数组里面的依赖发生变化时才会重新计算,如果对 useMemo 不熟悉的同学建议直接看 React 文档

接着我们构建一个 Table 组件接收 columns 和 data,并传入到 useTable 中,它会返回一系列属性,我们就可以利用这些属性来构建 HTML table:

function Table({ columns, data }) {
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
  } = useTable({
    columns,
    data,
  })

  return (
    <table {...getTableProps()}>
      <thead>
        {headerGroups.map((headerGroup) => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map((column) => (
              <th {...column.getHeaderProps()}>{column.render('Header')}</th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map((row, i) => {
          prepareRow(row)
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map((cell) => {
                return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
              })}
            </tr>
          )
        })}
      </tbody>
    </table>
  )
}

由于是使用原生的 HTML table,因此是没有任何样式的, 这也是 react-table 的特点,好处是我们可以随意自定义我们想要的样式,比如我们引入 github-markdown-css

npm i github-markdown-css

然后在项目中使用即可:

import React, { useMemo } from 'react'
import { useTable } from 'react-table'

import './App.css'
+ import 'github-markdown-css'

function App() {
  return (
-   <div>
+   <div className="markdown-body" style={{ padding: '20px' }}>
      <h1>React Table Demo —— 卡拉云(https://kalacloud.com)</h1>
      <Table columns={columns} data={data}></Table>
    </div>
  )
}

react-table 样式效果:

set-react-table

接下来我们给这个表格添加更多常见的功能:排序、搜索过滤筛选、分页等。

扩展阅读:《7 款最棒的开源 React 移动端 UI 组件库和模版框架 - 特别针对国内使用场景推荐》

React Table 表格排序功能

如果只是想设置默认排序,我们可以通过配置 initialState 来实现:

useTable({
  columns,
  data,
  initialState: {
    sortBy: [
      {
        id: 'order',
        desc: true
      }
    ]
  }
})

如果要实现手动排序,就需要通过 useSortBy 这个 hooks 实现:

import { useTable, useSortBy } from 'react-table' 

然后在 useTable 中传入 useSortBy

const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  rows,
  prepareRow,
} = useTable(
 {
   columns,
   data,
 },
+ useSortBy,
)

然后我们可以在 columns 中的某个列指定 sortType 属性,它接收 String 或 Function,对于 Function 的使用方式按下不表,而对于 String 类型,它可以接收以下三种:

  1. alphanumeric:字母或数字进行排序(默认值)
  2. basic:0 到 1 之间的数字排序
  3. datetime:日期排序,值必须为 Date 类型

比如在我们这个例子中,我们希望可以允许对「订单编号」进行排序,那我们则修改:

const columns = useMemo(
  () => [
    {
      Header: '订单编号',
      accessor: 'order',
+     sortType: 'basic'
    },
    {
      Header: '姓名',
      accessor: 'name'
    },
    {
      Header: '收货地址',
      accessor: 'address'
    },
    {
      Header: '下单日期',
      accessor: 'date',
    }
  ],
  []
)

接着我们在表头处中添加排序相关的逻辑,并且根据当前列的排序情况分别显示对应的箭头,或者在没有任何排序时不显示:

<thead>
  {headerGroups.map((headerGroup) => (
  <tr {...headerGroup.getHeaderGroupProps()}>
    {headerGroup.headers.map((column) => (
-   <th {...column.getHeaderProps()}>
+   <th {...column.getHeaderProps(column.getSortByToggleProps())}>
      {column.render('Header')}
+     <span>
+       {column.isSorted ? (column.isSortedDesc ? ' 🔽' : ' 🔼') : ''}
+     </span>
    </th>
    ))}
  </tr>
  ))}
</thead>

展示效果如下:

sort-demo-1

通过上图我们发现了一个问题:即便我们没有对「姓名」这一列配置 sortType,却依然可以进行排序,这是因为一旦在 useTable 传入了 useSortBy,则默认所有列都可进行排序,如果我们需要对特定的列禁用排序,可以这样:

const columns = useMemo(
  () => [
    {
      Header: '订单编号',
      accessor: 'order',
      sortType: 'basic'
    },
    {
      Header: '姓名',
      accessor: 'name',
+     disableSortBy: true,
    },
    {
      Header: '收货地址',
      accessor: 'address'
    },
    {
      Header: '下单日期',
      accessor: 'date',
    }
  ],
  []
)

关于排序功能更多详细细节参见文档:useSortBy

扩展阅读:《7 款最棒的开源 React UI 组件库和模版框架测评 - 特别针对国内使用场景推荐

React Table 表格搜索过滤筛选功能

我们可以通过 useFilters 来实现筛选功能:

import { useTable, useFilters } from 'react-table'

同样地,需要在 useTable 中传入:

const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  rows,
  prepareRow,
} = useTable(
 {
   columns,
   data,
 },
+ useFilters,
)

PS:注意 useFilters 必须位于 useSortBy 前面,否则会报错。

然后在表头中渲染筛选输入框:

<th {...column.getHeaderProps()}>
 {column.render('Header')}
+ <div>{column.canFilter ? column.render('Filter') : null}</div>
</th>

这个筛选输入框的 UI 需要我们自定义,所以我们定义一个 TextFilter 组件:

function TextFilter({ column: { filterValue, preFilteredRows, setFilter } }) {
  const count = preFilteredRows.length

  return (
    <input
      value={filterValue || ''}
      onChange={(e) => {
        setFilter(e.target.value || undefined)
      }}
      placeholder={`筛选 ${count} 条记录`}
    />
  )
}

这个组件接收三个参数:

  • filterValue:用户输入的筛选值
  • preFilteredRows:筛选前的行
  • setFilter:用于设置用户筛选的值

定义完筛选组件后,我们还将 TextFilter 传入到一个 defaultColumn 中:

const defaultColumn = React.useMemo(
 () => ({
   Filter: TextFilter,
 }),
 []
)

接着再把 defaultColumn 传入 useTable

const {
  getTableProps,
  getTableBodyProps,
  headerGroups,
  rows,
  prepareRow,
} = useTable(
 {
   columns,
   data,
+  defaultColumn,
 },
 useFilters,
)

展示效果如下: filter-demo-1

这里我们发现了一个问题:当点击筛选输入框时,会改变排序方式,这是因为改变排序的点击事件是放在 <th>,因此我们要阻止这个输入框的点击事件向外层冒泡:

- <div>
+ <div onClick={(e) => e.stopPropagation()}>
    {column.canFilter ? column.render('Filter') : null}
</div>

同样地,如果想要禁用某一个列的筛选,可以设置 disableFilters

const columns = useMemo(
  () => [
    {
      Header: '订单编号',
      accessor: 'order',
      sortType: 'basic'
    },
    {
      Header: '姓名',
      accessor: 'name',
+     disableFilters: true,
    },
    {
      Header: '收货地址',
      accessor: 'address'
    },
    {
      Header: '下单日期',
      accessor: 'date',
    }
  ],
  []
)

关于筛选功能更多详细细节参见文档:useFilters

扩展阅读:《最好用的 8 款 React Datepicker 时间日期选择器测评推荐》

React Table 表格分页功能

分页功能使用 usePagination 这个 hooks 实现:

import { useTable, usePagination } from 'react-table' 

然后在 useTable 中添加分页相关的参数:

const {
   getTableProps,
   headerGroups,
   getRowProps,
-  rows 
+  state: { pageIndex, pageSize },
+  canPreviousPage,
+  canNextPage,
+  previousPage,
+  nextPage,
+  pageOptions,
+  page
 } = useTable(
   {
     columns,
     data,
+    initialState: { pageSize: 2 },
   },
+  usePagination,
 )

然后我们 tbody 中的 rows 将从 page 变量中获取:

<tbody {...getTableBodyProps()}>
- {rows.map((row) => {
+ {page.map((row) => {    
  prepareRow(row)
  return (
    <tr {...row.getRowProps()}>
      {row.cells.map((cell) => {
        return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
      })}
    </tr>
  )
})}
</tbody>

我们还需要构建一个分页器:

function Pagination({
  canPreviousPage,
  canNextPage,
  previousPage,
  nextPage,
  pageOptions,
  pageIndex
}) {
  return (
    <div>
      <button onClick={() => previousPage()} disabled={!canPreviousPage}>
        上一页
      </button>{' '}
      <button onClick={() => nextPage()} disabled={!canNextPage}>
        下一页
      </button>
      <div>{' '}
        <em>
          {pageIndex + 1} / {pageOptions.length}
        </em>{' '}</div>
    </div>
  )
}

在 table 后面使用这个分页器:

<>
  <table {...getTableProps()}>...
  </table>
  <Pagination
    canPreviousPage={canPreviousPage}
    canNextPage={canNextPage}
    previousPage={previousPage}
    nextPage={nextPage}
    pageOptions={pageOptions}
    pageIndex={pageIndex}
  />
</>

展示效果如下:

pagination-demo-1

更复杂的分页可以参考官方示例:Examples: Pagination

扩展阅读:《最好用的 5 个 React select 多选下拉菜单组件测评推荐》

React table 排序、搜索过滤筛选、分页示例代码

通过前文我们已经把 react-table 的基本使用都演示了一遍,你可以在此获取示例代码。

React table 实战案例

但是实际开发中的需求自然不会满足于本地数据,因此接下来我们演示一个更加真实、完整的例子,它将包含以下功能:

  1. 模拟从远端请求数据,并且通过服务端进行分页、筛选、排序。
  2. 搭配 Material-UI 构建组件

首先创建一个新的项目:

npx create-react-app react-table-example

cd react-table-example

然后安装相关依赖:

npm i react-table mockjs axios lodash.orderby

npm i axios-mock-adapter --save-dev

npm i @material-ui/core @material-ui/icons

模拟 API

然后我们生成 200 条订单数据,同时模拟 API 的筛选、排序和分页功能:

// mock.js

import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import Mock from 'mockjs'
import _orderby from 'lodash.orderby'

const { Random, mock } = Mock

const orders = new Array(200).fill(null).map(() => {
  return mock({
    order: Random.natural(),
    name: Random.cname(),
    address: Random.province() + '-' + Random.city() + '-' + Random.county(),
    date: Random.date()
  })
})

const mockAPI = {
  start() {
    const mock = new MockAdapter(axios)

    mock.onGet('/api/orders').reply((config) => {
      let { filter, sortBy, page = 0, size = 10 } = config.params || {}

      let mockOrders = [...orders]

      if (filter) {
        mockOrders = orders.filter((order) => {
          return Object.values(order).some((value) => value.includes(filter))
        })
      }

      if (sortBy.length) {
        sortBy.forEach((sort) => {
          mockOrders = _orderby(
            mockOrders,
            [sort.id],
            [sort.desc ? 'desc' : 'asc']
          )
        })
      }

      const offset = page * size

      mockOrders = mockOrders.slice(offset, offset + size)

      return new Promise((resolve) => {
        setTimeout(() => {
          resolve([
            200,
            {
              data: mockOrders,
              total_count: orders.length
            }
          ])
        }, 500)
      })
    })
  }
}

export default mockAPI

然后在 App.js 中引入并开始 mock 数据:

import mockAPI from './mock'

mockAPI.start()

构建基础 React Table 组件

有了上面的经验,我们很快就可以构建一个基础的表格组件:

// components/Table.js

import React from 'react'

import { useTable } from 'react-table'

import MaUTable from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody'
import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'

function Table({ columns, data }) {
  const { getTableProps, headerGroups, prepareRow, rows } = useTable({
    columns,
    data
  })

  return (
    <TableContainer>
      <MaUTable {...getTableProps()}>
        <TableHead>
          {headerGroups.map((headerGroup) => (
            <TableRow {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column) => (
                <TableCell {...column.getHeaderProps()}>
                  {column.render('Header')}
                </TableCell>
              ))}
            </TableRow>
          ))}
        </TableHead>
        <TableBody>
          {rows.map((row, i) => {
            prepareRow(row)
            return (
              <TableRow {...row.getRowProps()}>
                {row.cells.map((cell) => {
                  return (
                    <TableCell {...cell.getCellProps()}>
                      {cell.render('Cell')}
                    </TableCell>
                  )
                })}
              </TableRow>
            )
          })}
        </TableBody>
      </MaUTable>
    </TableContainer>
  )
}

export default Table

然后在 App.js 中请求 API 并展示:

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

import Table from './components/Table'

import mockAPI from './mock'

mockAPI.start()

function App() {
  const fetchOrders = async (params = {}) => {
    return axios.get('/api/orders', { params }).then((res) => {
      const resp = res.data

      setOrders(resp.data)
    })
  }

  const [orders, setOrders] = useState([])

  const data = useMemo(() => {
    return [...orders]
  }, [orders])

  const columns = useMemo(
    () => [
      {
        Header: '订单编号',
        accessor: 'order'
      },
      {
        Header: '姓名',
        accessor: 'name'
      },
      {
        Header: '收货地址',
        accessor: 'address'
      },
      {
        Header: '下单日期',
        accessor: 'date'
      }
    ],
    []
  )
  
  useEffect(() => {
    fetchOrders()
  }, [])

  return (
    <div style={{ padding: '20px' }}>
      <h1>React Table Example —— 卡拉云(https://kalacloud.com)</h1>
      <Table data={data} columns={columns} />
    </div>
  )
}

export default App

展示效果如下:

react-table-demo

服务端分页

接着我们添加分页功能,首先添加 TablePaginationActions 组件:

// components/TablePaginationActions.js

import React from 'react'

import FirstPageIcon from '@material-ui/icons/FirstPage'
import IconButton from '@material-ui/core/IconButton'
import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'
import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'
import LastPageIcon from '@material-ui/icons/LastPage'
import { makeStyles, useTheme } from '@material-ui/core/styles'

const useStyles = makeStyles((theme) => ({
  root: {
    flexShrink: 0,
    marginLeft: theme.spacing(2.5)
  }
}))

const TablePaginationActions = (props) => {
  const classes = useStyles()
  const theme = useTheme()
  const { count, page, rowsPerPage, onPageChange } = props

  const handleFirstPageButtonClick = (event) => {
    onPageChange(event, 0)
  }

  const handleBackButtonClick = (event) => {
    onPageChange(event, page - 1)
  }

  const handleNextButtonClick = (event) => {
    onPageChange(event, page + 1)
  }

  const handleLastPageButtonClick = (event) => {
    onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
  }

  return (
    <div className={classes.root}>
      <IconButton
        onClick={handleFirstPageButtonClick}
        disabled={page === 0}
        aria-label="first page"
      >
        {theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
      </IconButton>
      <IconButton
        onClick={handleBackButtonClick}
        disabled={page === 0}
        aria-label="previous page"
      >
        {theme.direction === 'rtl' ? (
          <KeyboardArrowRight />
        ) : (
          <KeyboardArrowLeft />
        )}
      </IconButton>
      <IconButton
        onClick={handleNextButtonClick}
        disabled={page >= Math.ceil(count / rowsPerPage) - 1}
        aria-label="next page"
      >
        {theme.direction === 'rtl' ? (
          <KeyboardArrowLeft />
        ) : (
          <KeyboardArrowRight />
        )}
      </IconButton>
      <IconButton
        onClick={handleLastPageButtonClick}
        disabled={page >= Math.ceil(count / rowsPerPage) - 1}
        aria-label="last page"
      >
        {theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
      </IconButton>
    </div>
  )
}

export default TablePaginationActions

然后在 Table.js 中修改如下:

import React, { useEffect } from 'react'
import { useTable, usePagination } from 'react-table'

+ import TableFooter from '@material-ui/core/TableFooter'
+ import TablePagination from '@material-ui/core/TablePagination'
+ import TablePaginationActions from './TablePaginationActions'

- function Table({ columns, data }) {
+ function Table({ columns, data, totalCount, onStateChange }) {
  const {
    getTableProps,
    headerGroups,
    prepareRow,
-   rows,  
+   page,
+   gotoPage,
+   setPageSize,
+   state: { pageIndex, pageSize }
  } = useTable(
    {
      columns,
      data,
+     manualPagination: true,
+     pageCount: totalCount
    },
+   usePagination
  )

+  useEffect(() => {
+    onStateChange({ pageIndex, pageSize })
+  }, [pageIndex, pageSize, onStateChange])

+  const handleChangePage = (event, newPage) => {
+    gotoPage(newPage)
+  }

+  const handleChangeRowsPerPage = (event) => {
+    setPageSize(Number(event.target.value))
+  }

  return (
    <TableContainer>
      <MaUTable {...getTableProps()}>
				...
        <TableBody>
-         {rows.map((row, i) => {
+         {page.map((row, i) => {
					...
        </TableBody>

+       <TableFooter>
+         <TableRow>
+           <TablePagination
+             rowsPerPageOptions={[5, 10, 15, 20]}
+             colSpan={3}
+             count={totalCount}
+             rowsPerPage={pageSize}
+             page={pageIndex}
+             SelectProps={{
+               inputProps: { 'aria-label': 'rows per page' },
+               native: true
+             }}
+             onPageChange={handleChangePage}
+             onRowsPerPageChange={handleChangeRowsPerPage}
+             ActionsComponent={TablePaginationActions}
+           />
+         </TableRow>
+       </TableFooter>
      </MaUTable>
    </TableContainer>
  )
}

export default Table

App.js 中增加控制分页的逻辑:

const [totalCount, setTotalCount] = useState(0)

const fetchOrders = async (params = {}) => {
  return axios.get('/api/orders', { params }).then((res) => {
    const resp = res.data

    setOrders(resp.data)
    setTotalCount(resp.total_count)
  })
}

const onStateChange = useCallback(({ pageIndex, pageSize }) => {
  fetchOrders({
    page: pageIndex,
    size: pageSize
  })
}, [])

由于 Table 组件内部会触发 onStateChange,因此不需要在 useEffect 中获取数据 ,然后传入 Table 相关属性:

- useEffect(() => {
-     fetchOrders()
-   }, [])

<Table
  data={data}
  columns={columns}
+ totalCount={totalCount}
+ onStateChange={onStateChange}
/>

展示效果如下:

pagination-demo

服务端排序

接着我们添加排序功能,首先修改 Table.js

- import { useTable, usePagination } from 'react-table'
+ import { useTable, usePagination, useSortBy } from 'react-table'

+ import TableSortLabel from '@material-ui/core/TableSortLabel'

function Table({ columns, data, totalCount, onStateChange }) {
  const {
    getTableProps,
    headerGroups,
    prepareRow,
    page,
    gotoPage,
    setPageSize,
-   state: { pageIndex, pageSize }    
+   state: { pageIndex, pageSize, sortBy }
  } = useTable(
    {
      columns,
      data,
      manualPagination: true,
+     manualSortBy: true,
      pageCount: totalCount
    },
+   useSortBy,
    usePagination
  )
  
-  useEffect(() => {
-    onStateChange({ pageIndex, pageSize })
-  }, [pageIndex, pageSize, onStateChange])

+  useEffect(() => {
+    onStateChange({ pageIndex, pageSize, sortBy })
+  }, [pageIndex, pageSize, sortBy, onStateChange])


    <TableCell
-     {...column.getHeaderProps()}
+     {...column.getHeaderProps(column.getSortByToggleProps())}
    >
       {column.render('Header')}
+     <TableSortLabel
+       active={column.isSorted}
+       direction={column.isSortedDesc ? 'desc' : 'asc'}
+     />			
    </TableCell>
}

React table 排序功能展示效果如下:

react-table-sort-demo

扩展阅读:《React 实现 PDF 文件在线预览 - 手把手教你写 React PDF 预览功能》

服务端搜索过滤筛选

然后我们添加筛选功能,通常筛选器都是位于表格以外的,在本例子中,我们期待在筛选框中输入的搜索值应用在所有的列,这里我们创建一个 TableFilter 组件:

// components/TableFilter.js

import React from 'react'

import InputBase from '@material-ui/core/InputBase'
import { fade, makeStyles } from '@material-ui/core/styles'
import SearchIcon from '@material-ui/icons/Search'

const useStyles = makeStyles((theme) => ({
  search: {
    position: 'relative',
    borderRadius: theme.shape.borderRadius,
    backgroundColor: fade(theme.palette.common.white, 0.15),
    '&:hover': {
      backgroundColor: fade(theme.palette.common.white, 0.25)
    },
    marginRight: theme.spacing(2),
    marginLeft: 0,
    width: '100%',
    [theme.breakpoints.up('sm')]: {
      width: 'auto'
    }
  },
  searchIcon: {
    width: theme.spacing(7),
    height: '100%',
    position: 'absolute',
    pointerEvents: 'none',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  },
  inputRoot: {
    color: 'inherit'
  },
  inputInput: {
    padding: theme.spacing(1, 1, 1, 7),
    transition: theme.transitions.create('width'),
    width: '100%',
    [theme.breakpoints.up('md')]: {
      width: 200
    }
  }
}))

const GlobalFilter = ({ globalFilter, setGlobalFilter }) => {
  const classes = useStyles()

  return (
    <div className={classes.search}>
      <div className={classes.searchIcon}>
        <SearchIcon />
      </div>
      <InputBase
        value={globalFilter || ''}
        onChange={(e) => {
          setGlobalFilter(e.target.value || undefined)
        }}
        placeholder={`在此输入搜索值`}
        classes={{
          root: classes.inputRoot,
          input: classes.inputInput
        }}
        inputProps={{ 'aria-label': 'search' }}
      />
    </div>
  )
}

export default GlobalFilter

然后在 Table.js 中使用这个组件,并添加筛选逻辑:

- import { useTable, usePagination, useSortBy } from 'react-table'
+ import { useTable, usePagination, useSortBy, useGlobalFilter } from 'react-table'

+ import TableFilter from './TableFilters'

function Table({ columns, data, totalCount, onStateChange }) {
  const {
    getTableProps,
    headerGroups,
    prepareRow,
    page,
    gotoPage,
    setPageSize,
-   state: { pageIndex, pageSize, sortBy }    
+   state: { pageIndex, pageSize, sortBy, globalFilter },
+   setGlobalFilter
  } = useTable(
    {
      columns,
      data,
      manualPagination: true,
      manualSortBy: true,
+     manualGlobalFilter: true,
      pageCount: totalCount
    },
+   useGlobalFilter,
    useSortBy,
    usePagination
  )
  
  useEffect(() => {
-   onStateChange({ pageIndex, pageSize, sortBy })
+   onStateChange({ pageIndex, pageSize, sortBy, filter: globalFilter })
- }, [pageIndex, pageSize, sortBy, onStateChange])
+ }, [pageIndex, pageSize, sortBy, onStateChange, globalFilter])  
  
    <TableContainer>
+     <TableFilter
+       globalFilter={globalFilter}
+       setGlobalFilter={setGlobalFilter}
+     />  

App.js 中接收 filter 值并传递给 API:

const onStateChange = useCallback(
- ({ pageIndex, pageSize, sortBy }) => {
+ ({ pageIndex, pageSize, sortBy, filter }) => {
    fetchOrders({
      page: pageIndex,
      size: pageSize,
      sortBy,
+     filter
    })
  },
  []
)

react-table 搜索过滤筛选展示效果如下:

filter-demo

扩展阅读:《5款 React 实时消息提示通知(Message/Notification)组件推荐与测评

React Table 组件与卡拉云

前面我们展示了如何在 react-table 中搭配 Material-UI 构建一个完整的表格组件,相信你已经上手 react-table 的用法,而这只是 react-table 功能的冰山一角,还有更多例如:动态展示列、分组展开、动画、拖拽、行内编辑、虚拟列表等,所以 react-table 的强大可以让你搭配出更多自定义功能。

其实如果你只想专注在解决问题,而不想把时间浪费在调试前端问题上的话,推荐使用卡拉云,卡拉云是新一代低代码开发工具,不仅可以拖拽生成带有排序、分页、搜索功能的表格组件等多种你需要的前端组件。与各类前端框架相比,卡拉云完全不用写前端代码,极大提升了开发效率,1 周的工作量,现在只要 30 分钟即可完成。卡拉云直接注册即可开始使用,后台搭建完成后,还能一键分享给同事一起使用。

卡拉云 SQL admin 后台管理系统

可一键分享给同事一起使用:https://my.kalacloud.com/apps/q6p23cqa29/published

上面是用卡拉云搭建的数据库 CURD 后台管理系统,无需任何前端技术,只需拖拽组件,即可在10分钟内完成搭建。