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,再会~

相关推荐
2401_857638036 分钟前
【Perl CGI脚本全解析】打造动态Web应用的秘籍
前端·scala·perl
下一站丶42 分钟前
前端引用vue/element/echarts资源等引用方法Blob下载HTML
前端·vue.js·echarts
前端扎啤1 小时前
高效前端开发:解密pnpm的存储与链接
前端·前端框架·npm·pnpm·依赖
@Within1 小时前
vite+vue集成cesium
javascript·vue.js·cesium
苏十八1 小时前
前端基础:JavaScript(篇一)
开发语言·前端·javascript·面试·html·ecmascript·html5
narukeu2 小时前
React 中 useState 和 useReducer 的联系和区别
前端·react.js·前端框架
38kcok9w2vHanx_2 小时前
v-html 空格/换行不生效
前端·html
英俊潇洒美少年2 小时前
自定义一个背景图片的高度,随着容器高度的变化而变化,小于图片的高度时裁剪,大于时拉伸100%展示
javascript·css
吱吱喔喔2 小时前
vue3中省市区联动在同一个el-form-item中咋么设置rules验证都不为空的效果
javascript·vue.js·elementui