一份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的细节太多了,也希望大家多提一些宝贵的意见和建议,还有自己业务上遇到的场景也可以,这篇文章我也会持续更新下去,加油,继续搬砖去了!!!

相关推荐
用户47949283569156 分钟前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
没有故事、有酒18 分钟前
Ajax介绍
前端·ajax·okhttp
朝新_21 分钟前
【SpringMVC】详解用户登录前后端交互流程:AJAX 异步通信与 Session 机制实战
前端·笔记·spring·ajax·交互·javaee
裴嘉靖23 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242626 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁1 小时前
Angular【router路由】
前端·javascript·angular.js
brzhang2 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构
西洼工作室2 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
广州华水科技2 小时前
北斗形变监测传感器在水库安全中的应用及技术优势分析
前端