Wuxh

Front-end Development

0%

Ant Design 自定义组件空状态速记

最近在 antd 社区看到一个关于 Table fitler 查询条件下拉框的空状态有关 PR

发现 antd 的很多逻辑并没有在文档详细说明,索性写一个速记笔记方便自己日后 CV。

笔记具有一定时效性,请注意当前笔记记录时 antd 最新版本是 5.18.x

前排先总结

  1. 自定义要求不高,仅仅只是为空状态做一个静态展示,没有任何交互。可以使用 <ConfigProvider />renderEmpty 全局修改。(⚠️ 没办法具体细分到是否是 <Select /> 还是 <TreeSelect />

  2. 强自定义需求,空状态需要交互按钮等操作,不建议使用 <ConfigProvider /> 就近套组件,而是通过组件本身的 props 去实现

通过 defaultRenderEmpty 文件的历史记录不难发现目前支持自定义组件空状态的组件有 TableListSelectTreeSelectCascaderTransferMentions

带误区方案一

以往我自己的做法是通过为当前组件就近套 <ConfigProvider /> 组件用 renderEmpty 以实现自定义空状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import { ConfigProvider, Table } from 'antd';
import type { TableProps } from 'antd';

interface MyTableProps<T> extends TableProps<T> {
empty?: React.ReactNode;
}

const MyTable = <T extends Record<any, any>,>(props: MyTableProps<T>) => {
const { empty, ...restProps } = props;
return (
<ConfigProvider renderEmpty={() => empty}>
<Table<T> {...restProps} />
</ConfigProvider>
)
};

export { MyTable, type MyTableProps };

上面这个例子看起来没太大问题,但其实埋了一个坑,因为 <ConfigProvider/> 是一个配置上下文,如果在 Table 中有一个声明式的 <Select /> 或者 <Cascader /> 则也会一同被修改。

😰示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function App() {
const data = [
{ name: 'Tom', age: 20 },
{ name: 'Jerry', age: 22 },
];
const columns = [
{ title: 'Name', key: 'name' },
{
title: 'Age',
key: 'age',
render: () => <Select style={{ width: 180 }} options={[]} /> // 这里的空状态会被 CP 的 renderEmpty 覆盖
},
]
return <MyTable columns={columns} dataSource={data} empty={<div>Custom Empty</div>} />
}

解决上面这个问题也很简单,通过 renderEmpty 提供的 入参来判断。(这里我们还是用了 CP组件 )

1
2
3
4
5
6
   const { empty, ...restProps } = props;
return (
- <ConfigProvider renderEmpty={() => empty}>
+ <ConfigProvider renderEmpty={(components) => components === 'Table' ? empty : void 0}>
<Table<T> {...restProps} />
</ConfigProvider>

举一反三,如果我们需要自定义 <Select /> 的空状态其实也可以用就近套 <ConfigProvider />renderEmpty 的方式解决。

你以为这就结束了吗,继续往下看…

据我审查了 antd 代码,支持自定义 Empty 的组件中的 TreeSelectMentions 这两个组件的 renderEmpty 入参是不符合预期的(不知道是否有意如此),从 4.0.0 其就是这样,没有完全根据 defaultRenderEmpty 逻辑的 Switch Case 入参。

所以通过就近为组件套 <ConfigProvider /> 不是一个很好的方案

但是想全局设置或许只有这一个解决方案,在 App 入口处全局配置一遍。

另外这里顺带提一句 <ConfigProvider />renderEmpty 可能不需要兜底,我们默认交给 antd 去兜底即可(因为当前 api 实现,我们的兜底行为,可能会导致 antd 上游添加 feature 时不好抉择,会出现 BREAKCHANGE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function App() {
const columns = [
{ title: 'Name', key: 'name' },
]

return (
<ConfigProvider
renderEmpty={(components) => {
if (components === 'Table') {
return 'foo'
}
if (components === 'List') {
return 'bar'
}
// 以下兜底行为推荐不写,交由 antd 默认处理
return 'baz'
}}
>
<MyTable columns={columns} dataSource={[]}/>
</ConfigProvider>
)
}

方案二

单独使用组件的 props 实现自定义, 但是失去全局统一配置的便捷

Table/List

审查代码发现 <Table /> 其实还可以通 locale.emptyText 去自定义空状态, 其类型定义emptyText?: React.ReactNode | (() => React.ReactNode);

所以 <Table /> 可以通过这样去实现空状态

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
function App() {
const columns = [
{ title: 'Name', key: 'name' },
]

const openFormAddData = () => {
// some code
}

return (
<Table
columns={columns}
dataSource={[]}
locale={{
emptyText: (
<>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={null}/>
<Button type="primary" onClick={openFormAddData}>
Add Data
</Button>
</>
)
}}
/>
)
}

注意 <List />locale.emptyText 类型定义emptyText: React.ReactNode;,同样也能很方便的自定义。

1
2
3
4
5
6
7
8
9
function App() {
return (
<List
locale={{
emptyText: <Empty description="No data" />
}}
/>
)
}

Select/TreeSelect/Mentions/Cascader

这几个组件都提供了 notFoundContent props 来实现自定义空状态

1
2
3
4
5
6
7
function App() {
return (
<Component
notFoundContent={<Empty description="No data" />}
/>
)
}

Transfer

<Transfer /><Table /> 差不多,支持通过 locale.notFoundContent 其类型定义 notFoundContent?: React.ReactNode | React.ReactNode[];

1
2
3
4
5
6
7
8
9
10
11
12
function App() {
return (
<Transfer
locale={{
notFoundContent:[
<Empty description="Not Source"/>,
<Empty description="Not Target"/>,
]
}}
/>
)
}

另外 <Transfer /> 在通过 <ConfigProvider />renderEmpty 全局修改时也可以 return 一个 React.ReactNode[] 支持自定义穿梭框左右两侧不同的空状态😬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function App() {
return (
<ConfigProvider
renderEmpty={(components) => {
if (components === 'Transfer') {
return [
<Empty description="Not Source" />,
<Empty description="Not Target" />,
]
}
}}
>
<Transfer />
</ConfigProvider>
)
}

最后

其中提到的 <Table /><List /><Transfer /> 虽然都可以通过 locale.XXX 方式自定义。但是我们不能全局 <ConfigProvider locale={{ xxx: xxx}} /> 方式来实现全局配置 (虽然 TS 类型推导有提升..

比如这样的代码是不行的 ❌❌❌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function App() {

return (
<ConfigProvider
locale={{
...enUS,
Table: {
...enUS.Table,
emptyText: 'No data'
},
Transfer: {
...enUS.Transfer!,
notFoundContent: 'No data'
}
}}
>
{/*...*/}
</ConfigProvider>
)
}

上面提到的几个组件自定义空状态方式都有些不同,所以我们只能自行二次开发用自己的 context 实现,来抹平 props 差异。

OK,再会~

欢迎关注我的其它发布渠道