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

更多我对Antd的使用及思考,请参考:antd-doddlecss

扯点犊子

一晃眼,两个月过去了,本身从一家不大不小的屌丝公司跳到一家被具备纯正互联网血液的公司。从之前的围绕jQuery、Echarts为主技术栈开展工做,到如今以React、Antd为主技术栈开发业务;但不是全部的业务antd都能支持,因此有时得本身动手,在antd上作一层浅封装。
文章中提到的示例均可以在codeBox找到:codeBoxhtml

自定义表单组件

Antd的Form表单介绍一节中,提到过自定义表单控件。其实例是关于货币价值转换的,以下图所示:
image
当咱们在咱们的页面中须要频繁的用到某一个组合类型的组件,而Antd又不支持时,最好的作法就是对Antd组件作一层浅封装行成一个独立的组件,固然也可使用html 自有的表单元素进行封装,只是这样作出来费事,且样式和整个页面没有那么容易统一。封装的注意事项在上面的截图中已经一一列出,接下来将以一个实例来操做说明。前端

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

2018.12月更新:随着Select组件comobox模式在新的版本中被舍弃,和autoComplete组件的出现。这个组件也进行了重写。但总体逻辑没有改变,主要时改变了激活弹出框和关闭弹出框的逻辑。具体可参见个人github项目:React进阶
seri
这个组件的大体实现需求如上面动态图所示。产品需求就是须要一个编辑框,这个框在用户点击输入时,须要弹出一个搜索框,根据用户的输入远程搜索获取数据造成一个下拉列表,供用户选择。这在jquery时代,这是一个很常见的需求,也有不少的组件可选择,但在Antd的组件库中,没有彻底匹配的,但有及其类似功能的,好比:jquery

image

这个组件与产品的需求契合度已经达到了80%, 可是产品说了搜索输入框须要与编辑输入框分开,而且有明显的区别,ok,那就费点事,把Antd组件稍微作一下改变嘛。
image
因此简单分解一下,须要用到Input,Icon, Select这三种组件,具体实现能够查看SandBox上的源码及示例。说一下本身遇到的难点:git

支持双向绑定

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

在Antd的Form表单组件中,若是须要作数据双向绑定,就须要用到其提供的getFieldDecorator方法来装饰组件,而咱们本身封装的组件要支持这个特性的话,如最开始提到的,咱们须要使用onChange方法来触发装饰器值的同步。 github

在咱们为一个组件添加了装饰器后,能够查看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节点上,因此想用冒泡的机制来实现就不可能了。因此就和投机用了点击事件的节点名称来判断,看具体实现:antd

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项目中都能看到。app

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

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

加入如今有这样一个需求,用户须要选择本身的性别(男,女,其余),当用户选择其余时,下面说明项由非必填变为必填。这看似是一个很简单的需求,在用户选择其余时,将isRequired变量变为true就好了,看起来好像,大概,貌似成功了。可是本身在antd组件的应用上,发现,当你把isRequired置为true,label标签前面会加上一个*,但这只是一个假象,当你填上数据再删除时,antd组件这时并不会自动验证,并触发提示词显示。可是,这些都没有。所谓的isRequired置为true,并无达到真正想要的效果,测试代码以下,我猜想antd的这个机制和他的initValue更新机制类似,只有在组件初始化的时候会设置一次初始值,后面都是组件内部state参数进行状态切换,原觉得用resetFields能够解决,最后发现不能。可是巧的方法没有,不表明笨办法也没有。dom

<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 }}
>

2019.01.06日更:
上面的问题,在本身使用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(下拉弹框类)组件页面滚动时,下拉内容与弹出父组件分离

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

文章首发于: http://closertb.site

相关文章
相关标签/搜索