

react 通用列表组件封装 - rxliuli blog
source link: https://blog.rxliuli.com/p/c55a6470683e498f92ba05d7ff710b3a/
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.

re_
1.9k 字
27 分钟
本文最后更新于:2021年6月21日 晚上
在后台项目中,即便使用了 antd,仍然存在太多太多的列表页面。这些列表页面大多数又是非常相似的,所以吾辈需要解决重复的简单列表的编写,避免每次都手动控制过滤器/分页之类的东西,将之抽象成配置项,然后通过配置生成列表页面。或许已经有很多人做过了这件事情,但于吾辈而言,这仍然是全新的体验,所以也便于此记录,并供之以他人参考。
使用逐级递进的方式进行封装,使用者可以根据需求停留在一个合适的封装层次,使用不同封装层次的组件。
BasicList
:高层列表封装组件ListHeader
:列表页顶部工具栏组件CommonHeader
: 通用的顶部工具栏组件
ListFilter
:过滤器组件FilterSelect
:单选过滤器FilterTimeRange
:时间区间过滤器FilterSlot
:自定义过滤器
ListTable
:列表封装组件
BasicList 使用步骤
graph TD;
A[添加 api class 实现 BasicListApi] --> B[添加配置项]
B --> C{是否需要动态修改};
C --> |是| C1[使用 useMemo 声明]
C --> |否| C2[将之抽离到组件外部声明为常量]
C1 --> D
C2 --> D
D{是否有非通用 Filter}
D --> |是| D1[自定义 slot filter]
D --> |否| D2[使用 select/time filter]
D1 --> E
D2 --> E
E{是否需要自定义表格相关功能}
E --> |是| E1[使用 filterOperate 配置]
E --> |否| E2[不声明 filter 配置]
E1 --> F
E2 --> F
F[列表渲染]
BasicList 渲染流程
graph TB
A[从 props 拿到配置项列表] --> B{header 是否为 ReactElement}
B --> |是| B1[直接渲染]
B --> |否| B2[使用 ListHeader 渲染 header 配置项]
B1 --> C
B2 --> C
C{filters 是否存在}
C --> |是| C1{filters 是否为 ReactElement}
C1 --> |是| C1A[直接渲染]
C1 --> |否| C1B[使用 ListFilter 渲染 filters 配置项] --> C1C[监听 initialParams 变化, 以随时修改 form]
C --> |否| C2[不渲染]
C1A --> D
C1B --> D
C2 --> D
D[渲染列表]
使用基本 API
如下所示,我们想要构造下面这样一个简单的列表页面,包含一个面包屑导航列表、搜索框、过滤条件选择器和一个表格。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import * as React from "react";
import { Button } from "antd";
import { Link } from "react-router-dom";
import { Moment } from "moment";
import { LabeledValue } from "antd/es/select";
import { userApi } from "./api/UserApi";
import {
BasicList,
BasicListPropsType,
FilterFieldTypeEnum,
} from "../../components/list";
type PropsType = {};
type Config = Omit<BasicListPropsType, "params"> & {
params?: {
keyword?: string;
test?: number;
birthdayTimeRange?: [Moment, Moment];
};
};
const testOptionList: LabeledValue[] = [
{ value: 0, label: "测试 0" },
{ value: 1, label: "测试 1" },
{ value: 2, label: "测试 2" },
];
//列表配置项
const config: Config = {
header: {
placeholder: "用户名/住址",
list: ["用户", "列表"],
},
filters: [
{
type: FilterFieldTypeEnum.Select,
label: "测试字段",
field: "test",
options: testOptionList,
},
{
type: FilterFieldTypeEnum.TimeRange,
label: "生日",
field: "birthdayTimeRange",
},
],
columns: [
{ field: "id", title: "ID" },
{ field: "name", title: "姓名" },
{ field: "birthday", title: "生日" },
{
field: "operate",
title: "操作",
slot: (param) => <Link to={`/system/user/${param.record.id}`}>详情</Link>,
},
],
api: userApi,
};
/**
* 一个基本的列表页面
* @constructor
*/
const BasicListExample: React.FC<PropsType> = () => {
return <BasicList {...config} />;
};
export default BasicListExample;
使用自定义过滤器组件
事实上,总有各种奇怪的过滤器无法满足,这时候就需要添加一个自定义的过滤器了。
例如下面这个过滤器,包含了年龄的值和单位,是不是感觉很奇怪
关键代码配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const config: Config = {
filters: [
{
type: FilterFieldTypeEnum.Slot,
label: "年龄",
field: "age",
children: (
<Input.Group compact>
<Form.Item name={["age", "value"]} noStyle>
<InputNumber style={{ width: "calc(100% - 64px)" }} />
</Form.Item>
<Form.Item name={["age", "unit"]} noStyle>
<Select style={{ width: 64 }} options={ageUnitOptionList} />
</Form.Item>
</Input.Group>
),
},
],
// 此处是为了添加过滤器的默认值
params: {
age: {
unit: 0,
},
},
};
添加表格的额外操作
有时候,我们需要添加一个额外的表格操作,例如导出/导入数据/删除选中数据。
关键代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function handleBatchDelete({
selectedRowKeys,
setSelectedRowKeys,
searchPage,
}: ListTableOperateParam) {
if (selectedRowKeys.length === 0) {
return
}
await userApi.batchDelete(selectedRowKeys)
setSelectedRowKeys([])
await searchPage()
}
const config = useMemo<Config>(
() => ({
tableOperate: (params) => (
<Button onClick={() => handleBatchDelete(params)}>删除选中</Button>
),
}),
[],
)
过滤器的下拉框数据来源是异步的
很多时候,我们的数据来源并不是由前端写死,而是从后台获取的,这就要求我们传入的值是 react 的一个 State 而非一个固定值。
关键代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const testOptionList = useAsyncMemo([], dictApi.list)
const config = useMemo<Config>(
() => ({
filters: [
{
type: FilterFieldTypeEnum.Select,
label: '测试字段',
field: 'test',
options: testOptionList,
},
],
}),
[testOptionList],
)
组件 API
BasicList
参考 src/components/common/table/js/BasicListOptions.d.ts
prop
类型
说明
header
BasicList.Header
标题栏相关配置
[filters]
BaseFilterField[]
过滤器列表
((params: any, onChange: (params: any) => void) => ReactElement)
[params]
Params
查询参数
columns
TableColumn[]
列字段列表
api
BaseListApi
api 对象
[tableOptions]
TableOptions
一些其他选项
[tableOperate]
ListTableOperate
一些其他操作
ListFilter
| prop
| 类型 | 说明 |
| —————- | ——————– | ———————— | —————— | ———- |
| [initialValue]
| any
| 查询参数 |
| filters
| (FilterSelectType | FilterTimeRangeType | FilterSlotType)[]
| 过滤器列表 |
| onChange
| (value: T) => void
| 当过滤器的参数发生改变时 |
ListTable
prop
类型
说明
columns
TableColumn[]
列字段列表
api
BaseListApi
api 对象
params
Params
查询参数
[tableOptions]
TableOptions
一些其他选项
[tableOperate]
ListTableOperate
一些其他选项
其他类型定义
下面是类型定义,所有的类型定义都有对应的 .d.ts
文件,请使用 C-N
搜索 class
。
HeaderNavItem
类型
说明
string
导航的名字
name
string
导航的名字
[link]
string
如果是 route 的话必须有值
Header
类型
说明
list
HeaderNavItem[]
导航元素列表
placeholder
string
搜索框的提示文本
过滤器相关
FilterFieldTypeEnum
类型
说明
Slot
1
自定义 slot
Select
2
普通选择框
TimeRange
3
日期区间选择器
FilterFieldBase
类型
说明
type
FilterFieldTypeEnum
过滤器元素类型
label
string
显示的标题
FilterSelectType
类型
说明
extends
FilterFieldBase
继承基本过滤器配置
field
string
字段名
options
LabeledValue[]
值列表
FilterTimeRangeType
类型
说明
extends
FilterFieldBase
继承基本过滤器配置
field
string
字段名
FilterSlotType
类型
说明
extends
FilterFieldBase
继承基本过滤器配置
field
string
字段名
children
ReactElement
Form.Item
的子元素
[computed]
(res: Record<string, any>, value: any) => Record<string, any>
自定义计算方法
Params
类型
说明
keyword
string
查询关键字
...args
any[]
其他查询参数
TableColumn
类型
说明
field
string
在数据项中对应的字段名
title
string
列标题
[formatter]
(v: any, record: any) => any
自定义字段格式化函数
[slot]
(param: { text: string; record: any; i: number }) => ReactNode
自定义 slot
BaseListApi
类型
说明
pageList
(params: any) => Promise<PageRes<any>>
所有 ListTable 中的 api 对象必须实现该类型
ListTableOperateParam
类型
说明
searchPage
(page?: PageParam) => Promise<void>
导航元素列表
selectedRowKeys
string[]
当前选中行的主键
setSelectedRowKeys
(selectedRowKeys: string[]) => void
设置当前选中行的主键
page
PageData<any>
分页数据信息
params
Params
过滤器及搜索参数
TableOptions
类型
说明
[isSelect]
boolean
是否可选,默认为 false
[rowKey]
string
行的唯一键,默认为 id
相比于之前吾辈 Vue 表格封装 BasicTableVue,嗯,差距非常明显!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK