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

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

发现 antd 有关自定义空状态逻辑并没有在文档详细说明,索性写一个笔记方便自己日后 CV。

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

前排先总结

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

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

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

带误区方案一

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

tsx 复制代码
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 /> 则也会一同被修改。

😰示例代码

tsx 复制代码
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组件 )

diff 复制代码
   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)

tsx 复制代码
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 /> 可以通过这样去实现空状态

tsx 复制代码
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;,同样也能很方便的自定义。

tsx 复制代码
function App() {
  return (
    <List
      locale={{
        emptyText: <Empty description="No data" />
      }}
    />
  )
}

Select/TreeSelect/Mentions/Cascader

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

tsx 复制代码
function App() {
  return (
    <Component
      notFoundContent={<Empty description="No data" />}
    />
  )
}

Transfer

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

tsx 复制代码
function App() {
  return (
    <Transfer
      locale={{
        notFoundContent:[
          <Empty description="Not Source"/>,
          <Empty description="Not Target"/>,
        ]
      }}
    />
  )
}

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

tsx 复制代码
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 类型推导有提升..

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

tsx 复制代码
function App() {

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

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

OK,再会~

相关推荐
Myli_ing5 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维22 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
I_Am_Me_36 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
雯0609~43 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ1 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
前端百草阁1 小时前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue