前言
不知不觉来北京已经两年半了,经手的项目有小程序,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)
})
}, [])
最后我选择了第一种,稳妥一些
如果提示项过多,上下按钮无法操作滚动
这里提供三种思路:
- 监听滚动,计算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
})
}
}
- 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>
可以选择以下两种解法:
- 使用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>
- 使用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的细节太多了,也希望大家多提一些宝贵的意见和建议,还有自己业务上遇到的场景也可以,这篇文章我也会持续更新下去,加油,继续搬砖去了!!!