本文完整版:《React Table 表格组件使用教程 排序、分页、搜索过滤筛选功能实战开发》
在日常开发中,特别是内部使用的后台系统时,我们常常会需要用表格来展示数据,同时提供一些操作用于操作表格内的数据。简单的表格直接用原生 HTML table 就好,但如果要在 React 中实现一个功能丰富的表格,其实是非常不容易的。
在本站之前的文章《最好的 6 个 React Table 组件详细亲测推荐》 中有提到过 react-table 这个库,如果对这个库不太了解的同学可以先了解一下,这里不再赘述。
简而言之,react-table 是一个非常强大的库,它与常见的表格组件不同,它不负责渲染 HTML 和 CSS,而是提供了一系列的 hooks 让我们可以灵活地构建功能强大的表格组件。
因此使用 react-table 进行开发具有一定的难度,而本文将由浅入深地讲解如何在 React 项目中使用 react-table 实现各种常见的需求,例如:排序、分页、搜索过滤筛选等;同时还会结合一个完整的案例给大家讲解如何搭配使用 Material-UI 以及模拟从后端获取数据进行分页等功能。
如果你正在搭建后台管理工具,又不想处理前端问题,推荐使用卡拉云 ,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API,内置表格等常见的前端组件,无需懂前端,仅需拖拽即可快速搭建属于你自己的后台管理工具,一周工作量缩减至一天,详见本文文末。
跟随本文你将学到
扩展阅读:《顶级好用的 React 表单设计生成器,可拖拽生成表单》
首先,让我们先来创建一个 React 项目:
npx create-react-app react-table-demo
cd react-table-demo
然后我们安装一下 react-table:
接下来我们通过一个简单的示例,讲解如何在 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
接收两个必填的参数:
所以让我们先来定义这个订单表的 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 (
React Table Demo —— 卡拉云(https://kalacloud.com)
)
}
你可能会注意到这里我们使用 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 (
{headerGroups.map((headerGroup) => (
{headerGroup.headers.map((column) => (
{column.render('Header')}
))}
))}
{rows.map((row, i) => {
prepareRow(row)
return (
{row.cells.map((cell) => {
return {cell.render('Cell')}
})}
)
})}
)
}
由于是使用原生的 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 (
-
+
React Table Demo —— 卡拉云(https://kalacloud.com)
)
}
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 类型,它可以接收以下三种:
- alphanumeric:字母或数字进行排序(默认值)
- basic:0 到 1 之间的数字排序
- datetime:日期排序,值必须为 Date 类型
比如在我们这个例子中,我们希望可以允许对「订单编号」进行排序,那我们则修改:
const columns = useMemo(
() => [
{
Header: '订单编号',
accessor: 'order',
+ sortType: 'basic'
},
{
Header: '姓名',
accessor: 'name'
},
{
Header: '收货地址',
accessor: 'address'
},
{
Header: '下单日期',
accessor: 'date',
}
],
[]
)
接着我们在表头处中添加排序相关的逻辑,并且根据当前列的排序情况分别显示对应的箭头,或者在没有任何排序时不显示:
{headerGroups.map((headerGroup) => (
{headerGroup.headers.map((column) => (
-
+
{column.render('Header')}
+
+ {column.isSorted ? (column.isSortedDesc ? ' ' : ' ') : ''}
+
))}
))}
展示效果如下:
通过上图我们发现了一个问题:即便我们没有对「姓名」这一列配置 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
前面,否则会报错。
然后在表头中渲染筛选输入框:
{column.render('Header')}
+ {column.canFilter ? column.render('Filter') : null}
这个筛选输入框的 UI 需要我们自定义,所以我们定义一个 TextFilter
组件:
function TextFilter({ column: { filterValue, preFilteredRows, setFilter } }) {
const count = preFilteredRows.length
return (
{
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,
)
这里我们发现了一个问题:当点击筛选输入框时,会改变排序方式,这是因为改变排序的点击事件是放在 ,因此我们要阻止这个输入框的点击事件向外层冒泡:
-
+ e.stopPropagation()}>
{column.canFilter ? column.render('Filter') : null}
同样地,如果想要禁用某一个列的筛选,可以设置 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
变量中获取:
- {rows.map((row) => {
+ {page.map((row) => {
prepareRow(row)
return (
{row.cells.map((cell) => {
return {cell.render('Cell')}
})}
)
})}
我们还需要构建一个分页器:
function Pagination({
canPreviousPage,
canNextPage,
previousPage,
nextPage,
pageOptions,
pageIndex
}) {
return (
{' '}
第{' '}
{pageIndex + 1} / {pageOptions.length}
{' '}
页
)
}
在 table 后面使用这个分页器:
<>
...
>
展示效果如下:
更复杂的分页可以参考官方示例:Examples: Pagination。
扩展阅读:《最好用的 5 个 React select 多选下拉菜单组件测评推荐》
React table 排序、搜索过滤筛选、分页示例代码
通过前文我们已经把 react-table 的基本使用都演示了一遍,你可以在此获取示例代码。
React table 实战案例
但是实际开发中的需求自然不会满足于本地数据,因此接下来我们演示一个更加真实、完整的例子,它将包含以下功能:
- 模拟从远端请求数据,并且通过服务端进行分页、筛选、排序。
- 搭配 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 (
{headerGroups.map((headerGroup) => (
{headerGroup.headers.map((column) => (
{column.render('Header')}
))}
))}
{rows.map((row, i) => {
prepareRow(row)
return (
{row.cells.map((cell) => {
return (
{cell.render('Cell')}
)
})}
)
})}
)
}
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 (
React Table Example —— 卡拉云(https://kalacloud.com)
)
}
export default App
展示效果如下:
服务端分页
接着我们添加分页功能,首先添加 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 (
{theme.direction === 'rtl' ? : }
{theme.direction === 'rtl' ? (
) : (
)}
= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === 'rtl' ? (
) : (
)}
= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === 'rtl' ? : }
)
}
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 (
...
- {rows.map((row, i) => {
+ {page.map((row, i) => {
...
+
+
+
+
+
)
}
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.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])
{column.render('Header')}
+
}
React table 排序功能展示效果如下:
扩展阅读:《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 (
{
setGlobalFilter(e.target.value || undefined)
}}
placeholder={`在此输入搜索值`}
classes={{
root: classes.inputRoot,
input: classes.inputInput
}}
inputProps={{ 'aria-label': 'search' }}
/>
)
}
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])
+
在 App.js
中接收 filter 值并传递给 API:
const onStateChange = useCallback(
- ({ pageIndex, pageSize, sortBy }) => {
+ ({ pageIndex, pageSize, sortBy, filter }) => {
fetchOrders({
page: pageIndex,
size: pageSize,
sortBy,
+ filter
})
},
[]
)
react-table 搜索过滤筛选展示效果如下:
扩展阅读:《5款 React 实时消息提示通知(Message/Notification)组件推荐与测评》
React Table 组件与卡拉云
前面我们展示了如何在 react-table 中搭配 Material-UI 构建一个完整的表格组件,相信你已经上手 react-table 的用法,而这只是 react-table 功能的冰山一角,还有更多例如:动态展示列、分组展开、动画、拖拽、行内编辑、虚拟列表等,所以 react-table 的强大可以让你搭配出更多自定义功能。
其实如果你只想专注在解决问题,而不想把时间浪费在调试前端问题上的话,推荐使用卡拉云,卡拉云是新一代低代码开发工具,不仅可以拖拽生成带有排序、分页、搜索功能的表格组件等多种你需要的前端组件。与各类前端框架相比,卡拉云完全不用写前端代码,极大提升了开发效率,1 周的工作量,现在只要 30 分钟即可完成。卡拉云直接注册即可开始使用,后台搭建完成后,还能一键分享给同事一起使用。
可一键分享给同事一起使用:https://my.kalacloud.com/apps/q6p23cqa29/published
卡拉云可帮你快速搭建企业内部工具,下图为使用卡拉云搭建的内部广告投放监测系统,无需懂前端,仅需拖拽组件,10 分钟搞定。你也可以快速搭建一套属于你的后台管理工具。
卡拉云是新一代低代码开发平台,与前端框架 Vue、React等相比,卡拉云的优势在于不用首先搭建开发环境,直接注册即可开始使用。开发者完全不用处理任何前端问题,只需简单拖拽,即可快速生成所需组件,可一键接入常见数据库及 API,根据引导简单几步打通前后端,数周的开发时间,缩短至 1 小时。立即免费试用卡拉云。