react?表單數(shù)據(jù)形式配置化設(shè)計(jì)
前言
在日常的中后臺(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)容上更容易理解。
到此這篇關(guān)于react 表單數(shù)據(jù)形式配置化設(shè)計(jì)的文章就介紹到這了,更多相關(guān)react 表單數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Hooks之使用useCallback和useMemo進(jìn)行性能優(yōu)化方式
這篇文章主要介紹了React Hooks之使用useCallback和useMemo進(jìn)行性能優(yōu)化方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06React + Node.js實(shí)現(xiàn)圖片上傳功能
最近筆者在開(kāi)發(fā)個(gè)人博客的后臺(tái)管理系統(tǒng),里面用到了圖片上傳相關(guān)的功能,在這里記錄并分享一下,希望可以幫到大家,話不多說(shuō)直接開(kāi)始吧,感興趣的朋友可以參考下2024-01-01React DOM-diff 節(jié)點(diǎn)源碼解析
這篇文章主要為大家介紹了React DOM-diff節(jié)點(diǎn)源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02React如何將組件渲染到指定DOM節(jié)點(diǎn)詳解
這篇文章主要給大家介紹了關(guān)于React如何將組件渲染到指定DOM節(jié)點(diǎn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)下吧。2017-09-09為什么說(shuō)form元素是React的未來(lái)
這篇文章主要介紹了為什么說(shuō)form元素是React的未來(lái),本文會(huì)帶你聊聊React圍繞form的布局與發(fā)展,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06基于Webpack5 Module Federation的業(yè)務(wù)解耦實(shí)踐示例
這篇文章主要為大家介紹了基于Webpack5 Module Federation的業(yè)務(wù)解耦實(shí)踐示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12