9

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

 1 year ago
source link: https://kalacloud.com/blog/react-table-tutorial/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

前端工程师
最近更新 2022年07月05日

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分钟内完成搭建。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK