资讯专栏INFORMATION COLUMN

项目中react表单数据形式配置化设计

3403771864 / 349人阅读

  作为开发和前段人员,日常中经常接触到有关表单。其实表单在日常中工作内容虽然是重复,也要不停在写 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 || '-' :</p>
  <p>
  getFieldDecorator('name', {</p>
  <p>
  initialValue: obj.name,</p>
  <p>
  })(
  <Input placeholder="请输入" />
  )
  }
  </FormItem>
  <FormItem label="性别" >
  {isEdit ? obj.sex || '-' :</p>
  <p>
  getFieldDecorator('sex', {</p>
  <p>
  initialValue: obj.sex,</p>
  <p>
  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 || '-' :</p>
  <p>
  getFieldDecorator('phone', {</p>
  <p>
  initialValue: obj.phone,</p>
  <p>
  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));
  }

  实现思路

1.png

  如上图所示,表单组件的数据在详情页中显示文本亦或是编辑页中,其实显而易见这两个应用都是一样,只是展现形式不同而已。在这里我们暂时将数据的形式定为表单组件形式与文本形式。其实在实际的使用中,由于数据的收集形式不同,会出现第三种数据形式。它就是表单文本形式,一种以文本展示但数据可被表单自动收集的形式,我把它定义为 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', {</p>
  <p>
  initialValue: 'egg',</p>
  <p>
  })(<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) :</p>
  <p>
  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 => (</p>
  <p>
  <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.png

   2. 触发校验

3.png

  3.详情

4.png

  已全部讲述完毕,即使在目前的前端领域,关于页面配置、可视化等更加复杂的能力,在各个方面都全面更新和展现。但优化还是要不断继续。

  

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/128139.html

相关文章

  • React

    摘要:基础创建虚拟参数元素名称,例如参数属性集合,例如,,,从参数开始,表示该元素的子元素,通常这些元素通过创建,文本文件可以直接插入嘻嘻哈哈这是渲染器,将元素渲染到页面中。 React简介 FeceBook开源的一套框架,专注于MVC的视图V模块。实质是对V视图的一种实现。 React框架的设计没有过分依赖于某个环境,它自建一套环境,就是virtual DOM(虚拟DOM)。 提供基础AP...

    hlcc 评论0 收藏0
  • vue和react的差异

    摘要:而中实现原理是利用高阶函数通过将多个函数组合成一个可执行执行函数关键步骤代码如下所示。和都是基于更新差异元素。 引言 平时开发单页项目应用基于vue,目前另外两个比较热的库还有angular和react,angular 1系列用过,进入公司后由于基于vue技术栈就没在关注了。一直在关注react,目的不是学习用法,只是为了拓展自己的视野和思维,通过了解一些使用上的差异性,来进一步的思考...

    OnlyLing 评论0 收藏0
  • 重磅:前端 MVVM 与 FRP 的升阶实践 —— ReRest 可视编程

    摘要:是前端开发领域新兴的方法论体系,它继承了与编程理念,在技术上有不少创新。但专利与开源协议是平行的两个世界,改底层也不大容易解决问题。此外,要求在中结合各属性的是否变化,判断是否该触发更新。 ReRest (Reactive Resource State Transfer) 是前端开发领域新兴的方法论体系,它继承了 MVVM 与 FRP 编程理念,在技术上有不少创新。本文从专利稿修改而来...

    Cciradih 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<