基于antd封装一个高可用form组件 减少cv代码导致的bug
程序员文章站
2022-07-09 20:16:44
引言 在开发中台过程中 我们的原型中有很多表单,antd有表单组件,但是粒度比较细,就单纯组件而言,无可厚非,但是在开发过程中,可能会造成代码不够聚合,有些表单公共逻辑无法提取,copy paste比较多,所以可以加以封装,搞一个兼容性和扩展性都契合项目本身的组件。 主要思路 我简单查阅了一番资料, ......
引言
在开发中台过程中 我们的原型中有很多表单,antd有表单组件,但是粒度比较细,就单纯组件而言,无可厚非,但是在开发过程中,可能会造成代码不够聚合,有些表单公共逻辑无法提取,copy paste比较多,所以可以加以封装,搞一个兼容性和扩展性都契合项目本身的组件。
主要思路
我简单查阅了一番资料,参考了一下别人的封装方式,决定以col
和formitem
作为基本单元,进行表单的搭建,主要原因
- 可以将
formitem
的信息和col
的信息以对象方式传入,这样我们可以更加专注于组件的包含的信息 减少cv代码可能导致的bug -
col
可以进行布局的调整,可以调整表单单元位置和所占宽度 - 表单的组件形式是不定的,可能是input也有可能是select,所以可以进行外部传入,通用属性可以内部修改
- 复用性高,可以用它来实现一个纯提交表单 和列表组件结合成可搜索表单 或者其他任何项目里需要自定义的一个表单
实现细节
抽象的formiteminfo
export interface formiteminfo { name: string, //label id: string, // 属性 collayout?: object,// 列布局 formitemlayout?: object,// 表单项布局 comp?: any, // 传入的组件 不传默认input rulearr?: ruleobj[],// 验证规则 initialvalue?: string, // 初始值 required?: boolean, // 是否必填 compextraprops?: object // 传入组件额外属性 isdetail?: boolean //是否需要被getfielddecorator托管 }
baseform组件
* @param name 表单项label * @param id 属性字段名 * @param collayout 列布局 * @param formitemlayout 表单项布局 * @param comp 传入的组件 不传默认input * @param rulearr 验证规则 长度只需传入{max:xxx}验证信息是统一的 * @param initialvalue 初始值 编辑时推荐使用antd的mappropstofields 不要手动设置回显值 * @param required 是否必填 默认否 因为要统一写验证提示 * @param compextraprops 组件额外属性 const maptoformitem = (val: formiteminfo) => { ...... // 是否传入组件 const hascomptype = comp && comp.type // 根据组件类型 给出提示文字 const msg = getmsgbycomptype(hascomptype && hascomptype.name, name) // 判断是不是select组件 是的话 调整宽度样式 const mixstyle = fixstylebycomptype(hascomptype && hascomptype.name) // 生成验证规则 const rules = getformrules(rulearr || [], required || false, name) return ( <col {...(collayout || defaultcolspan)} key={id}> {!isdetail ? <formitem label={name} key={id} {...(formitemlayout || {})}> {/*用cloneelement方法给传入的组件加新属性*/} { getfielddecorator(id, { initialvalue: initialvalue || '', rules, })(comp ? react.cloneelement(comp, { placeholder: msg, ...mixstyle, ...compextraprops }) : <input type="text" placeholder={msg}/>) } </formitem> : <formitem label={name} key={id} {...(formitemlayout || {})}> {comp}</formitem>} </col> ) }
统一方法
- 根据组件类型 给出提示文字
/**根据组件类型 给出提示文字 * @param type 组件类型 根据传入组件的函数名判断 在map里维护 * @param name label名称 * @returns 提示文字 * */ const getmsgbycomptype = (name: string, type?: string): string => { if (!type) { return `请输入${name}` } const map: { [props: string]: string } = { select: '请选择', input: '请输入', default: '请输入', } return `${map[type] || map.default}${name}` }
- 生成验证规则
// 生成验证规则 * @param rulearr 验证规则 长度只需传入{max:xxx}验证信息是统一的 * @param name 表单项label * @param required 是否必填 默认否 因为要统一写验证提示 const getformrules = (val: ruleobj[], required: boolean, name: string) => { // 根据max生成最长输入提示 const rulearr: object[] = [] val.foreach(item => { if ('max' in item) { rulearr.push({ ...item, message: `输入信息不能超过${item.max}字` }) } else { rulearr.push(item) } }) // 根据name生成报错提示 return [{ required: required, message: `${name}不能为空` }, ...rulearr]
- 根据组件类型 给出统一样式修改
/**根据组件类型 给出统一样式修改 * @param type 组件类型 根据传入组件的函数名判断 在map里维护 * @returns 样式对象 * */ const fixstylebycomptype = (type?: string): object => { if (!type) { return {} } const map: { [props: string]: object } = { select: { style: { width: '100%' } }, default: {}, } return map[type] || map.default }
注意:当出现表单显示一个详情文字或者一个按钮 此时需要用isdetail
干掉getfielddecorator托管
实现提交表单submitform
* @param form -`antd`的form * @param title - 主标题 * @param subtitle - 分组标题 * @param formiteminfos - 二维数组 顺序和数量和分组标题对应 存放表单项信息 * @param isloading - `dva-loading` 执行effects过程中的loading * @param onsubmit - 传入的submit方法 * @param buttonarea - 可选 不传默认数提交和取消 传入覆盖 * @param children - 在表单下面 按钮区域上面的传入内容 * @param formlayouttype - 布局方式 详情见`getlayoutbytype`方法 * @returns reactnode ...... <form onsubmit={onsubmit}> <submitlayout subtitle={subtitle} title={title} renderformarea={renderformarea}> {children} {buttonarea?buttonarea: <row type="flex" gutter={24} justify="center"> <col> <button type="primary" htmltype="submit" loading={isloading}>提交</button> </col> <col> <button type="default" onclick={() => router.go(-1)}>取消</button> </col> </row>} </submitlayout> </form>
对baseform
的调用在renderformarea
方法里
// 水平和垂直布局 export enum formlayouttype { normal = 'normal', vertical = 'vertical' } const renderformarea = (idx: number) => { // 在这边加上normal表单的layout const formiteminfo = formiteminfos[idx] // 根据传入参数 返回布局类型 const _formiteminfo = getlayoutbytype(formlayouttype||formlayouttype.normal, formiteminfo) return <baseform formitems={_formiteminfo} form={form}/> }
formiteminfos
这里是二维数组,二维分别承载分组信息和表单项信息 而且在这里用getlayoutbytype
封装常用的水平 垂直布局方式
这里结合了我们业务里的表单布局 大标题 和小标题对表单区域进行分组,我将布局单独搞了个submitlayout
组件 将渲染逻辑和样式组件在其中实现 这样也方便更换成其他layout
// submitlayout * @param title - 主标题 * @param subtitle - 分组标题 * @param renderformarea - 根据分组的顺序 渲染表单区域 * @param children - 传入内容 ...... <card title={title} bordered={false} classname={styles["special-card"]}> <list itemlayout="vertical" classname="special-list"> {subtitle.map((item,idx) => { return ( <> <meta title={item}/> <list.item> {renderformarea(idx)} </list.item> </> ) })} </list> {children} </card>
结语
后续还实现了serachform
等业务相关性高的组件,所以个人觉得封装的思路主要是
- 底层组件越纯粹越好
- 中层可以实现一些适用性比较高的具体业务组件和通用方法
- 高层就具体要页面的细节的方方面面了