12.12【java exp4】react table全局搜索tailwindcss 布局 (Layout) css美化 3. (rowId: number
react table
- 创建一个下拉菜单,允许用户选择要搜索的列。
- 创建一个输入框,用于输入搜索关键词。
- 根据用户的选择,动态地应用过滤器到指定的列
全局搜索
import React from 'react';
import { useTable, useFilters, useGlobalFilter, useSortBy, usePagination } from 'react-table';
// 自定义过滤组件
function ColumnFilter({ column }) {
const { filterValue, setFilter } = column;
return (
<input
value={filterValue || ''}
onChange={(e) => setFilter(e.target.value)}
placeholder={`Search ${column.id}`}
style={{ marginBottom: '0.5rem' }}
/>
);
}
const data = [
{ id: 1, name: 'Alice', age: 24, email: 'alice@example.com' },
{ id: 2, name: 'Bob', age: 30, email: 'bob@example.com' },
{ id: 3, name: 'Charlie', age: 28, email: 'charlie@example.com' },
];
const columns = React.useMemo(
() => [
{
Header: 'ID',
accessor: 'id',
Filter: ColumnFilter, // 为 ID 列添加过滤器
},
{
Header: 'Name',
accessor: 'name',
Filter: ColumnFilter, // 为 Name 列添加过滤器
},
{
Header: 'Age',
accessor: 'age',
Filter: ColumnFilter, // 为 Age 列添加过滤器
},
{
Header: 'Email',
accessor: 'email',
Filter: ColumnFilter, // 为 Email 列添加过滤器
},
],
[]
);
const DataTable = () => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state: { globalFilter },
setGlobalFilter,
} = useTable(
{
columns,
data,
defaultColumn: { Filter: ColumnFilter }, // 为所有列添加默认过滤器
},
useFilters, // 启用列过滤
useGlobalFilter, // 启用全局过滤
useSortBy, // 启用排序
usePagination // 启用分页
);
// 全局搜索输入框
const [searchInput, setSearchInput] = React.useState('');
const handleSearchChange = (e) => {
const value = e.target.value || '';
setSearchInput(value);
setGlobalFilter(value);
};
return (
<div>
{/* 全局搜索栏 */}
<div>
<input
value={searchInput}
onChange={handleSearchChange}
placeholder="Search all columns..."
style={{ marginBottom: '1rem' }}
/>
</div>
{/* 表格 */}
<table {...getTableProps()} style={{ border: 'solid 1px blue' }}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
{/* 显示排序图标 */}
<span>
{column.isSorted ? (column.isSortedDesc ? ' 🔽' : ' 🔼') : ''}
</span>
{/* 渲染列过滤器 */}
<div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
);
};
export default DataTable;
tailwindcss
className
是 React 中用于指定 CSS 类名的一个属性,它并不是 Tailwind CSS 特有的,而是 React 生态系统中的一个标准属性。Tailwind CSS 是一个实用优先的 CSS 框架,可以帮助你快速构建现代化的用户界面,但它并不会改变 className
属性的本质
-
引入 Tailwind 样式: 在你的项目的入口 CSS 文件(通常是
src/index.css
)中添加以下内容:@tailwind base; @tailwind components; @tailwind utilities;
Tailwind CSS 是一个高度可定制的、低级别的 CSS 框架,它提供了一套实用工具类(utility-first),而不是预定义的组件样式。与传统的 CSS 框架(如 Bootstrap)不同,Tailwind 不提供现成的按钮、卡片等组件样式,而是通过组合简单的 CSS 类来构建自定义的设计。这使得你可以完全控制设计的每个细节,同时保持代码的简洁和一致性
@tailwind base:
这个指令会引入 Tailwind CSS 的基础样式,包括归一化样式(Normalize CSS)和一些基本的 HTML 元素样式。
例如,它会重置浏览器的默认样式,确保不同浏览器之间的样式一致性。
@tailwind components:
这个指令会引入 Tailwind CSS 的组件样式。
组件样式通常用于定义一些常见的 UI 组件,如按钮、表单元素等。
你可以在这里添加自己的组件样式,或者使用 Tailwind CSS 提供的默认样式。
@tailwind utilities:
这个指令会引入 Tailwind CSS 的实用工具类(Utility Classes)。
实用工具类是一些预定义的 CSS 类,用于快速调整元素的样式,如 text-center、bg-blue-500 等。
这些类可以在 HTML 中直接使用,无需编写额外的 CSS 代码。
- Tailwind CSS 提供了大量的低级别的、原子化的类名,每个类名对应一个特定的样式属性。
- 这些类名可以在 HTML 或 JSX 中直接使用,而不需要编写额外的 CSS 文件
- 由于类名是预先定义好的,开发人员可以立即看到样式的变化,无需来回切换文件进行调试。
- 这提高了开发效率,减少了上下文切换的时间。
-
布局 (Layout)
-
Flexbox 和 Grid:
整体布局:
使用 p-4 为整个组件添加内边距。
使用 flex 和 items-center 使搜索栏中的元素在同一行对齐。
选择列和搜索输入框:
使用 mr-2 添加右侧外边距。
使用 p-2 添加内边距。
使用 border 和 rounded 添加边框和圆角。
表格:
使用 w-full 使表格宽度占满父容器。
使用 border 和 border-gray-300 添加边框。
使用 bg-gray-100 为表头背景添加浅灰色。
使用 p-2 和 text-left 为表头单元格添加内边距和左对齐。
使用 border-b 和 border-gray-300 为表头和表格行添加底部边框。
分页控制:
使用 mt-4 为分页控件添加顶部外边距。
使用 flex 和 justify-between 使分页按钮和页面信息分别位于两端。
使用 px-2 和 py-1 为按钮添加水平和垂直内边距。
使用 mr-2 为按钮之间添加右侧外边距。
使用 bg-blue-500 和 text-white 为按钮添加蓝色背景和白色文字。
使用 rounded 为按钮添加圆角。
使用 disabled:bg-gray-300 为禁用状态的按钮添加浅灰色背景。
使用 ml-2 为输入框和选择框添加左侧外边距
css美化
如果你导入了多个 CSS 文件,并且这些文件中有重叠的样式规则,CSS 的层叠机制(Cascading)和优先级(Specificity)将决定最终应用的样式。
CSS 层叠机制决定了当多个规则应用于同一个元素时,哪个规则会生效。
规则的优先级顺序如下:
内联样式(Inline styles)
ID 选择器(ID selectors)
类选择器、属性选择器和伪类(Class selectors, attribute selectors, and pseudo-classes)
标签选择器和伪元素(Element selectors and pseudo-elements)
通用选择器、子选择器和相邻兄弟选择器(Universal selectors, child combinators, and adjacent sibling combinators)
优先级是根据选择器的复杂度来计算的。优先级越高,规则越有可能被应用。
计算方法:
内联样式:1000
ID 选择器:100
类选择器、属性选择器和伪类:10
标签选择器和伪元素:1
通用选择器、子选择器和相邻兄弟选择器:0
如果两个规则具有相同的优先级,那么后导入的样式会覆盖先导入的样式。
:root:
:root 选择器表示文档的根元素,在 HTML 中就是 <html> 标签。
在 :root 中定义的 CSS 变量可以在整个文档中使用。
--background 和 --foreground:
--background 和 --foreground 是自定义的 CSS 变量。
--background 被设置为白色(#ffffff)。
--foreground 被设置为深灰色(#171717)
@media (prefers-color-scheme: dark):
这是一个媒体查询,用于检测用户的偏好颜色方案是否为暗模式。
prefers-color-scheme: dark 会匹配用户设置了暗模式的情况。
--background 和 --foreground 的重定义:
在暗模式下,--background 被重新设置为深黑色(#0a0a0a)。
--foreground 被重新设置为浅灰色(#ededed)。
color 和 background:
color 属性使用 var(--foreground) 变量,这意味着文本颜色会根据用户的颜色方案(亮模式或暗模式)自动调整。
background 属性使用 var(--background) 变量,这意味着背景颜色也会根据用户的颜色方案自动调整。
font-family:
font-family 属性设置了默认的字体系列,使用 Arial、Helvetica 和 sans-serif 作为备选字体。
要使表格更加紧凑,可以通过调整 CSS 样式来减少单元格的内边距(padding)、边框宽度(border-width)以及其他相关的间距属性。以下是具体的调整步骤和代码示例:
1. 减少单元格内边距
通过减少 p-2 类中的内边距值,可以使表格单元格更加紧凑。
2. 调整边框宽度
可以将边框宽度从 border 改为更细的边框,例如 border-t 和 border-b 来减少水平方向的边框。
3. 调整表格整体宽度
可以将表格的宽度从 w-full 改为一个固定宽度或百分比宽度,以控制表格的整体尺寸。
减少外边距和内边距:
将 p-4 改为 p-2 或 p-1。
将 mb-4 改为 mb-2。
将 px-2 py-1 改为 px-1 py-0.5。
调整边框:
将 border 改为 border-t border-b,以减少水平方向的边框。
调整表格整体宽度:
保持 w-full,但可以根据需要调整为固定宽度或百分比宽度。
mt-2: 设置上外边距(margin-top)为 0.5rem(即 8px)。这是为了在该 div 的上方添加一些间距。
flex: 使用 Flexbox 布局,使得子元素可以按照 Flexbox 规则进行排列。
justify-between: 在主轴(默认为水平轴)上将子元素均匀分布,第一个子元素在起始位置,最后一个子元素在结束位置。
items-center: 在交叉轴(默认为垂直轴)上将子元素居中对齐。
综合起来,这段代码的作用是创建一个具有 Flexbox 布局的 div,其中子元素会在水平方向上均匀分布,并且在垂直方向上居中对齐。这个 div 还会在其上方添加一些间距。
container mx-auto p-4 是 Tailwind CSS 类,container 类会自动设置最大宽度,并使其居中,mx-auto 使容器水平居中,p-4 添加内边距。
为了清晰地展示页面的边界,你可以通过多种方式来实现,包括添加边框、背景颜色、阴影效果等。以下是几种常见的方法:
1. 添加边框
你可以在父容器上添加边框,以便更清楚地看到其边界。例如:
从你提供的 RootLayout 组件来看,page.tsx 不是最顶层的组件。RootLayout 是 Next.js 应用程序的根布局组件,它包裹了所有的页面组件(包括 page.tsx)。以下是详细的解释:
1. 根布局组件 (RootLayout)
RootLayout 是 Next.js 应用程序的根布局组件,定义在 app/layout.tsx 或类似的文件中。它负责定义整个应用程序的基本结构和样式。
RootLayout: 根布局组件,包裹所有页面组件。
page.tsx: 具体的页面组件,代表一个具体的页面。
因此,page.tsx 不是最顶层的组件,而是被 RootLayout 包裹的一个页面组件。
4. 确保页面占满整个视口
为了确保 page.tsx 占满整个视口,你需要确保 RootLayout 和 page.tsx 的样式设置正确。以下是具体的步骤:
4.1 设置 RootLayout 的样式
确保 RootLayout 的 body 标签高度为 100%。
?
表示这个属性是可选的。也就是说,使用该类型的对象时,onRowClick
不一定是必需的。如果父组件没有提供 onRowClick
回调函数,那么它将不会被触发。
- 有
onRowClick
的情况:如果父组件传递了onRowClick
函数,那么当用户点击某一行时,该函数将被调用。 - 没有
onRowClick
的情况:如果父组件没有传递onRowClick
函数,那么点击行时不会触发任何回调。
3. (rowId: number | string) => void
回调函数签名
这部分定义了 onRowClick
回调函数的参数和返回值类型:
-
参数
rowId
:回调函数接受一个参数rowId
,表示被点击行的唯一标识符。rowId
的类型可以是number
或string
,这意味着它可以是一个数字或字符串,具体取决于你如何标识每一行。number
:如果你使用数字作为行的唯一标识符(例如,数据库中的自增 ID),那么rowId
将是一个数字。string
:如果你使用字符串作为行的唯一标识符(例如,UUID 或其他字符串格式的 ID),那么rowId
将是一个字符串。
-
返回值
void
:回调函数不返回任何值。void
表示该函数的执行结果是空的,即它不会返回任何数据。
回调函数定义
通过将 onRowClick
定义为回调函数,你可以实现父子组件的解耦。这意味着子组件(例如 TableComponent
)不需要知道父组件(例如 App
)的具体实现细节,它只需要负责触发事件,而具体的业务逻辑由父组件来处理。
- 子组件的责任:
TableComponent
只负责渲染表格,并在用户点击某一行时调用传递给它的onRowClick
回调函数。 - 父组件的责任:
App
组件可以根据业务需求定义onRowClick
的具体行为,例如导航到详情页面、显示模态框、更新状态等。
这种方式使得代码更加模块化和可维护,因为每个组件只关心自己的职责,而不依赖于其他组件的内部实现。
使用回调函数可以让你根据不同的场景灵活地定义不同的行为。假设你有多个地方使用了 TableComponent
,但每次点击行时的行为不同:
- 在一个页面上,点击行可能需要导航到该行的详细信息页面。
- 在另一个页面上,点击行可能需要弹出一个编辑表单。
- 在第三个页面上,点击行可能只是简单地高亮该行。
通过将 onRowClick
定义为回调函数,你可以在不同的地方传递不同的实现,而不需要修改 TableComponent
的代码。这大大提高了组件的复用性和灵活性。
回调函数 是一种编程模式,其中一个函数作为参数传递给另一个函数,并在适当的时机被调用。通过这种方式,父组件可以定义具体的业务逻辑,而子组件只需要负责触发事件并调用回调函数。
1. 将函数作为参数传递
是的,回调函数的核心思想就是将函数作为参数传递。JavaScript 中的函数是一等公民(first-class citizens),这意味着你可以像传递其他类型的值(如数字、字符串)一样传递函数。你可以将函数作为参数传递给另一个函数,并在适当的时候调用它。
2. 父组件实现,子组件调用
在 React 中,父组件可以通过 props 将回调函数传递给子组件。子组件可以在特定事件发生时(例如用户点击某一行),调用这个回调函数并将相关数据(如 rowId
)作为参数传递回去。父组件可以根据传回的数据执行相应的业务逻辑。
在 React 中,数据流是单向的,即父组件通过 props 向子组件传递数据,而子组件不能直接修改父组件的状态。但是,子组件可以通过回调函数将事件或数据传递回父组件,从而让父组件根据这些信息更新自己的状态。
- 子组件触发事件:用户点击表格中的一行,触发
onClick
事件。 - 子组件调用回调函数:
onClick
事件处理器调用父组件传递的onRowClick
回调函数,并将row.id
作为参数传递给它。 - 父组件更新状态:父组件中的
handleRowClick
回调函数被调用,接收row.id
作为参数,并更新父组件的状态(例如,设置isModalOpen
为true
,并保存selectedRowId
)。 - 父组件重新渲染:由于父组件的状态发生了变化,React 会重新渲染父组件及其子组件。此时,弹窗会根据
isModalOpen
的值显示出来,并显示选中的行 ID。
关键点:子组件无法直接修改父组件的状态
你提到“子组件显然是无法修改父组件里的数据的”,这是正确的。在 React 中,子组件确实不能直接修改父组件的状态。但是,通过回调函数,子组件可以将事件或数据传递给父组件,父组件可以根据这些信息更新自己的状态。这就是为什么我们说 React 的数据流是单向的,但仍然可以通过回调函数实现从子组件到父组件的通信。
在 JavaScript 中,函数调用确实会形成一个调用栈,但 React 的事件处理机制并不是基于递归调用栈的工作方式。相反,React 使用的是合成事件系统,它会将事件处理程序绑定到最外层的 DOM 节点上,并通过事件冒泡机制将事件传递给相应的组件。
具体来说,当用户点击表格中的一行时:
- 事件首先被捕获并传递给最外层的 DOM 节点(例如,
document
或body
)。 - 然后,React 会根据事件的目标元素(即被点击的行),找到对应的 React 组件(即
TableComponent
)。 - 最后,React 会调用该组件的事件处理程序(即
onClick
),并执行回调函数。
因此,虽然子组件不能直接修改父组件的状态,但它可以通过回调函数将事件或数据传递给父组件,父组件再根据这些信息更新自己的状态。这种设计确保了数据流的单向性,同时保持了组件之间的解耦和灵活性。
总结
- 父组件管理状态:父组件负责维护是否弹出弹窗的状态,并提供回调函数来更新这个状态。
- 子组件触发回调函数:子组件在用户点击某一行时调用父组件传递的回调函数,并将行 ID 作为参数传递给父组件。
- 父组件更新状态:父组件根据传回的行 ID 更新自己的状态(例如,显示弹窗)。
- 单向数据流:子组件不能直接修改父组件的状态,但可以通过回调函数将事件或数据传递给父组件,由父组件决定如何处理。
React.useMemo
在使用 React.useMemo 时,有两个主要参数:factory 函数 和 依赖数组(deps)。下面分别解释这两个概念以及你在代码中提到的 classes.map((cls) => 中的 cls: Class 的含义。
定义:factory 是一个函数,返回你想要缓存的值。
作用:当依赖数组中的值发生变化时,factory 函数会被重新执行,计算新的值;否则,会返回之前缓存的值。
依赖数组(deps)
定义:deps 是一个数组,包含所有在 factory 函数中使用的外部变量。
作用:当数组中的任何一个变量发生变化时,factory 函数会被重新执行;否则,useMemo 返回缓存的值。
定义:classes.map((cls) => { ... }) 是对 classes 数组中的每个元素进行遍历,并对每个元素执行提供的回调函数。
cls: Class:
cls 是 classes 数组中的每一个元素。
Class 是 TypeScript 中的类型注解,表示 cls 的类型是 Class。
这个类型注解是由你的 IDE(如 IntelliJ IDEA)自动推断出来的,基于 classes 数组的类型定义。