学习Pro-component源码 EditableProTable

行内编辑使用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
相关推荐
陈卓4105 分钟前
Redis-限流方案
前端·redis·bootstrap
顾林海13 分钟前
Flutter Dart 运算符全面解析
android·前端
七月丶20 分钟前
🚀 现代 Web 开发:如何优雅地管理前端版本信息?
前端
漫步云端的码农21 分钟前
Three.js场景渲染优化
前端·性能优化·three.js
悬炫22 分钟前
赋能大模型:ant-design系列组件的文档知识库搭建
前端·ai 编程
用户1083863868026 分钟前
95%开发者不知道的调试黑科技:Apipost让WebSocket开发效率翻倍的秘密
前端·后端
稀土君1 小时前
👏 用idea传递无限可能!AI FOR CODE挑战赛「创意赛道」作品提交指南
前端·人工智能·trae
OpenTiny社区1 小时前
Node.js 技术原理分析系列 4—— 使用 Chrome DevTools 分析 Node.js 性能问题
前端·开源·node.js·opentiny
写不出代码真君1 小时前
Proxy和defineProperty
前端·javascript
乐坏小陈1 小时前
TypeScript 和 JavaScript:2025 年应该选择哪一个?【转载】
前端·javascript