博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
小而美的 React Form 组件
阅读量:6212 次
发布时间:2019-06-21

本文共 9907 字,大约阅读时间需要 33 分钟。

背景

之间在一篇介绍过 Table 组件 的文章中讲到过,在企业级后台产品中,用的最多且复杂的组件主要包括 Table、 Form、 Chart,在处理 Table 的时候我们遇到了很多问题。今天我们这篇文章主要是分享一下 Form 组件,在业务开发中, 相对 Table 来说,Form 处理起来更麻烦,不是所有表单都像注册页面那样简单,它往往需要处理非常多的逻辑,比如:

  • 需要处理数据验证逻辑。
  • 在一个项目中的很少有可以复用的表单,每个表单的逻辑都需要单独处理。
  • 在表单中往往存在需要自定义的组件,比如:Toggle、Tree、Picker 等等。

Form 作为一个功能型组件,它需要解决的问题无非就是两个:

  • 把一个数据对象扔给它,它能够在让 Form 内的交互型组件获取到数据并展示出来,同时这些组件上的数据也能反过来让 Form 组件获取到。
  • 对数据的有效性进行验证。

在 React 项目开发中没有用过其他 Form 相关的组件,但是这里我又忍不住想和 Ant Design 的 Form 对比一下,以下是我在 antd 官网上的一个截图:

antd form

大家可以通过以上图片看到,我想输入自己的中文名字都不能正常输入,这里主要存在两个问题:

  • onChange 被触发了多次,在一次中文未完整的输入前,不应该触发 onChange 事件。
  • 另外,中文在文本框内就不能正常显示。

我在想这两个问题不在组件上处理,在哪里处理的呢?访问地址: 可以试一下,也希望他们可以解决掉这个问题。这个问题应该怎么解决,我之前做过记录: 。

我们在设计的时候自然是解决了这些问题,预览效果: ,接下来看一下具体的设计。

设计

针对前面提到 Form 需要解决的两个问题(数据校验和数据获取),在设计的时候,我们把这个两个问题作为两个功能,独立在不同的库处理。

  • 处理表单数据初始化,数据获取。
  • 定义数据模型,做数据有效性验证。

这两个库可以独立使用,如果你有自己一套自己的 Form 组件,只是缺少一个数据验证的工具,那你可以单独把 rsuite-schema 拿过来使用。

Form 定义一个表单

分别看一下它们是怎么工作的,form-lib 用起来比较简单,提供了两个组件:

  • <Form> 处理整个表单服务的逻辑
  • <Field> 处理表单中各个交互型组件的逻辑,比如 input,select

看一个示例:

安装

首先需要安装 form-lib

npm i form-lib --save

示例代码

import { Form, Field, createFormControl } from 'form-lib';const SelectField = createFormControl('select');const user = {  name:'root',  status:1};

在默认情况下 Field 是一个文本输入组件,如果你需要使用 HTML 表单中其他的标签,你可以像上面示例一样 通过 createFormControl(标签名称) 方法创建一个组件, 在把这个组件通过 accepter 属性赋给Field 组件,这样你就能像操作原生 HTML 一样设置 Field。 通过这种方式,后面在功能介绍的时候会讲到怎么把自定义组件放在 Field 中。

自定义布局

这里存在一个疑问,<Field> 必须放在 <Form> 下面第一层吗? 如果对布局没有要求,设计成这样是处理起来最方便的,因为在 <Form> 中可以直接通过 props.children 获取到所有的 <Field>,想怎么处理都可以。

刚开始我们是这样的,后来在实际应用中发现,表单的布局是有很多种,如果要设计成这样,那肯定就带来一个问题,不好自定义布局。 所以这里 <Form><Field> 之间的通信我们用的是 React 的 context。 这样的话你就可以任意布局:

Form Props

<Form><Field> 详细 API 说明,参考以下表格

<Form> Props :

名称 类型 描述
horizontal bool 设置表单内的元素左右两栏布局
inline bool 设置表单内元素在一行布局
values object 表单的值 受控组件
defaultValues object 表单的初始默认值 非受控组件
model Schema rsuite-schema 对象
checkDelay number 数据校验的时候,延迟处理,默认为 500 毫秒
checkTrigger string 数据校验的触发类型,可选项: changeblurnull,默认为:change
onChange function(values:Object, event:Object) 数据改变后的回调函数
onError function(errors:Object) 校验出错的回调函数
onCheck function(errors:Object) 数据校验的回调函数
errors object 表单错误信息

<Field> Props :

名称 类型 描述
name string 表单元素名称
accepter elementType 受代理的组件

Schema 定义一个数据模型

安装

npm i rsuite-schema --save

rsuite-schema 主要有两个对象

  • SchemaModel 用于定义数据模型。
  • Type 用于定义数据类型,包括:

    • StringType
    • NumbserType
    • ArrayType
    • DateType
    • ObjectType
    • BooleanType

这里的 Type 有点像 React 中 PropTypes 的定义。

示例代码

一个示例:

const userModel = SchemaModel(    username: StringType().isRequired('用户名不能为空'),    email: StringType().isEmail('请输入正确的邮箱'),    age: NumberType('年龄应该是一个数字').range(18, 30, '年应该在 18 到 30 岁')});

这里定义了一个 userModel, 包含 usernameemailage 3个字段, userModel 拥有了一个 check 方法, 当把数据扔进去后会返回验证结果:

const checkResult = userModel.check({    username: 'foobar',    email: 'foo@bar.com',    age: 40})// checkResult 结果:/**  {      username: { hasError: false },      email: { hasError: false },      age: { hasError: true, errorMessage: '年应该在 18 到 30 岁' }  }**/

多重验证

StringType()  .minLength(6,'不能少于 6 个字符')  .maxLength(30,'不能大于 30 个字符')  .isRequired('该字段不能为空');

自定义验证

通过 addRule 函数自定义一个规则。

如果是对一个字符串类型的数据进行验证,可以通过 pattern 方法设置一个正则表达式进行自定义验证。

const myModel = SchemaModel({    field1: StringType().addRule((value) => {        return /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(value);    }, '请输入合法字符'),    field2: StringType().pattern(/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/, '请输入合法字符')});

自定义动态错误信息

例如,要通过值的不同情况,返回不同的错误信息,参考以下

const myModel = SchemaModel({    field1: StringType().addRule((value) => {        if(value==='root'){          return {            hasError: true,            errorMessage:'不能是关键字 root'          }        }else if(!/^[a-zA-Z]+$/.test(value)){          return {            hasError: true,            errorMessage:'只能是英文字符'          }        }        return {            hasError: false        }    })});

复杂结构数据验证

const userModel = SchemaModel({  username:StringType().isEmail('正确的邮箱地址').isRequired('该字段不能为空'),  tag: ArrayType().of(StringType().rangeLength(6, 30, '字符个数只能在 6 - 30 之间')),  profile: ObjectType().shape({    email: StringType().isEmail('应该是一个 email'),    age: NumberType().min(18, '年龄应该大于18岁')  })})

更多设置,可以查看

Form 与 Schema 的结合

const userModel = SchemaModel({    username: StringType().isRequired('用户名不能为空'),    email: StringType().isEmail('请输入正确的邮箱'),    age: NumberType('年龄应该是一个数字').range(18, 30, '年应该在 18 到 30 岁')});

把定义的的SchemaModel对象赋给,<Form>model 属性,就把它们绑定起来了,<Field>name 对应 SchemaModel 对象中的 key

以上的示例代码是不完整的,没有处理错误信息和获取数据,只是为了方便大家理解。完整的示例,可以参考接下来的实践与解决方案。

实践与解决方案

一个完整的示例

import React from 'react';import { Form, Field, createFormControl } from 'form-lib';import { SchemaModel, StringType } from 'rsuite-schema';const TextareaField = createFormControl('textarea');const SelectField = createFormControl('select');const model = SchemaModel({  name: StringType().isEmail('请输入正确的邮箱')});class DefaultForm extends React.Component {  constructor(props) {    super(props);    this.state = {      values: {        name: 'abc',        status: 0      },      errors: {}    };    this.handleSubmit = this.handleSubmit.bind(this);  }  handleSubmit() {    const { values } = this.state;    if (!this.form.check()) {      console.error('数据格式有错误');      return;    }    console.log(values, '提交数据');  }  render() {    const { errors, values } = this.state;    return (      
this.form = ref} onChange={(values) => { console.log(values); this.setState({ values }); // 清除表单所有的错误信息 this.form.cleanErrors(); }} onCheck={(errors) => { this.setState({ errors }); }} values={values} model={model} >
{errors.name}
); }}export default DefaultForm;

在 rsuite 中的应用

rsuite 提供了很多 Form 相关的组件,比如 FormGroup,FormControl,ControlLabel,HelpBlock 等等, 我们通过一个例子看一下怎么结合使用。

通过上一个例子中我们可以看到,没有个 Field 中有很多公共部分,所以我们可以自定义一个无状态组件 CustomField,把 ControlLabel,Field,HelpBlock 这些表单元素都放在一起。

import React from 'react';import { Form, Field, createFormControl } from 'form-lib';import { SchemaModel, StringType, ArrayType } from 'rsuite-schema';import {  FormControl,  Button,  FormGroup,  ControlLabel,  HelpBlock,  CheckboxGroup,  Checkbox} from 'rsuite';const model = SchemaModel({  name: StringType().isEmail('请输入正确的邮箱'),  skills: ArrayType().minLength(1, '至少应该会一个技能')});const CustomField = ({ name, label, accepter, error, ...props }) => (  
{label}
{error}
);class DefaultForm extends React.Component { constructor(props) { super(props); this.state = { values: { name: 'abc', skills: [2, 3], gender: 0, status: 0 }, errors: {} }; this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit() { const { values } = this.state; if (!this.form.check()) { console.error('数据格式有错误'); return; } console.log(values, '提交数据'); } render() { const { errors, values } = this.state; return (
this.form = ref} onChange={(values) => { this.setState({ values }); console.log(values); }} onCheck={errors => this.setState({ errors })} defaultValues={values} model={model} >
Node.js
Javascript
CSS 3
未知
); }}export default DefaultForm;

自定义 Field

如果一个组件不是原生表单控件,也不是 RSuite 库中提供的基础组件,要在 form-lib 中使用,应该怎么处理呢?

只需要在写组件的时候实现以下对应的 API:

  • value : 受控时候设置的值
  • defalutValue: 默认值,非受控情况先设置的值
  • onChange: 组件数据发生改变的回调函数
  • onBlur: 在失去焦点的回调函数(可选)

接下来我们使用 rsuite-selectpicker 作为示例, 在 rsuite-selectpicker 内部已经实现了这些 API。

import React from 'react';import { SchemaModel, NumberType } from 'rsuite-schema';import { Button, FormGroup, ControlLabel, HelpBlock } from 'rsuite';import Selectpicker from 'rsuite-selectpicker';import { Form, Field } from 'form-lib';const model = SchemaModel({  skill: NumberType().isRequired('该字段不能为空')});const CustomField = ({ name, label, accepter, error, ...props }) => (  
{label}
{error}
);class CustomFieldForm extends React.Component { constructor(props) { super(props); this.state = { values: { skill: 3, }, errors: {} }; this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit() { const { values } = this.state; if (!this.form.check()) { console.error('数据格式有错误'); return; } console.log(values, '提交数据'); } render() { const { errors, values } = this.state; return (
this.form = ref} onChange={(values) => { this.setState({ values }); console.log(values); }} onCheck={errors => this.setState({ errors })} defaultValues={values} model={model} >
); }}export default CustomFieldForm;

更多示例:

如果你在使用中存在任何问题,可以提交 issues,如果你有什么好的想法欢迎你 pull request,GitHub地址:

  • rsuite:
  • form-lib:
  • rsuite-schema:

转载地址:http://madja.baihongyu.com/

你可能感兴趣的文章
光伏发电如何破局“十三五”?
查看>>
爱立信CTO在乌镇:“5G与物联网推动互联网未来发展”
查看>>
面对大数据与“工业4.0”时代 我们该做些什么?
查看>>
斯诺登称特朗普当选是美国历史的“黑暗时刻” 但不是结束
查看>>
软银与ARM联合公开信:ARM已成软银集团一员 业务如常
查看>>
Facebook为自家Live Video流媒体直播平台推出“实时世界地图”
查看>>
Alphabet重组机器人部门 纳入X管理体系
查看>>
资本“钟情”移动医疗,医学影像等或成下一个热点
查看>>
代理和AOP
查看>>
Veritas备份与恢复解决方案新添全新功能包
查看>>
机器学习常用算法
查看>>
癌细胞最偏爱10个字,你却每天都在喂养“它”!
查看>>
右击 -> 查看源文件,和其他一些前端性能测试技巧
查看>>
这56家公司共同发力智慧城市
查看>>
MIT开发Polaris,使网页载入加快34%
查看>>
《Cisco IPv6网络实现技术(修订版)》一2.8 参考文献
查看>>
深圳卓炎科技|企业网站建设|提高内容网站粘度的常见方法
查看>>
OS X Eagle Peak:据说这是 Mac 新系统名称
查看>>
《大规模元搜索引擎技(1)》一1.1 Web上查找信息
查看>>
英国政府采用基于 LibreOffice 的开源办公套件 "GovOffice"
查看>>