如何在React中创建可排序和可过滤的表格- CodesCode

学习如何在React中创建可排序和可过滤的表格组件,这将有助于在处理大型数据集时加快流程

动态表格在Web应用程序中经常用于以结构化格式表示数据。在处理大量数据集时,对数据集进行排序和过滤可以加快处理速度。在本教程中,我们将介绍如何在React中创建一个可排序和可过滤的表格组件。

您可以在GitHub上找到完整的源代码。以下是最终结果的图片。

最终表格组件

先决条件

在开始之前,本教程假设您有对HTML、CSSJavaScriptReact的基本了解。虽然我们会逐步介绍项目,但我们不会详细解释React或JavaScript数组方法的核心概念。我们还将使用TypeScript,但也可以不使用它来实现相同的功能。说了这么多,让我们开始编码吧。

设置项目

对于这个项目,我们将使用Vite,一个强大而受欢迎的前端工具。如果您还没有现有的React应用程序,您可以使用以下命令之一在终端中引导一个新项目:

# Using NPMnpm create vite@latest folder-name -- --template react-ts# Using Yarnyarn create vite folder-name --template react-ts# Using PNPMpnpm create vite folder-name --template react-ts# Using Bunbunx create-vite folder-name --template react-ts

当你准备好了之后,为React项目设置一个新的文件夹,用于Table组件,结构如下:

src├─ components│  ├─ Table│  │  ├─ index.ts │  │  ├─ table.css│  │  ├─ Table.tsx├─ App.tsx
  • index.ts。我们将使用这个文件来重新导出 Table.tsx以简化导入路径。
  • table.css。包含与组件相关的样式。对于本教程,我们将使用纯CSS。
  • Table.tsx。组件本身。

打开Table.tsx并导出以下内容,以便我们在导入时可以验证组件是否加载:

import './table.css'export const Table = () => {  return (    <h1>Table component</h1>  )}

index.ts 中,使用以下代码重新导出组件:

export * from './Table'

现在我们已经设置好了组件文件,让我们通过将其导入我们的应用程序来验证它是否加载。在本教程中,我们将使用App组件。如果你有一个现有的React项目,你可以将其导入到你想要的位置。像这样将Table组件导入到你的应用程序中:

import { Table } from './components/Table'const App = () => {  return (    <Table />  )}export default App

生成假数据

当然,为了处理表格,我们首先需要一些假数据。在本教程中,我们可以使用JSON Generator,这是一个用于生成随机JSON数据的免费服务。我们将使用以下模式生成数据:

[  '{{repeat(10)}}',  {    id: '{{index()}}',    name: '{{firstName()}} {{surname()}}',    company: '{{company().toUpperCase()}}',    active: '{{bool()}}',    country: '{{country()}}'  }]

JSON生成器具备各种内置功能,可生成不同类型的数据。上述模式将创建一个包含十个随机对象的对象数组,格式如下:

{  id: 0,                 // number - Index of the array, starting from 0  name: 'Jaime Wallace', // string - A random name  company: 'UBERLUX',    // string - Capitalized random string  active: false,         // boolean - either `true` or `false`  country: 'Peru'        // string - A random country name}

使用上述模式生成条目列表,然后在src文件夹内创建一个名为data.ts的新文件,并以以下方式导出数组:

export const data = [  {    id: 0,    name: 'Jaime Wallace',    company: 'UBERLUX',    active: false,    country: 'Peru'  },  { ... },]

打开App.tsx文件,并将这个数据作为名为rows的属性传递给Table组件。根据这个数据生成表格:

  import { Table } from './components/Table'+ import { data } from './data'  const App = () => {    return (-     <Table />+     <Table rows={data} />    )  }  export default App

创建组件

既然我们已经设置好了组件和数据,我们可以开始处理表格了。为了根据传递的数据动态生成表格,将Table组件中的所有内容替换为以下代码:

import { useState } from 'react'import './table.css'export const Table = ({ rows }) => {  const [sortedRows, setRows] = useState(rows)  return (    <table>      <thead>        <tr>          {Object.keys(rows[0]).map((entry, index) => (            <th key={index}>{entry}</th>          ))}        </tr>      </thead>      <tbody>        {sortedRows.map((row, index) => (          <tr key={index}>            {Object.values(row).map((entry, columnIndex) => (              <td key={columnIndex}>{entry}</td>            ))}          </tr>        ))}      </tbody>    </table>  )}

这将根据rows属性动态生成表头和单元格。让我们来解析一下它是如何工作的。由于我们要对行进行排序和过滤,所以我们需要使用useState Hook将其存储在状态中。该属性将作为初始值传递给Hook。

为了显示表头,我们可以在数组的第一个条目上使用Object.keys,它将返回对象的键作为字符串列表:

const rows = [  {    id: 0,    name: 'Jaime Wallace'  },  { ... }]// #1 Turn object properties into an array of keys:Object.keys(rows[0]) -> ['id', 'name']// #2 Chain `map` from the array to display the values inside `th` elements:['id', 'name'].map((entry, index) => (...))

为了显示表格单元格,我们需要在每一行上使用 Object.values。它会返回对象中每个键的值,与 Object.keys 不同。具体来说,这是我们如何显示表格单元格:

const sortedRows = [  {    id: 0,    name: 'Jaime Wallace'  },  { ... }]// #1 Loop through each object in the array and create a `tr` element:{sortedRows.map((row, index) => (<tr key={index}>...</tr>))}// #2 Loop through each property of each object to create the `td` elements:Object.values(row) -> [0, 'Jaime Wallace']

这种方法使我们的 Table 组件非常灵活,可以使用任何类型的数据,而无需重写逻辑。到目前为止,我们使用我们的组件创建了以下表格。然而,格式方面存在一些问题。

Table组件格式问题

格式化表格单元

目前,active 列没有显示出来。这是因为这些字段的值是布尔值,在 JSX 中它们不会被打印为字符串。要解决这个问题,我们可以引入一个新的函数来根据值格式化条目。将以下内容添加到 Table 组件中,并将 entry 包装在 JSX 中的函数中:

const formatEntry = (entry: string | number | boolean) => {  if (typeof entry === 'boolean') {    return entry ? '✅' : '❌'  }  return entry}return (  <table>    <thead>...</thead>    <tbody>      {sortedRows.map((row, index) => (        <tr key={index}>          {Object.values(row).map((entry, columnIndex) => (            <td key={columnIndex}>{formatEntry(entry)}</td>          ))}        </tr>      ))}    </tbody>  </table>)

formatEntry函数期望一个参数,而在我们的例子中,它可以是字符串数字或者布尔值,然后如果typeof entry布尔值的话,它会返回一个格式化后的值。对于true值,我们会显示一个绿色的勾号,而对于false值,我们会显示一个红色的叉号。使用类似的方法,我们也可以格式化表头。让我们使用以下函数将它们大写化:

export const capitalize = (  str: string) => str?.replace(/\b\w/g, substr => substr.toUpperCase())

这个函数使用正则表达式从每个单词中提取第一个字母并将其转换为大写。要使用这个函数,我们可以在src文件夹的根目录下创建一个utils.ts文件,导出该函数,然后在我们的Table组件中导入并以以下方式使用:

import { capitalize } from '../../utils'export const Table = ({ rows }) => {  ...  return (      <table>        <thead>          <tr>            {Object.keys(rows[0]).map((entry, index) => (              <th key={index}>{capitalize(entry)}</th>            ))}          </tr>        </thead>        <tbody>...</tbody>      </table>  )}

基于这些修改,我们现在有一个动态构建的格式化表格。

React中的格式化表格

键入props

在我们开始对表格进行样式设置和添加控件之前,让我们正确地为rows属性进行类型设置。为此,我们可以在src文件夹的根目录下创建一个types.ts文件,并导出可以在整个项目中重用的自定义类型。创建该文件并导出以下类型:

export type Data = {    id: number    name: string    company: string    active: boolean    country: string}[]

要在 Table 组件中输入 rows 属性,只需导入这个类型,并按照以下方式将其传递给组件:

import { Data } from '../../types'export type TableProps = {  rows: Data}export const Table = ({ rows }: TableProps) => { ... }

样式化表格

为了给整个表格组件设置样式,我们只需要几条规则。首先,我们想要设置颜色和边框,可以使用以下样式来实现:

table {  width: 100%;  border-collapse: collapse;}thead {  text-align: left; /* `thead` is centered by default */  color: #939393;  background: #2f2f2f;}th,td {  padding: 4px 6px;  border: 1px solid #505050;}

将上述代码添加到 table.css 中。确保在 <table> 上设置 border-collapsecollapse,以避免重复边框。由于表格跨越整个屏幕,我们还需要进行一些调整,移除左右边框,因为它们本来就看不到:

th:first-child,td:first-child {  border-left: 0;}th:last-child,th:last-child {  border-right: 0;}

这将消除<table>每侧的边框,使其看起来更整洁。最后,让我们为表格行添加一个悬停效果,以帮助用户在搜索表格时获得视觉上的帮助:

tr:hover {  background: #2f2f2f;}

目前为止,我们已经为组件定义了以下行为。

Table hover effect

添加控件

既然我们已经为表格进行了样式设置,让我们来添加排序和筛选功能的控件。我们将创建一个<input>用于筛选,以及一个<select>元素用于排序。我们还将包含一个按钮来切换排序顺序(升序/降序)。

Table with filter options

要添加这些输入,我们还需要为当前顺序(升序或降序)以及用于排序的对象的键(哪个键用于排序)定义新的状态。请使用以下代码扩展Table组件:

const [order, setOrder] = useState('asc')const [sortKey, setSortKey] = useState(Object.keys(rows[0])[0])const filter = (event: React.ChangeEvent<HTMLInputElement>) => {}const sort = (value: keyof Data[0], order: string) => {}const updateOrder = () => {}return (  <>    <div className="controls">      <input        type="text"        placeholder="Filter items"        onChange={filter}      />      <select onChange={(event) => sort()}>        {Object.keys(rows[0]).map((entry, index) => (          <option value={entry} key={index}>            Order by {capitalize(entry)}          </option>        ))}      </select>      <button onClick={updateOrder}>Switch order ({order})</button>    </div>    <table>...</table>  </>)

让我们按顺序来了解发生了什么变化:

  • order。首先,我们需要为排序顺序创建一个新的状态。可以选择ascdesc之一。我们将在sort函数中使用它的值。
  • sortKey。我们还需要一个用于排序关键字的状态。默认情况下,我们可以使用Object.keys(rows[0])[0]在对象数组中获取第一个属性的键。我们将使用这个键来跟踪在不同排序顺序之间的排序。
  • filter。我们需要一个用于过滤结果的函数。它需要传递给<input>元素上的onChange事件。请注意,React.ChangeEvent是一个泛型,可以接受触发更改的HTML元素的类型。
  • sort。就像filter函数一样,这个函数也需要附加到<select>元素上的onChange事件,但这一次,它将接受两个参数:
  • value。它可以取我们数据对象的键。我们可以使用keyof关键字来指定类型。这意味着value可以是idnamecompanyactivecountry之一。
  • order。排序的顺序,可以是ascdesc
  • updateOrder。最后,我们还需要一个用于更新排序顺序的函数。这将在按钮点击时触发。
  • 请注意,我们对于<th>元素所使用的逻辑也适用于动态生成<select>的选项。我们也可以重用capitalize实用函数来格式化选项。

    可用选择选项

    样式化控件

    在继续之前,让我们给控件添加样式。这可以通过一小部分CSS规则来完成。在table.css中添加以下内容:

    .controls {  display: flex;}input,select {  flex: 1;  padding: 5px 10px;  border: 0;}button {  background: #2f2f2f;  color: #FFF;  border: 0;  cursor: pointer;  padding: 5px 10px;}

    这将确保输入框相互对齐。通过在 <input><select> 元素上使用 flex: 1,我们可以使它们平均占用可用空间的宽度。 <button> 将根据其文本需要占用所需的空间。

    过滤表格

    现在我们已经有了控件,让我们看看如何实现功能。要根据任何字段对表格进行过滤,我们需要按照以下逻辑操作:

    const rows = [  {    id: 0,    name: 'Jaime Wallace'  },  { ... }]// #1: Set `rows` to a filtered version using `filter`// The return value of `filter` will determine which rows to keepsetRows([ ...rows ].filter(row => { ... }))// From here on, we discuss the return value of `filter`// #2: Grab every field from the `row` object to use it for filteringObject.values(row) -> [0, 'Jaime Wallace']// #3: Join the values together into a single string[0, 'Jaime Wallace'].join('') -> '0Jaime Wallace'// #4: Convert the string into lowercase to make search case-insensitive'0Jaime Wallace'.toLowerCase() -> '0jaime wallace'// #5: Check if the string contains the value entered in the input'0jaime wallace'.includes(value) -> true / false

    当所有内容结合在一起时,我们可以基于上述逻辑创建filterreturn值。这为filter函数留下了以下实现:

    const filter = (event: React.ChangeEvent<HTMLInputElement>) => {  const value = event.target.value  if (value) {    setRows([ ...rows.filter(row => {      return Object.values(row)        .join('')        .toLowerCase()        .includes(value)    }) ])  } else {    setRows(rows)  }}

    请注意,我们还要检查是否存在value。它的缺失意味着<input>字段为空。在这种情况下,我们想要重置状态并将未过滤的rows传递给setRows以重置表格。

    过滤表格

    排序表格

    我们有了过滤功能,但是我们仍然缺少排序功能。为了排序,我们有两个单独的函数:

    • sort。处理排序的函数。
    • updateOder。将排序顺序从升序切换到降序,反之亦然。

    让我们先从排序函数开始。每当<select>改变时,将调用sort函数。我们想要使用<select>元素的值来决定使用哪个键进行排序。为此,我们可以使用简单的sort方法和括号表示法来动态比较对象的键:

    const sort = (value: keyof Data[0], order: string) => {  const returnValue = order === 'desc' ? 1 : -1  setSortKey(value)  setRows([ ...sortedRows.sort((a, b) => {    return a[value] > b[value]      ? returnValue * -1      : returnValue  }) ])}

    让我们从头到尾逐步了解这个函数的实现,以更好地理解它。

    • returnValue。根据order状态,我们希望返回值为1或-1。这有助于定义排序顺序(1表示降序,-1表示升序)。
    • setSortKey。传递给函数的value<select>元素的值。我们希望将这个值记录在我们的状态(sortKey)中,可以通过调用setSortKey更新函数来实现。
    • setRows。实际的排序发生在这个调用中。使用方括号表示法,我们可以比较a[value]b[value],并返回-1或1。

    我们以以下内容作为例子:

    const rows = [{ id: 0 }, { id: 1 }]const value = 'id'// This translate to a.id and b.idrows.sort((a, b) => a[value] > b[value] ? -1 : 1)// If `returnValue` is -1, the order changesrows.sort((a, b) => a[value] > b[value] ? 1 : -1)

    切换排序方式

    要更新排序方式,我们只需要在按钮被点击时更新order状态。我们可以通过以下功能来实现:

    const updateOrder = () => {  const updatedOrder = order === 'asc' ? 'desc' : 'asc'  setOrder(updatedOrder)  sort(sortKey as keyof Data[0], updatedOrder)}

    每次点击都会将order设置为相反的值。注意,在使用setOrder更新order状态后,我们还需要调用sort函数来根据更新后的顺序重新对表格进行排序。为了推断出sortKey变量的正确类型,我们可以使用类型转换引用Data类型的键,如as keyof Data[0]。作为第二个参数,我们还需要传递更新后的顺序。

    Ordering the table

    处理过滤过度

    为了完成这个项目,让我们为过滤过度的状态添加一些指示。只有在没有结果时,我们才想要显示过滤过度的状态。这可以通过检查sortedRows状态的length来轻松实现。在<table>元素之后,添加以下内容:

    return (  <>    <div className="controls">...</div>    <table>...</table>    {!sortedRows.length && (      <h1>No results... Try expanding the search</h1>    )}  </>)

    过滤过度状态

    结论

    总而言之,在React中构建可排序和可过滤的表格并不复杂。通过使用数组方法和函数链式调用,使用正确的功能,我们可以创建简洁和精确的函数来处理这些任务。包括全部内容,我们设法将整个逻辑放在不到100行代码中。

    正如在本教程的开始处所见,整个项目可以在GitHub上一次性获得。感谢您的阅读;祝编码愉快!

    分享本文


    Leave a Reply

    Your email address will not be published. Required fields are marked *