Antd 组件使用进阶及踩过的坑

发布于 2023-08-15 05:42:50 字数 11397 浏览 48 评论 0

自定义表单组件

Antd 的 Form 表单介绍一节中,提到过自定义表单控件。其实例是关于货币价值转换的,如下图所示:

当我们在我们的页面中需要频繁的用到某一个组合类型的组件,而 Antd 又不支持时,最好的做法就是对 Antd 组件做一层浅封装行成一个独立的组件,当然也可以使用 html 自有的表单元素进行封装,只是这样做出来费事,且样式和整个页面没有那么容易统一。封装的注意事项在上面的截图中已经一一列出,接下来将以一个实例来操作说明。

一个带远程搜索的下拉选择组件

2018.12 月更新:随着 Select 组件 comobox 模式在新的版本中被舍弃,和 autoComplete 组件的出现。这个组件也进行了重写。但整体逻辑没有改变,主要时改变了激活弹出框和关闭弹出框的逻辑。具体可参见我的 github 项目: React 进阶

seri

这个组件的大致实现需求如上面动态图所示。产品需求就是需要一个编辑框,这个框在用户点击输入时,需要弹出一个搜索框,根据用户的输入远程搜索获取数据形成一个下拉列表,供用户选择。这在 jquery 时代,这是一个很常见的需求,也有很多的组件可选择,但在 Antd 的组件库中,没有完全匹配的,但有及其相似功能的,比如:

这个组件与产品的需求契合度已经达到了 80%, 但是产品说了搜索输入框需要与编辑输入框分开,并且有明显的区别,ok,那就费点事,把 Antd 组件稍微做一下改变嘛。

所以简单分解一下,需要用到 Input,Icon, Select 这三种组件,具体实现可以查看 SandBox 上的源码及示例 。说一下自己遇到的难点:

支持双向绑定

<FormItem type="inline" label="员工姓名">
  {getFieldDecorator('search',{
    initialValue: { name: 'Dom' }
  })(
    <OriginSearch {...modalProps} />
  )}
</FormItem>

在 Antd 的 Form 表单组件中,如果需要做数据双向绑定,就需要用到其提供的 getFieldDecorator 方法来装饰组件,而我们自己封装的组件要支持这个特性的话,如最开始提到的,我们需要使用 onChange 方法来触发装饰器值的同步。

在我们为一个组件添加了装饰器后,可以查看 props 明显发现多了 id,onChange, value 三个属性,value 属性是用于获取我们在 initialValue 设定的值的,而 onChange 方法是用于同步值,实现双向绑定。所以在我们这个组件中,当用户从下拉框中选择一个选项后,我们需要调用 onChange 方法去同步值,代码如下所示:

handleSelect(value, option) {
  const { handleSelect } = this.props;
  const { seachRes } = this.state;
  // 初始化基础信息
  const selectValue = handleSelect(value, option, seachRes) || value;
  this.triggerChange(selectValue);
  this.setState({ value: selectValue });
  this.handleCloseSearch();
}
triggerChange(value) {
  const { onChange } = this.props;
  // 调用装饰器方法去同步选中后的值
  onChange && onChange(value);
}

点击组件以外的地方收起组件

这看似是个很容易实现的需求,但因为 Antd 所有的弹框组件都用了同一套方法,其弹框 Dom 树并不是挂载在 Select 输入框的父节点上,而是直接挂载在 Body 节点上,所以想用冒泡的机制来实现就不可能了。所以就和投机用了点击事件的节点名称来判断,看具体实现:

componentDidUpdate(prevProps, prevState) {
  const { isShowSearch } = this.state;
  const bodyNode = document.querySelector("body");
  if (isShowSearch !== prevState.isShowSearch) {
    // 状态切换的时候才改变状态
    if (isShowSearch) {
      document
        .querySelector(".js-origin-search .ant-select-search__field")
        .focus();
      bodyNode.addEventListener("click", this.handleChangeVisible);
    } else {
      bodyNode.removeEventListener("click", this.handleChangeVisible);
    }
  }
}
handleChangeVisible(event) {
  const { isShowSearch } = this.state;
  event = event || window.event;
  const elem = event.target;
  let inComponentClick = false;
  // 当搜索框框被打开时,点击空白处搜索框收起;由于 antd 的下拉列表是挂载在 body 下,而非搜索框节点下的某一子节点,所以
  // 无法采用阻止冒泡的方式来避免 body 下的 click 事件被响应,所以只有靠判断被点击的节点类,来判断 body 的 click 事件是否响应
  if (
    (this.searchInputElement && this.searchInputElement.contains(elem)) ||
    elem.className.indexOf("ant-select-dropdown") !== -1
  ) {
    inComponentClick = true;
  }
  // 当点击事件为非下拉列表选中事件,切搜索框为展开时,触发搜索框收起方法;
  !inComponentClick && isShowSearch && this.handleCloseSearch();
}

虽然这只是一次很简单的封装,但其包含的知识点还是非常多的。自己还封装过日期多选,日期选择增加至今,地址地区联合选择器这种,从实现上其实都是一个思路,在这一个 SandBox 项目中都能看到。

组件奇特使用方式(持续更新)

动态更新表单组件 Required 参数,但验证没有同步

加入现在有这样一个需求,用户需要选择自己的性别(男,女,其他),当用户选择其他时,下面说明项由非必填变为必填。这看似是一个很简单的需求,在用户选择其他时,将 isRequired 变量变为 true 就行了,看起来好像,大概,貌似成功了。但是自己在 antd 组件的应用上,发现,当你把 isRequired 置为 true,label 标签前面会加上一个*,但这只是一个假象,当你填上数据再删除时,antd 组件这时并不会自动验证,并触发提示词显示。

但是,这些都没有。所谓的 isRequired 置为 true,并没有达到真正想要的效果,测试代码如下,我猜测 antd 的这个机制和他的 initValue 更新机制相似,只有在组件初始化的时候会设置一次初始值,后面都是组件内部 state 参数进行状态切换,原以为用 resetFields 可以解决,最后发现不能。但是巧的方法没有,不代表笨办法也没有。

<FormItem
  label="性别"
  labelCol={{ span: 5 }}
  wrapperCol={{ span: 12 }}
>
  {getFieldDecorator('gender', {
    rules: [{ required: true, message: 'Please select your gender!' }],
  })(
    <Select
      placeholder="Select a option and change input text above"
      onChange={this.handleSelectChange}
    >
      <Option value="male">male</Option>
      <Option value="female">female</Option>
    </Select>
  )}
</FormItem>
<FormItem
  label="说明"
  labelCol={{ span: 5 }}
  wrapperCol={{ span: 12 }}
>
  {getFieldDecorator('note', {
    rules: [{ required: isRequired, message: 'Please input your note!' }],
  })(
    <Input />
  )}
</FormItem>

最后想出的最好的解决办法就是动态销毁重新挂载这个组件,我们可以通过动态设定 key 值,来保证状态的同步。其实这是重新渲染了一个新的组件来替换。

<FormItem
  label="说明"
  key={isRequired}
  labelCol={{ span: 5 }}
  wrapperCol={{ span: 12 }}
> 

上面的问题,在自己使用 3.9 的版本上没有再出现了,但是好像又出现了一个更大的表单动态校验问题。看下图,需求和上面差不多:

2303313742-5c317658312cd_articlex

当选择启用时,原因必填。其他时,变为非必填。现在出现的情况时,必填时触发错误提醒,但将状态置为禁用时,label 的必填属性虽然被重置了,但错误提醒仍然存在,有些奇怪。

基于上面的现象,我去基友社区搜了一下 Issuse,果然时存在的: form 动态校验问题 ,幸运的时,antd 大佬也给出了解决方案:使用 form.validateFields([key], { force: true })来解决

实践后的幡然醒悟

用了两个多月,其实 Antd 自己本身没啥坑,只是由于我们组现在使用的版本是 2.9,但自己习惯于看 3.x 的版本文档,所以多次在一个地方徘徊好久,总以为是自己代码实现有问题,其实是 2.9 版本还没有实现。

总结一

在 Select 组件上,2.9 与 3.x 就有较大的差异:

  1. Select 的 option 必须带有不同的 Key 值,且 value 值也不能有相同的,比如在远程搜索加载员工列表时,就会出现同名的情况,所以这时的 value 就不能只用名字,得用 value 加工号或则其他值来代替。
  2. Select 的 onchange 事件在 3.x 版本以前回调函数只有 value 值,没有 option 回调参数。
  3. Select 的 notFundContent 属性可配置结合 Spin 实现加载动画,但在版本 3 以下,该配置对于 comobox 模式无效(其文档未对这个特性(Bug)做说明)。。。
  4. Select 的 onSelect 事件在 3.x 以后也有较大改动,其 option 参数包含的内容作了很大调整,在 2.9 版本还可以通过 option.props.index 获取选择的索引,在 3.x 版本只能间接通过设置 key 为 index,然后通过获取 key 值来获取 index;
  5. Select 组件渲染出来的下拉列表是没有挂载在 Select 组件父节点上的,其是采用绝对定位,挂载在 body 节点上的。。。所有用父节点做筛选是无法获取的。

总结二

另外在表单组件自校验 validator 的使用上,有一个隐藏的少有人知的使用方法是:

<FormItem {...formItemLayout} label="确认密码">
    {
        getFieldDecorator('confirmPassword', {
            rules: [{
                    required: true,
                    message: '请再次输入以确认新密码',
                }, {
                    validator: this.handleConfirmPassword
                }],
        })(<Input type="password" />)
    }
</FormItem> 
handleConfirmPassword(rule, value, callback) {
    if (value && value.length < 5 ) {
        callback('长度不足');
        return;
    }
    // Note: 必须总是返回一个 callback,否则 validateFieldsAndScroll 无法响应
    callback()
}

总结三

当我们使用 getFieldDecorator 并用 initialValue 设定初始值时,当我们改变组件的值时,组件表现出的值也改变了,但这个值并不是 initialValue 设定的,其是组件内部的 state 值保持的,如果需要继续用 initialValue 来设定组件的值,我们需要调用 resetFields 方法使 initialValue 有效;

总结四:Table 设置 width 无效

Antd 组件个人觉得最好用的功能就是 Table,其配合 pagination 可以直接实现前端分页,在有些使用场景可以大大提高使用体验。但是 Table 也有坑(其实也是 css 一个隐形知识点),就是有时你会发现你为一列设置了 width,但是并没有鸟用。

{
  key: 'userId',
  name: '用户 ID',
  value: 'asddsddsfsfsdfsdfsdfsfsfdsfsfsfsfsfddefgervwerbvw'
  width: 80
}

就像上面的这种数据,设置了 80 的宽度,但最后撑开差不多是 300。最后的最后,记起了又一个 css 属性叫** word-break **,来历就是在浏览器中,纯数字或者纯字母的字符串,他的显示默认是不换行的,就算他已经超出了这一行的边际,就是这么叼的一个属性。所以 Table 也受这个影响,由于我要展示的内容中是纯字母,纵然我设置了 width,依然没什么鸟用。需要设置与 td 相关联的 table 样式,加上 word-break:break-all 这样的解药。

总结四:Select(下拉弹框类)组件页面滚动时,下拉内容与弹出父组件分离

如上图所示,外层页面一滚动,下拉框与下拉内容就分离了,分离,离,离了。这个出现原就是因为 ANTD 所有的弹框都是默认挂载在 body 下面,然后绝对定位的。所以滚动 body 内容, 就会造成与弹出触发的那个组件错位。幸好在 3.0 以后 ANTD 对这个 bug 做出了一个解决方案,就是增加了 getPopupContainer 属性,可以让下拉内容挂载任何存在的 dom 节点上,并相对其定位。具体用例请查看 官方示例

Antd Hooks 自定义组件报 cannot be given refs

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()

随着 React hooks 不断的应用,在 antd 中,用 hooks 自定义的组件,会报如上所示的 refs 无法获取,这是 antd form 双向绑定对 ref 有需要。因为 ref 和 key 一样,不是通过 prop 来传递的,react 对其有特殊的处理。好在这个可以通过 forwardRef 来解决, 官方文档 ,看一个示例:

export default function FundSelect(props) {
    return (
        <YourComponent {...props}>
    )
}

使用 forwardRef 转发 Ref

function FundSelect(props, ref) {
    return (
        <YourComponent ref={ref} {...props}>
    )
}

export default forwardRef(FundSelect);

getFieldDecorator 包裹下的 Switch 组件无法显示为 true 状态

<FormItem {...formItemLayout} label="是否显示">
     {getFieldDecorator('enable', {
      initialValue: true,})
(<Switch />)}
 </FormItem>

发现在 getFieldDecorator 包裹下的 Switch 组件无法显示为 true 的状态,3.23 版本报以下提示信息:

warning.js:6 Warning: [antd: Switch] value is not validate prop, do you mean checked ?

查找文档得知 Switch 组件是通过 checked 的属性来显示状态的,所以需要一个额外的属性 valuePropName

<FormItem {...formItemLayout} label="是否显示文档">
            {getFieldDecorator('showDocument', {
              initialValue: true,
              valuePropName: 'checked'
            })(<Switch  />)}
</FormItem>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

撞了怀

暂无简介

文章
评论
29 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文