一份antd的使用盘点

前言

不知不觉来北京已经两年半了,经手的项目有小程序,h5,pc端等等,当然主要还是以pc为主,作为个人来讲确实比较喜欢做pc端的项目,因为它相对于h5和小程序比较省心,不需要处理h5复杂的兼容性场景,也不需要处理小程序的一些认证过程,但这并不意味着pc端就很简单,一个好的h5前端往往有着很多强大的平台来支撑,例如CI,CD,数据监控等等,而作为pc端的好伙伴,antd的熟练掌握自然是必不可少的。

背景

我在使用中发现了很多antd自带的问题,还有是在特定业务场景下产生的问题,我把这些分享出来,做一个记录,以备不时之需。

问题汇总

公司使用的是版本3和版本4,这里会针对这两个版本来讨论

Mentions组件

在TextArea中回车需要发送接口请求,输入@之后提示框回车也会触发keyUp事件

解决办法:在onSelect中加入标识区分两个keyUp事件

ini 复制代码
const [mentionSelect, setMentionSelect] = useState(false);
const onKeyUp = (e) => {
    if(e.keyCode === 13) {
        if(mentionSelect) {
            console.log('select------')
        } else {
            // 发送请求
            sendRequestXXX
        }
    }
}
const onSelect = () => {
    setMentionSelect(true)
}
<Mentions onKeyUp={onKeyUp} onSelect={onSelect}>
    <Mentions.Option value="sample">Sample</Mentions.Option>
</Mentions>

但是这样的逻辑是有问题的:正常点击之后回车发送不了请求了(select事件用来拦截回车了)

所以只能在click的时候把mentionSelect重置为false。 但问题又来了,如果小于4.24版本 Option的click事件不生效。。。

我只是想加一个回车发送请求的功能,为啥介么复杂呀 这里其实有两个方案:

  • 把版本升级到4.24,改用options
ini 复制代码
<Mentions options={[{label: <div onClick={() => {
    setMentionSelect(false)
}}></div>}]}/>
  • 监听ant-mentions-active的点击事件
scss 复制代码
useEffect(() => {
    document.querySelector('ant-mentions-active').addEventListener('click', () => {
        setMentionSelect(false)
    })
}, [])

最后我选择了第一种,稳妥一些

如果提示项过多,上下按钮无法操作滚动

这里提供三种思路:

  1. 监听滚动,计算scrollTop(这种办法兼容性好一些,但需要兼容到顶和到底的情况)
ini 复制代码
let activeLi = document.getElementsByClassName('ant-mentions-dropdown-menu-item-active');
    if(activeLi.length > 0) {
      const scrollTop = document.getElementsByClassName('ant-mentions-dropdown-menu')[0].scrollTop;
      const offsetTop = document.getElementsByClassName('ant-mentions-dropdown-menu-item-active')[0].offsetTop;
      // 判断滚动到底了
      if(offsetTop - scrollTop > height) {
        document.getElementsByClassName('ant-mentions-dropdown-menu')[0].scrollTo({
          top: scrollTop + height
        })
      }
    }
  1. scrollIntoView:调用它的元素滚动到浏览器窗口的可见区域
dart 复制代码
document.querySelector('.ant-mentions-dropdown-menu-item-active')?.scrollIntoView({ block: 'nearest' })

这里nearest代表保持接近边界

List To Tree

这其实是一个常见的需求

ini 复制代码
let testConvertArray = [{
      name: 'aaa',
      id: 'a',
      parentkey: 0
    },
    {
      name: 'bbb',
      id: 'b',
      parentkey: 'a'
    },
    {
      name: 'ccc',
      id: 'c',
      parentkey: 'b'
}]
const transverse = (arr, rootId) => {
    return arr.filter((item) => {
        if(item.parentKey === rootId) {
            const tempItem = transverse(arr, item.key);
            item.children = tempItem.length > 0 ? tempItem : null;     
        }
        item.label = (
            <Link to={item.path}>
              {item.name}
            </Link>
        );
        return item.parentKey === rootId   
    });
}
return <Menu selectedKeys={[activeKey]} mode="horizontal" items={transverse(testConvertArray, 0)} />

这里需要注意tempItem如果是空数组需要转成null,不然menu组件会出现小块空白情况

慎用onShowSizeChange

有这样一个需求,在切换pageSize的时候需要将pageIndex重置为1,但是 onChange方法又会将pageIndex设置回当前页面

ini 复制代码
const onShowSizeChange = (current, size) => {
    setState((prev) => {
        prev.pageIndex = 1;
        prev.pageSize = size
    })
}
const changePage = (pageIndex, size) => { 
    setState((prev) => {
        prev.pageIndex = pageIndex; // 这里会将当前页设置回pageIndex
        prev.pageSize = size
    })
}
<Pagination
    total={state.total}
    onShowSizeChange={onShowSizeChange}
    onChange={changePage}
    pageSize={state.pageSize}
    current={state.pageIndex}
/>

解决方法是去掉onShowSizeChange,改为以下的写法:

ini 复制代码
const changePage = (pageIndex, size) => { 
    setState((prev) => {
        prev.pageIndex = state.pageSize !== size ? 1 : pageIndex; // 修改pageSize重置为第一页
        prev.pageSize = size
    })
}

使用useWatch监听setFieldValue设置的最新值

场景:表单中多个选项之间相互关联影响,想要监听setFieldValue的最新值进行相应的逻辑操作

scss 复制代码
const datasetValue = Form.useWatch('datasetId', form);
const onValuesChange = (changedValues, allValues) => {
    const { scene, datasetId } = changedValues;
    form.setFieldsValue({datasetId: 'aaa'})
}
useEffect(() => {
    if(datasetValue) {
        XXXX // 这里写相关的逻辑
    }
}, [datasetValue])

这里不用onValuesChange的原因是:

  • 可以让表单的值可以受变化项的控制,把对应的逻辑都写在一起,方便处理
  • onValuesChange无法监听到setFieldsValue设置的最新值

下拉框会随着body的滚动而滚动

getPopupContainer:在有下拉样式的组件中让下拉随容器滚动,这个容器默认指向body,可以修改成父容器,这样body滚动的时候下拉就不会随之滚动了

使用onCell回调确定Columns的dataIndex

在table表格中我们很容易区分是哪一行,但并不好区分到底是哪列,比如一个弹框的table点击某列之后需要将弹框关闭

javascript 复制代码
<Table
  columns={[{dataIndex: 'aaa'}, {dataIndex: 'bbb'}].map((item) => {
      item.onCell={(record) => {
        return {
          dataIndex: item.dataIndex, // 这里能直接拿到
          onClick: (event) => {}, // 点击行
        };
      }}
  })}
/>

如何在debounce中使用异步请求

常见的场景,在用户输入的时候通过接口校验用户名是否重复

javascript 复制代码
export function debounceFn(fn, time) {
	let timer = null;
	return function(...args) {
		if(timer) {
			clearTimeout(timer);
		}
		return timer = setTimeout(() => {
      fn.call(null, ...args)
    }, time);
	}
}
const validateModelName = debounceFn(async (rule, value) => {
    const params = { modelName: value }
    const res = await modelNameCheck(params)
    if (res.result) {
      return Promise.reject('用户名称重复')
    } else {
      return Promise.resolve()
    }
  }, 500)
<Form.Item
  label={intl.getStr('common.name')}
  name="modelName"
  style={{ marginTop: 20 }}
  validateFirst
  rules={[{
    validator: validateModelName,
  }]}
>
  <Input />
</Form.Item>

上面的代码不能起到debounce的效果,每次输入都会触发,原因是啥呢? 仔细观察,会发现promise链断了,因为async和await在经过debounceFn的包装之后返回的是一个正常的函数,而不是promise了,所以将debounceFn的返回结果Promise化就好了

javascript 复制代码
export function debounceFn(fn, time) {
	let timer = null;
	return function(...args) {
		if(timer) {
			clearTimeout(timer);
		}
                // 这里进行promise化
		return new Promise((resolve) => {
			timer = setTimeout(() => {
				resolve(fn.call(null, ...args))
			}, time);
		});
	}
}

关于Form.Item

在Form.Item中如果包含两个节点会导致setFieldValue设置失效,像这样:

less 复制代码
<Form.Item name="userName">
    <span>Label:</span>
    <Input />
    <div>123</div>
</Form.Item>

可以选择以下两种解法:

  1. 使用Row和Col去包裹Form.Item和div
xml 复制代码
    <Row>
      <Col><span>Label:</span></Col>
      <Col>
          <Form.Item name="userName">
              <Input />
          </Form.Item>
      </Col>
      <Col><div>123</div></Col>
    </Row>
  1. 使用Form.Item的noStyle
less 复制代码
    <Form.Item noStyle>
        <span>Label:</span>
        <Form.Item name="userName">
            <Input />
        </Form.Item>
        <div>123</div>
    </Form.Item>
    <div>123</div>
    </Form.Item>

noStyle加上formitem的shouleUpdate可以配合做表单项的动态渲染比较好

typescript 复制代码
    <Form.Item noStyle shouldUpdate={(prevValues, curValues) => prevValues.type !== curValues.type}>
    {({ getFieldValue }) => {
          const type = getFieldValue('type')
          if (type === 1) {
                <Form.Item name="userName">
                  <Select options={intervalTypeList} />
                </Form.Item>
          }
      }}
  </Form.Item>

这里给大家一个建议,如果自己设计组件需要包裹在Form.Item中使用,像这样:

xml 复制代码
  <Form.Item name="aaa">
      <MyComp />
  </Form.Item>

在MyComp中不要维护value状态和onChange事件,第一是如果组件需要拆分多层,层层传递会有遗漏,第二就是每层都有一个状态不好维护,最好是通过...restProps透传给下一层组件。

总结

其实这一篇比较偏向笔记的文章,对我来讲需要养成写文章总结的好习惯,因为antd的细节太多了,也希望大家多提一些宝贵的意见和建议,还有自己业务上遇到的场景也可以,这篇文章我也会持续更新下去,加油,继续搬砖去了!!!

相关推荐
破z晓4 分钟前
OpenLayers 开源的Web GIS引擎 - 地图初始化
前端·开源
维生素C++24 分钟前
【可变模板参数】
linux·服务器·c语言·前端·数据结构·c++·算法
vah10130 分钟前
python队列操作
开发语言·前端·python
项目題供诗30 分钟前
尚品汇-H5移动端整合系统(五十五)
java·服务器·前端
DT——6 小时前
Vite项目中eslint的简单配置
前端·javascript·代码规范
学习ing小白8 小时前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进9 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er9 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063719 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl9 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js