網站首頁 編程語言 正文
前言
在日常的中后臺系統開發中,表單是和我們打交道非常多的名詞。但是在一般的表單實現中、我們會做著很多重復的工作,不停在寫 FormItem...,以及為組件加上“請輸入/請選擇”等無腦的 placeholder 文本和“請輸入xx/請選擇xx”等必填提示。其次表單一般都存在編輯頁和詳情頁,而為了代碼更好的維護性通常會將編輯和詳情用一套代碼實現。此時我們的代碼里就會出現“isEdit ? 表單組件 : 純文本”
這樣無腦且重復率高的代碼。秉承著更少代碼更多產出的原則,我設計了一套配置化邏輯來提升這一開發體驗。
實現
一般實現
// 一般實現 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="請輸入" /> ) } </FormItem> <FormItem label="性別" > {isEdit ? obj.sex || '-' : getFieldDecorator('sex', { initialValue: obj.sex, rules: [{ required: true, message: '請選擇性別' }], })( <Select placeholder="請選擇" > <Option key="male" value="male">男</Option> <Option key="female" value="female">女</Option> </Select> ) } </FormItem> <FormItem label="手機號" > {isEdit ? obj.phone || '-' : getFieldDecorator('phone', { initialValue: obj.phone, rules: [{ required: true, message: '請輸入手機號' }], })( <Input placeholder="請輸入" /> ) } </FormItem> <> ) }
配置化的實現
// 配置化的實現 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, // 表單默認值 required: false, // 是否必填、默認必填 }, { label: '性別', field: 'sex', initialValue: obj.sex, formItemType: 'Select', // 表單類型默認 Input options: [{ value: 'male', label: '男' }, { value: 'female', label: '女' }], // 下拉選項 }, { label: '手機號', field: 'phone', initialValue: obj.phone, }]; const dataForm = isEdit ? 'form' : 'text'; // 傳入form,表單配置,想要的數據形式 return renderDataForm(form, conf, dataForm)); }
實現思路
?如上圖所示,無論是在詳情頁中顯示文本亦或是編輯頁中的表單組件包裹的數據,其實本身所對應的都是同一個數據,只是展示形式不一樣而已。在這里我們暫時將數據的形式定為表單組件形式與文本形式。其實在實際的使用中,由于數據的收集形式不同,會出現第三種數據形式。它就是表單文本形式,一種以文本展示但數據可被表單自動收集的形式,我把它定義為 FormText(如下所示)。至此,針對實現數據詳情與編輯形式的方案有了這樣兩種,表單與文本組合或表單與表單文本組合的實現。本次我選擇表單與文本組合的方案。
/** * 用于 Form 表單內部受控展示文本 */ 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>
具體實現
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} />; } // 不存在對應組件時返回默認的 Input 組件 if (!obj[formItemType]) { return <Input placeholder="請輸入" {...itemRest} />; } const Comp = obj[formItemType]; // 雙標簽組件處理 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> ); } // 單標簽組件 return <Comp {...getDefaultCompProps(itemRest)} {...itemRest} />; }; // 獲取組件屬性 const getDefaultCompProps = (conf = {}) => { const { formItemType } = conf; const props = {}; props.placeholder = renderMessage(formItemType); if (formItemType === 'InputNumber') { // zeroOmit 小數點后多余的零是否省略,limitDecimal 限制最長的小數位數 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; // 輸入框的默認最大輸入字符長度 } if (formItemType === 'TextArea') { props.maxLength = 500; // 文本框的默認最大輸入字符長度 } 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; // 日期組件組件值格式化為對應的 文本 return initialValue !== undefined ? moment(initialValue).format(format) : '-'; // ...code default: return bizStringFormat(initialValue); // 無 值 時 默認 ‘-' } }
4、通用校驗規則整理
export const postCode = /^[0-9]{6}$/; export const phone = /^1\d{10}$/; // ...其他正則 // form rules export const postCodeRule = { pattern: postCode, message: '請輸入6位數字', }; export const phoneRule = { pattern: phone, message: '請輸入11位號碼', }; // ...其他表單校驗規則
使用示例
const Demo = (props) => { const { form } = props; // 數據 const obj = { email: '123@egg.com', addr: '派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星派大星', sort: 'up', birthday: '1999-01-23', sex: 'male', file: [{ fileId: '123', name: '信用承諾書', size: 1024 }], }; // 因為數據的形式默認為表單,所以 dataForm: 'form' 可不配置 const formConf = [{ label: '郵箱', field: 'email', initialValue: obj.email, rules: [emailRule], // emailRule 為郵箱校驗規則 }, { 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: '信用承諾書', field: 'file', initialValue: obj.file, formItemType: 'Upload', }]; const dataForm = isEdit ? 'form' : 'text'; // 將配置遍歷傳入renderDataForm // 當然你也可以封裝成組建,直接向組建傳入 form、formConf,減少遍歷的重復書寫和整潔 return formConf.map(item => renderDataForm(form, item, dataForm));
最終呈現如下:
1.編輯
?2. 觸發校驗
3.詳情
總結
雖然,在目前的前端領域,關于頁面配置、可視化等更加復雜的能力,已有更豐富和更全面的實現。比如我們前端團隊的無相應用早已實現整個表單頁的配置化能力。而本文展示的表單塊的代碼配置化能力接入較為輕量、內容上更容易理解。
原文鏈接:https://juejin.cn/post/7119639489567260686
相關推薦
- 2022-11-14 基于統計自適應線性回歸-目標尺寸預測
- 2022-10-20 Swift繼承Inheritance淺析介紹_Swift
- 2022-10-22 關于redis的延遲雙刪策略總結_Redis
- 2022-04-15 C語言的程序環境與預處理你真的了解嗎_C 語言
- 2022-07-30 一文掌握Python正則表達式_python
- 2022-09-26 車載藍牙PIN碼是什么
- 2022-08-19 ubuntu上設置Redis開機自啟
- 2022-04-25 可空類型Nullable<T>用法詳解_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支