行内编辑使用Popver的形式报错
EditTableProTable 报错采用 Popover 的形式去展示,而不是使用默认错误提示理由如下:
保证了单元格不会出现抖动: 单元格占内容有限,默认报错提示会导致单元格发送抖动情况。
在错误内容比较长的情况下甚至会出现折行的情况。
如果最后要有一个保存按钮的话,可以想象点击会破坏整个table结果。
所以从体验感受上来看采用Popver形式报错提示是合适的;看看Popover对这块实现:
EditableProTable 源码实现
pro-components/packages/utils/src/components/InlineErrorFormItem/index.tsx
从GitHub Copilot 看出 实现主要 _internalItemRender 属性用于自定义表单项内容的渲染,包含输入框、错误列表和额外信息。但是它是Form.Item 的内置方式,并且antd源码中注释不可用于任何生产环境。内置方式的问题是提供方不能确保稳定,万一那次更新导致提供的Props下掉了就会导致报错。
ts
// 自定义 Form.Item 的渲染结构
_internalItemRender?: {
mark: string;
render: (
props: FormItemInputProps & FormItemInputMiscProps,
domList: {
input: JSX.Element; // 输入控件
errorList: JSX.Element | null; // 错误信息列表
extra: JSX.Element | null; // 额外信息
},
) => React.ReactNode;
}
自定义实现
所以不能直接抄它源码实现。从效果来看我们实际也不难。我们在Form.Item 上包裹一层Popover组件然后,获取表单error报错信息,然后展示到Popver的 content 上去就可以了,于是会就有👇🏻下面代码:
typescript
interface InlineErrorFormItemPopoverProps{
fieldName: NamePath
children: React.ReactNode
}
const InlineErrorFormItemPopover<InlineErrorFormItemPopoverProps> = ({children,fieldName})=>{
const [open,setOpen] = useState(false)
const form = Form.useFormInstance()
const errors = form.getFieldError(fieldName) ?? []
const hasError = errors.length > 0
const handleChange = (changeOpen)=>{
setOpen(()=> hasError&&ChangeOpen)
}
return <Popover open={open} onChange={setOpen}
content={<span>{errors.join(',')}</span>}>
// 额外加div让其能接受对应onMouseEnter onMouseLeave on Focus onClick点击事件
<div>
{children}
</div>
</Popover>
}
// App.tsx
...
<InlineErrorFormItemPopover>
<Form.Item name="username" label="UserName" rules={[{ required: true }]}>
<Input allowclear/>
</Form.Item>
</InlineErrorFormItemPopover>
...
这里装饰器模式的代码很容易想到,但是这种只会在挂载页面时渲染只执行一次,在校验触发报错时不会触发渲染。
所以这里实现关键是 如何在触发报错时,让 InlineErrorFormItemPopover 去渲染获取错误,弹出错误弹框。
Antd Form 提供 shouldUpdate 允许比较表单值来触发重新渲染。可惜shouldUpdate回调里只提供了值做比较,要提供状态做比较就可以一步到位。
这个issue 比较有意思,挂着对提问题人鞭尸😁
javascript
return <Form.Item shouldUpdate={(pre,current)=> pre!==current}>
{
({getFieldError,getFieldName})=>{
hasError = form.getFieldError(fieldName).length > 0
const errors = form.getFieldError(fieldName)
<Popover open={open} onChange={setOpen} content={<span> {errors.join(',')}</span>}>
<div>
{children}
</div>
</Popover>
}
}
</Form.Item>
这里每个修改表单值,确实触发了每次对上面代码渲染执行,但是当表单触发校验错误时form.getFieldError(fieldName)
获得的是空数组 ???导致Popver不会弹出错误。
通过查阅antd issue 发现shouldUpdate 如果不设置为true的话,shouldUpdate 下的 form.getFieldError 始终获取为空数组。应该是和校验时机的问题,这个问题出现在2020年,到现在antd也好像没有解决。
所以目前实现只能让 shouldUpdate 设置为true 去解决,但这会导致额外渲染,后续可以关注antd 更新是否解决这个bug。最后实现如下:
typescript
import { Form, Popover } from 'antd'
import { NamePath } from 'antd/es/form/interface'
import { FC, useState } from 'react'
interface InlineErrorFormItemPopoverProps {
fieldName: NamePath
children: React.ReactNode
}
/**
* @description 行内错误信息展示
*/
const InlineErrorFormItemPopover: FC<InlineErrorFormItemPopoverProps> = ({
children,
fieldName,
}) => {
const [open, setOpen] = useState(false)
return (
<Form.Item noStyle shouldUpdate>
{(form) => {
const errors = form.getFieldError(fieldName)
const hasError = errors.length > 0
return (
<Popover
open={hasError && open}
onOpenChange={setOpen}
content={
<span style={{ color: '#ff4d4f' }}>{errors.join(', ')}</span>
}
placement="topLeft"
trigger={['click']}
>
<div>{children}</div>
</Popover>
)
}}
</Form.Item>
)
}
export default InlineErrorFormItemPopover