React智能数据表构建完整指南
作者:帕拉马南塔姆·哈里森✏️
表格型用户界面在网络产品中非常常见,因为它是在用户界面中组织复杂数据的最简便方法之一。许多公司使用数据表来展示复杂的报表。
表格用户界面的一些常见用例包括显示财务报告数据、体育排行榜以及定价和比较页面。
大量使用数据表的产品包括:
- Airtable
- Asana 列表视图
- Asana 时间线
- Google 表格
- 概念表
表格用户界面功能
数据表用户界面的基本功能包括:
- 毫不妥协的用户体验和用户界面。清晰易懂的字体和表格用户界面内的自定义元素。
- 远程数据调用以加载数据
- 搜索表格或特定列
- 基本筛选和排序选项
数据表用户界面中的高级功能包括:
- 根据数据类型(数字、字符串、布尔值、选择输入等)对列进行自定义排序和筛选选项
- 分页支持或无限长表格(针对超大型数据集的性能)
- 显示和隐藏列
- 支持对列数据进行内联编辑
- 支持通过模态/详细信息面板编辑整行数据。
- 固定标题和固定列,便于查看数据。
- 支持多设备(响应速度)
- 可调整列宽,以便在列中容纳较长的数据点(例如,多行注释)
- 支持水平和垂直滚动
- 可展开的行,用于显示该行的完整数据
表格用户界面中常见的用户体验挑战
从用户界面角度来看,数据表是清晰呈现复杂数据的最佳选择之一。但从用户体验角度来看,它却很棘手——当需要支持多种设备时,很容易出现问题。表格在用户体验方面面临的一些挑战包括:
响应能力
如果不改变布局以适应较小的屏幕尺寸,就很难使表格具有响应式设计。
滚动
表格可能需要双向滚动。浏览器默认的滚动条对于全宽表格来说效果不错,但大多数表格的宽度都是自定义的。自定义滚动条在触摸屏和非触摸屏上的支持都非常棘手。
管理列宽
根据数据长度管理列宽是一项棘手的任务。当我们在表格中加载动态数据时,这常常会导致用户体验问题。每次数据更改时,列宽都会调整,从而造成对齐错位。我们在设计用户体验时需要谨慎处理这些问题。
React 中用于数据表的顶级库
本文将介绍如何使用 React 构建一个简单的 Airtable 克隆版。我们将探索一些开源的 React 表格库,并选择最适合我们用例的库。
react-table
React - table是 React 中最常用的表格库之一。它在 GitHub 上拥有超过 7000 个 star,更新频繁,并支持 Hooks。React-table 非常轻量级,提供了任何简单表格所需的所有基本功能。
何时使用 react-table
当您的表格用户界面需要:
- 排序、筛选和分页等基本功能
- 在不影响功能的前提下,为表格设计自定义用户界面。
- 易于扩展;您可以使用自定义插件钩子在库的基础上构建自己的功能。
何时不应使用 react-table
当您需要时:
- 默认支持固定标题和列
- react-table 开箱即用,支持触控和非触控设备的水平和垂直滚动。它不定义用户界面;它是无头框架,因此我们需要根据自身需求来定义用户界面。
- 支持列的内联编辑。我们可以在 react-table 中实现这一点,但这超出了我们表格本身的功能范围。我们需要在其基础上创建一个插件或组件来支持此类功能。react-table 名副其实,最适合渲染简单的表格。
- 它无法处理像 Google 表格那样无限长的表格,性能方面也无法胜任。对于中等大小的表格,它表现良好,但对于过长的表格则不然。
用例
- 适用于需要搜索、排序、筛选等基本功能的简单表格。
- 体育排行榜/统计数据,带有自定义元素的财务数据表
react-data-grid
react-data-grid是另一个用于创建智能表格的库。它在 GitHub 上拥有近 4000 个 star,并且维护良好。
何时使用 react-data-grid
当您的数据表需要:
- 基本功能包括列分组、排序、搜索和筛选。
- 列的内联编辑
- 列内的下拉菜单(例如 Google 表格)或列内的任何自定义输入元素
- 支持展开列以显示更多数据
- 为了优化性能,例如,它支持无限长表格行的虚拟渲染。
- 支持无行时的空状态
何时不应使用 react-data-grid
react-data-grid 几乎涵盖了数据表的所有基本需求。但是,它默认不支持分页,因此如果你的表格需要分页,你需要手动实现和处理。默认情况下,react-data-grid 支持较长的表格 UI,并且针对性能进行了优化,因此除非用户体验有特殊要求,否则分页可能并非必要。
它还使用 Bootstrap 进行样式设计。即使不使用 Bootstrap,你仍然可以使用 react-data-grid,但那样你就需要自己为表格添加样式。与 react-table 相比,它的可定制性较差,react-table 允许你创建表格结构。在 react-data-grid 中,表格 UI 由库自动生成,因此它不太适合 UI 繁重的自定义页面。
虽然以上几点并不算是缺点,但在开始使用 react-data-grid 之前了解它们还是很有帮助的。
用例
当你需要构建一个类似于 Google Sheets 或 Airtable 的小型可编辑数据表,并具有良好的用户体验时,中级需求。
react-datasheet
react - datasheet与 react-data-grid 类似。它们在 GitHub 上拥有相近的 star 数和贡献数,并且同样是一个维护良好的库。
它主要专注于创建类似 Google Sheets 的应用程序。它内置了一些基本功能,可用于创建此类对用户体验要求较高的应用程序。但需要再次强调的是,它可能不适合创建包含表格的通用页面用户界面。
然而,与 react-data-grid 不同,它并未针对大型数据集进行优化,因此适用于需要类似 Sheets 功能的小型应用程序。它仅适用于这一种场景,并且与 react-data-grid 相比,其功能非常有限。
react-virtualized
顾名思义,react-virtualized在处理大型数据集时针对性能进行了深度优化。这个库并非严格意义上的表格库,它的功能远不止于此。它专门用于在用户界面上以不同的格式(例如网格、表格和列表)显示大型数据集。
我不会深入探讨这个库的细节,因为它功能远超我们的需求。
何时使用 react-virtualized
当数据量非常大时,表格的渲染性能是关键指标;在这种情况下,建议使用 react-virtualized。对于一般的使用场景,这个库就显得过于复杂,其 API 也过于高级。
用例
使用 react-virtualized 可以为大型数据集创建自定义时间线、包含无限长日历的图表以及复杂的 UI 元素。
那么,你应该选择哪个 React 表格库呢?
- 对于数据量有限、样式自定义程度低、交互性仅需少量功能(例如排序和筛选)的简单页面,请使用react_–_table。
- 要构建一个类似 Google Sheets 的迷你应用,但数据量有限,可以使用react-data-grid或react-datasheet。
- 对于类似 Google Sheets 或 Airtable 且拥有大型数据集的应用程序,请使用react-data-grid
- 当您处理非常大的数据集并且需要提供表格、网格以及更多选项的自定义 UI 时,请选择react-virtualized。
何时应该构建自己的表格用户界面
在某些情况下,您可能需要构建自己的表格用户界面:
- 当你的餐桌只是一个展示柜,互动性不强时
- 当您需要为表格定制用户界面时
- 当你需要一张非常轻便且没有任何功能的桌子时
用例
- 产品/营销页面,附有对比表格。
- 价格表
- 简单的表格,带有自定义样式,除了简单的弹出文本外,无需对列进行太多交互。
使用 React 构建智能表格 UI
理论就讲到这里——让我们开始构建一个简单的表格用户界面,它具备排序和搜索等基本功能react-table。我们将构建这个简单的表格,它拥有基本的搜索和排序功能。
首先,使用 create-react-app 创建一个 React 应用:
npx create-react-app react-table-demo
调用 TV Maze API 获取表格数据
这是API接口。我们将调用该接口,并使用搜索词“雪”来获取节目信息。
为了调用 API,我们需要安装axios:
yarn add axios
// App.js
import React, { useState, useEffect } from "react";
import Table from "./Table";
import "./App.css";
function App() {
// data state to store the TV Maze API data. Its initial value is an empty array
const [data, setData] = useState([]);
// Using useEffect to call the API once mounted and set the data
useEffect(() => {
(async () => {
const result = await axios("https://api.tvmaze.com/search/shows?q=snow");
setData(result.data);
})();
}, []);
return (
<div className="App"></div>
);
}
export default App;
我们创建一个名为 的状态data,组件挂载后,我们使用 Axios 调用 API 并设置data。
使用数据渲染一个简单的表格用户界面
现在我们将添加 react-table:
yarn add react-table
react-table 使用 Hooks。它有一个名为 `table_hook` 的主表格 Hook useTable,并且还有一个插件系统,可以添加插件 Hooks。因此,react-table 可以根据我们的自定义需求轻松扩展。
让我们用 Hook 创建基本的 UI useTable。我们将创建一个新Table组件,它接受两个 props:` datadata` 和 ` columnscolumns`。`data`data是我们通过 API 调用获取的数据,columns`columns` 是用于定义表格列(表头、行、行的显示方式等)的对象。稍后我们将看到代码示例。
// Table.js
export default function Table({ columns, data }) {
// Table component logic and UI come here
}
// App.js
import React, { useMemo, useState, useEffect } from "react";
import Table from "./Table";
function App() {
/*
- Columns is a simple array right now, but it will contain some logic later on. It is recommended by react-table to Memoize the columns data
- Here in this example, we have grouped our columns into two headers. react-table is flexible enough to create grouped table headers
*/
const columns = useMemo(
() => [
{
// first group - TV Show
Header: "TV Show",
// First group columns
columns: [
{
Header: "Name",
accessor: "show.name"
},
{
Header: "Type",
accessor: "show.type"
}
]
},
{
// Second group - Details
Header: "Details",
// Second group columns
columns: [
{
Header: "Language",
accessor: "show.language"
},
{
Header: "Genre(s)",
accessor: "show.genres"
},
{
Header: "Runtime",
accessor: "show.runtime"
},
{
Header: "Status",
accessor: "show.status"
}
]
}
],
[]
);
...
return (
<div className="App">
<Table columns={columns} data={data} />
</div>
);
}
export default App;
在这里,我们可以在列中创建多个表头和列组。我们创建了两级。
所有列都有一个访问器,用于访问data对象中的数据。我们的数据位于对象内部的show数组中——这就是为什么所有访问器都带有show.前缀的原因。
// sample data array looks like this
[
{
"score": 17.592657,
"show": {
"id": 44813,
"url": "http://www.tvmaze.com/shows/44813/the-snow-spider",
"name": "The Snow Spider",
"type": "Scripted",
"language": "English",
"genres": [
"Drama",
"Fantasy"
],
"status": "In Development",
"runtime": 30,
"premiered": null,
"officialSite": null,
"schedule": {
"time": "",
"days": [
]
}
...
},
{
// next TV show
}
...
]
让我们完成这个Table组件:
// Table.js
import React from "react";
import { useTable } from "react-table";
export default function Table({ columns, data }) {
// Use the useTable Hook to send the columns and data to build the table
const {
getTableProps, // table props from react-table
getTableBodyProps, // table body props from react-table
headerGroups, // headerGroups if your table have groupings
rows, // rows for the table based on the data passed
prepareRow // Prepare the row (this function need to called for each row before getting the row props)
} = useTable({
columns,
data
});
/*
Render the UI for your table
- react-table doesn't have UI, it's headless. We just need to put the react-table props from the Hooks, and it will do its magic automatically
*/
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>
);
}
我们将把 ` columnsand`传递data给useTable`。`。`。`。`。`。`。`。`。`。`。`。`。`。`。`。`表头`将通过遍历`,`。`。`。`。`。`。`。`。`。。表格headerGroups主体`行`将通过遍历`创建rows。` ...
{rows.map((row, i) => {
prepareRow(row); // This line is necessary to prepare the rows and get the row props from react-table dynamically
// Each row can be rendered directly as a string using the react-table render method
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
})}
</tr>
);
})}
这样,我们就渲染出了单元格和表头。但是我们的cell值只是字符串,即使是数组值也会被转换成逗号分隔的字符串值。例如:
// Genres array
show.genres = [
'Comedy',
'Sci-fi',
]
在表格中,它将简单地显示为一个以逗号分隔的字符串,例如:Comedy,Sci-fi。此时,我们的应用程序应该看起来像这样:
react-table 中的自定义样式
对于大多数使用场景来说,这个表格已经足够好了,但如果我们需要自定义样式呢?react-table 允许你为每个单元格定义自定义样式。可以在column对象中这样定义。让我们创建一个类似徽章的自定义元素来显示每个类型。
// App.js
import React, { useMemo } from "react";
...
// Custom component to render Genres
const Genres = ({ values }) => {
// Loop through the array and create a badge-like component instead of comma-separated string
return (
<>
{values.map((genre, idx) => {
return (
<span key={idx} className="badge">
{genre}
</span>
);
})}
</>
);
};
function App() {
const columns = useMemo(
() => [
...
{
Header: "Details",
columns: [
{
Header: "Language",
accessor: "show.language"
},
{
Header: "Genre(s)",
accessor: "show.genres",
// Cell method will provide the cell value, we pass it to render a custom component
Cell: ({ cell: { value } }) => <Genres values={value} />
},
{
Header: "Runtime",
accessor: "show.runtime",
// Cell method will provide the value of the cell, we can create custom element for the Cell
Cell: ({ cell: { value } }) => {
const hour = Math.floor(value / 60);
const min = Math.floor(value % 60);
return (
<>
{hour > 0 ? `${hour} hr${hour > 1 ? "s" : ""} ` : ""}
{min > 0 ? `${min} min${min > 1 ? "s" : ""}` : ""}
</>
);
}
},
{
Header: "Status",
accessor: "show.status"
}
]
}
],
[]
);
...
}
...
在这个例子中,我们通过该方法访问值Cell,然后返回计算值或自定义组件。
对于“运行时长”,我们计算小时数并返回自定义值。对于“类型”,我们循环并将该值传递给一个自定义组件,该组件会创建一个类似徽章的元素。
使用 react-table 自定义外观非常简单。完成此步骤后,我们的表格 UI 将如下所示:
这样,我们可以根据需要自定义每个单元格的样式。您可以根据数据值显示每个单元格的任何自定义元素。
添加搜索功能
让我们给表格添加一些功能。如果你查看react-table 的演示页面useFilters,你会发现他们已经提供了创建自定义智能表格所需的一切。他们的演示中唯一缺少的是全局搜索功能。所以我决定使用react-table 的 Hook 插件来实现这个功能。
首先,让我们创建一个搜索输入框Table.js:
// Table.js
// Create a state
const [filterInput, setFilterInput] = useState("");
// Update the state when input changes
const handleFilterChange = e => {
const value = e.target.value || undefined;
setFilterInput(value);
};
// Input element
<input
value={filterInput}
onChange={handleFilterChange}
placeholder={"Search name"}
/>
管理输入状态非常简单直接。但是现在,如何将此筛选值传递给我们的表格并筛选表格行呢?
为此,react-table 提供了一个不错的 Hook 插件,名为useFilters。
// Table.js
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
setFilter // The useFilter Hook provides a way to set the filter
} = useTable(
{
columns,
data
},
useFilters // Adding the useFilters Hook to the table
// You can add as many Hooks as you want. Check the documentation for details. You can even add custom Hooks for react-table here
);
在我们的示例中,我们将仅针对该Name列设置筛选器。为了在输入值更改时筛选名称,我们需要将第一个参数设置为列访问器或 ID 值,将第二个参数设置为搜索筛选值。
让我们更新一下handleFilterChange函数:
const handleFilterChange = e => {
const value = e.target.value || undefined;
setFilter("show.name", value); // Update the show.name filter. Now our table will filter and show only the rows which have a matching value
setFilterInput(value);
};
这是实现搜索功能后的用户界面:
这是一个非常基础的筛选器示例,react-table API 提供了多种筛选选项。您可以点击此处查看 API 文档。
为表格添加排序功能
让我们为表格实现另一个基本功能:排序。允许对所有列进行排序。这很简单——和筛选一样。我们需要添加一个名为 `sorting` 的插件钩子useSortBy,并创建样式以在表格中显示排序图标。它会自动处理升序/降序排序。
// Table.js
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
setFilter
} = useTable(
{
columns,
data
},
useFilters,
useSortBy // This plugin Hook will help to sort our table columns
);
// Table header styling and props to allow sorting
<th
{...column.getHeaderProps(column.getSortByToggleProps())}
className={
column.isSorted
? column.isSortedDesc
? "sort-desc"
: "sort-asc"
: ""
}
>
{column.render("Header")}
</th>
根据排序结果,我们添加类名sort-desc或sort-asc。我们还将排序属性添加到列标题中。
{...column.getHeaderProps(column.getSortByToggleProps())}
这将自动启用所有列的排序功能。您可以通过disableSortBy在列上设置选项来禁用特定列的排序。在我们的示例中,我们启用了所有列的排序功能。您可以尝试运行演示程序。
这是我们实现排序功能后的用户界面:
当然,你还可以进一步扩展这个演示——如果你需要帮助,请在评论区留言。一些扩展思路包括:
- 使用全局筛选器筛选多列。(提示:使用`\t`
setAllFilters代替 `\t`setFilter) - 创建分页并加载更多数据到表格中。
- 仅允许对特定字段进行排序(禁用
sortby对列的排序) - 不要将硬编码的搜索值传递给 TV Maze API,而是创建一个输入来直接搜索 TV Maze API(即,移除客户端过滤,并通过 API 添加服务器端电视节目搜索并更改数据)。
可以查看 react-table 的丰富示例页面来扩展这个演示。他们提供了非常全面的示例,几乎涵盖了所有用例,并能提供相应的解决方案。
结论
这就是添加排序功能后的最终演示效果。您可以在这里体验演示并查看其代码库。
我们已经学习了如何使用 React 构建表格 UI。创建表格来满足基本需求并不难,但请尽可能避免重复造轮子。希望你喜欢学习表格 UI 的相关知识——欢迎在评论区分享你使用表格的经验。
编者按:发现本文有误?您可以在这里找到正确版本。
插件:LogRocket,一款用于 Web 应用的 DVR

LogRocket是一款前端日志工具,可让您重现问题,如同在您自己的浏览器中发生一样。无需猜测错误原因,也无需用户提供屏幕截图和日志转储,LogRocket 即可让您重现会话,快速了解问题所在。它与任何框架的应用程序完美兼容,并提供插件来记录来自 Redux、Vuex 和 @ngrx/store 的额外上下文信息。
除了记录 Redux 操作和状态之外,LogRocket 还会记录控制台日志、JavaScript 错误、堆栈跟踪、包含标头和正文的网络请求/响应、浏览器元数据以及自定义日志。它还会对 DOM 进行插桩,记录页面上的 HTML 和 CSS,即使是最复杂的单页应用程序,也能生成像素级精确的视频。
免费试用。
这篇文章《在 React 中构建智能数据表的完整指南》最初发表于LogRocket 博客。
文章来源:https://dev.to/bnevilleoneill/the-complete-guide-to-building-a-smart-data-table-in-react-538p




