網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
在日常的中后臺(tái)系統(tǒng)開(kāi)發(fā)中,表單是和我們打交道非常多的名詞。但是在一般的表單實(shí)現(xiàn)中、我們會(huì)做著很多重復(fù)的工作,不停在寫(xiě) FormItem...,以及為組件加上“請(qǐng)輸入/請(qǐng)選擇”等無(wú)腦的 placeholder 文本和“請(qǐng)輸入xx/請(qǐng)選擇xx”等必填提示。其次表單一般都存在編輯頁(yè)和詳情頁(yè),而為了代碼更好的維護(hù)性通常會(huì)將編輯和詳情用一套代碼實(shí)現(xiàn)。此時(shí)我們的代碼里就會(huì)出現(xiàn)“isEdit ? 表單組件 : 純文本”
這樣無(wú)腦且重復(fù)率高的代碼。秉承著更少代碼更多產(chǎn)出的原則,我設(shè)計(jì)了一套配置化邏輯來(lái)提升這一開(kāi)發(fā)體驗(yàn)。
實(shí)現(xiàn)
一般實(shí)現(xiàn)
// 一般實(shí)現(xiàn) import React from 'react'; import { Form, Input, Select } from 'antd'; const Demo = (props) => { const { form: { getFieldDecorator }, obj = {}, isEdit } = props; return ( <> <FormItem label="姓名" > {isEdit ? obj.name || '-' : getFieldDecorator('name', { initialValue: obj.name, })( <Input placeholder="請(qǐng)輸入" /> ) } </FormItem> <FormItem label="性別" > {isEdit ? obj.sex || '-' : getFieldDecorator('sex', { initialValue: obj.sex, rules: [{ required: true, message: '請(qǐng)選擇性別' }], })( <Select placeholder="請(qǐng)選擇" > <Option key="male" value="male">男</Option> <Option key="female" value="female">女</Option> </Select> ) } </FormItem> <FormItem label="手機(jī)號(hào)" > {isEdit ? obj.phone || '-' : getFieldDecorator('phone', { initialValue: obj.phone, rules: [{ required: true, message: '請(qǐng)輸入手機(jī)號(hào)' }], })( <Input placeholder="請(qǐng)輸入" /> ) } </FormItem> <> ) }
配置化的實(shí)現(xiàn)
// 配置化的實(shí)現(xiàn) import React from 'react'; import { renderDataForm } from 'src/util/renderDataForm'; const Demo = (props) => { const { form, obj = {}, isEdit } = props; const conf = [{ label: '姓名', // 表單的label field: 'name', // 表單字段名 initialValue: obj.name, // 表單默認(rèn)值 required: false, // 是否必填、默認(rèn)必填 }, { label: '性別', field: 'sex', initialValue: obj.sex, formItemType: 'Select', // 表單類(lèi)型默認(rèn) Input options: [{ value: 'male', label: '男' }, { value: 'female', label: '女' }], // 下拉選項(xiàng) }, { label: '手機(jī)號(hào)', field: 'phone', initialValue: obj.phone, }]; const dataForm = isEdit ? 'form' : 'text'; // 傳入form,表單配置,想要的數(shù)據(jù)形式 return renderDataForm(form, conf, dataForm)); }
實(shí)現(xiàn)思路
?如上圖所示,無(wú)論是在詳情頁(yè)中顯示文本亦或是編輯頁(yè)中的表單組件包裹的數(shù)據(jù),其實(shí)本身所對(duì)應(yīng)的都是同一個(gè)數(shù)據(jù),只是展示形式不一樣而已。在這里我們暫時(shí)將數(shù)據(jù)的形式定為表單組件形式與文本形式。其實(shí)在實(shí)際的使用中,由于數(shù)據(jù)的收集形式不同,會(huì)出現(xiàn)第三種數(shù)據(jù)形式。它就是表單文本形式,一種以文本展示但數(shù)據(jù)可被表單自動(dòng)收集的形式,我把它定義為 FormText(如下所示)。至此,針對(duì)實(shí)現(xiàn)數(shù)據(jù)詳情與編輯形式的方案有了這樣兩種,表單與文本組合或表單與表單文本組合的實(shí)現(xiàn)。本次我選擇表單與文本組合的方案。
/** * 用于 Form 表單內(nèi)部受控展示文本 */ export default class FormText extends Component { render() { const { value, formatMethod = a => a, defaultText = '-', ...resetProps } = this.props; return <span {...resetProps}>{formatMethod(value) || defaultText}</span>; } } // 使用 <FormItem label="姓名"> {getFieldDecorator('name', { initialValue: 'egg', })(<FormText />)} </FormItem>
具體實(shí)現(xiàn)
1、形式選擇(表單組件 or 文本)
const renderDataForm = (form, conf = {}, dataForm = 'form') => { // customRenderText 自定義文本形式 const { customRenderText } = conf; return ( <FormItem label={conf.label} {...conf.formItemProps} > {dataForm === 'form' ? renderFormItem(form, conf) : customRenderText ? customRenderText(conf) : renderText(conf) } </FormItem> ); };
2、表單組件選擇
export const renderFormItem = (form, rest) => { const { getFieldDecorator } = form; const { label = '', field = '', formItemType = 'input', initialValue, required = true, rules = [], ...itemRest } = rest; return (getFieldDecorator(field, { initialValue, rules: [ // 必填提示 { required, message: renderMessage(formItemType, label) }, ...rules, ], ...(formItemType === 'upload' ? { // Upload組件的通用配置 getValueFromEvent: (e) => { if (Array.isArray(e)) { return e; } return e && e.fileList; }, valuePropName: 'fileList' } : {}), })( renderItem(formItemType, itemRest) )); }; // 選擇表單組件 const renderItem = (formItemType, itemRest) => { const { options = [], CustomFormItem } = itemRest; const obj = { Input, TextArea, InputNumber, Upload, Select, RadioGroup, CheckboxGroup, DatePicker }; // 自定義的表單組件 if (formItemType === 'CustomFormItem') { return <CustomFormItem {...itemRest} />; } // 不存在對(duì)應(yīng)組件時(shí)返回默認(rèn)的 Input 組件 if (!obj[formItemType]) { return <Input placeholder="請(qǐng)輸入" {...itemRest} />; } const Comp = obj[formItemType]; // 雙標(biāo)簽組件處理 if (['Select', 'Upload'].includes(formItemType)) { return formItemType === 'Upload' ? ( <Upload {...itemRest} > <Button><Icon type="upload" />上傳</Button> </Upload> ) : ( <Comp {...getDefaultCompProps(itemRest)} {...itemRest} > {options.map(el => ( <Option key={el.value} value={el.value}>{el.label || el.name}</Option>))} </Comp> ); } // 單標(biāo)簽組件 return <Comp {...getDefaultCompProps(itemRest)} {...itemRest} />; }; // 獲取組件屬性 const getDefaultCompProps = (conf = {}) => { const { formItemType } = conf; const props = {}; props.placeholder = renderMessage(formItemType); if (formItemType === 'InputNumber') { // zeroOmit 小數(shù)點(diǎn)后多余的零是否省略,limitDecimal 限制最長(zhǎng)的小數(shù)位數(shù) const { zeroOmit = true, limitDecimal = 6 } = conf; const limitDecimalsF = (value) => { const reg = new RegExp(`^(-)*(\\d+)\\.(\\d{${limitDecimal}}).*$`); return `${value}`.replace(reg, '$1$2.$3'); }; if (zeroOmit) { props.formatter = limitDecimalsF; props.parse = limitDecimalsF; } } if (formItemType === 'Input') { props.maxLength = 100; // 輸入框的默認(rèn)最大輸入字符長(zhǎng)度 } if (formItemType === 'TextArea') { props.maxLength = 500; // 文本框的默認(rèn)最大輸入字符長(zhǎng)度 } return props; };
3、映射文本
export const renderText = (rest) => { const { formItemType = 'Input', initialValue, selectOptions = [], selectMode = '', options = [] } = rest; switch (formItemType) { case 'RadioGroup': return (options.find(item => item.value === initialValue) || {}).label || '-'; case 'DatePick': const { format = 'YYYY-MM-DD HH:mm:ss' } = rest; // 日期組件組件值格式化為對(duì)應(yīng)的 文本 return initialValue !== undefined ? moment(initialValue).format(format) : '-'; // ...code default: return bizStringFormat(initialValue); // 無(wú) 值 時(shí) 默認(rèn) ‘-' } }
4、通用校驗(yàn)規(guī)則整理
export const postCode = /^[0-9]{6}$/; export const phone = /^1\d{10}$/; // ...其他正則 // form rules export const postCodeRule = { pattern: postCode, message: '請(qǐng)輸入6位數(shù)字', }; export const phoneRule = { pattern: phone, message: '請(qǐng)輸入11位號(hào)碼', }; // ...其他表單校驗(yàn)規(guī)則
使用示例
const Demo = (props) => { const { form } = props; // 數(shù)據(jù) const obj = { email: '123@egg.com', addr: '派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星', sort: 'up', birthday: '1999-01-23', sex: 'male', file: [{ fileId: '123', name: '信用承諾書(shū)', size: 1024 }], }; // 因?yàn)閿?shù)據(jù)的形式默認(rèn)為表單,所以 dataForm: 'form' 可不配置 const formConf = [{ label: '郵箱', field: 'email', initialValue: obj.email, rules: [emailRule], // emailRule 為郵箱校驗(yàn)規(guī)則 }, { label: '地址', field: 'addr', initialValue: obj.addr, formItemType: 'TextArea', }, { label: '排序', field: 'sort', initialValue: obj.sort, formItemType: 'Select', options: [{ value: 'up', label: '升序' }, { value: 'down', label: '降序' }], }, { label: '生日', field: 'birthday', initialValue: obj.birthday, formItemType: 'DatePicker', format: 'YYYY-MM-DD', // 日期組件的格式配置字段 }, { label: '性別', field: 'sex', initialValue: obj.sex, formItemType: 'RadioGroup', options: [{ value: 'male', label: '男' }, { value: 'female', label: '女' }], }, { label: '信用承諾書(shū)', field: 'file', initialValue: obj.file, formItemType: 'Upload', }]; const dataForm = isEdit ? 'form' : 'text'; // 將配置遍歷傳入renderDataForm // 當(dāng)然你也可以封裝成組建,直接向組建傳入 form、formConf,減少遍歷的重復(fù)書(shū)寫(xiě)和整潔 return formConf.map(item => renderDataForm(form, item, dataForm));
最終呈現(xiàn)如下:
1.編輯
?2. 觸發(fā)校驗(yàn)
3.詳情
總結(jié)
雖然,在目前的前端領(lǐng)域,關(guān)于頁(yè)面配置、可視化等更加復(fù)雜的能力,已有更豐富和更全面的實(shí)現(xiàn)。比如我們前端團(tuán)隊(duì)的無(wú)相應(yīng)用早已實(shí)現(xiàn)整個(gè)表單頁(yè)的配置化能力。而本文展示的表單塊的代碼配置化能力接入較為輕量、內(nèi)容上更容易理解。
原文鏈接:https://juejin.cn/post/7119639489567260686
相關(guān)推薦
- 2022-02-13 自動(dòng)化進(jìn)行Pod的擴(kuò)縮容-HPA
- 2022-11-01 Git操作規(guī)范之tag的使用技巧詳解_相關(guān)技巧
- 2024-04-05 @Version樂(lè)觀(guān)鎖配置mybatis-plus使用(version)
- 2023-02-04 Go語(yǔ)言中websocket的使用demo分享_Golang
- 2022-12-29 Kotlin使用滾動(dòng)控件RecyclerView實(shí)例教程_Android
- 2022-11-23 GoLang?strings.Builder底層實(shí)現(xiàn)方法詳解_Golang
- 2022-04-18 Taro編譯小程序的時(shí)候,就沒(méi)有錄音權(quán)限,沒(méi)有scope.record這個(gè)權(quán)限
- 2022-04-21 C#實(shí)現(xiàn)chart控件動(dòng)態(tài)曲線(xiàn)繪制_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支