zent
Version:
一套前端设计语言和基于React的实现
354 lines (294 loc) • 14.9 kB
Markdown
# zent-form
[](https://www.npmjs.com/package/zent-form) [](https://www.npmjs.com/package/zent-form)
表单组件
- [zent-form](#zent-form)
* [组件原理](#组件原理)
* [使用指南](#使用指南)
+ [创建表单](#创建表单)
+ [表单提交](#表单提交表单提交)
+ [封装自定义的表单元素组件](#封装自定义的表单元素组件)
+ [Field中 value 的生命周期](#field中-value-的生命周期)
* [API](#api)
+ [Form](#form)
+ [Form.createForm(options)(FormComponent)](#formcreateformoptionsformcomponent)
- [options](#options)
- [createForm返回的组件可接收的props](#createform返回的组件可接收的props)
- [zentForm prop](#zentform-prop)
- [handleSubmit prop](#handlesubmit-prop)
+ [Form.Field](#formfield)
+ [Form.getControlGroup](#formgetcontrolgroup)
+ [内置对其他zent组件的封装](#内置对其他zent组件的封装)
+ [内置validation rules](#内置validation-rules)
## 组件原理
组件核心由以下几部分组成:
- createForm函数:用来构建一个高阶组件,其中维护了表单中的所有表单元素(Field组件)实例。通过向子组件的props中注入zentForm属性来提供表单和表单元素的各种操作方法。
- Form组件:作为整个表单的最顶层骨架,是对<form>标签的简单封装,定义了默认的class来提供基础样式。
- Field组件:用来封装各种表单元素组件(如Input、Checkbox、Select以及各种自定义组件)的一个高阶组件。其中维护了表单元素value值和校验错误等信息。Field组件会向表单元素组件传入封装过的onChange、onBlur回调和value、error等表单元素需要的props。
注意:
1. 使用Form组件构造的组件必须调用createForm函数进行封装,否则children中的Field无法生效,控制台中会抛错。
2. Field组件本质上是一个辅助性的组件,本身不提供任何样式。它的作用是管理表单元素value值的生命周期和表单元素的error等信息。真正控制表单元素如何展现的是Field component属性中传入的组件。
## 使用指南
### 创建表单
```js
class FormComponent extends React.Component {
render() {
return (
<Form>
// 各种表单元素
<Field
// 表单元素名,必需要有
name="a"
type="number"
// 表单元素组件
component={InputField}
label="a"
value="2"
validations={{
required: true,
// 使用自定义校验方法时可以额外传入参数
isMoreThan: 'b'
lessThan100(values, value) {
return +value < 100;
}
}}
validationErrors={{
required: '不能为空',
isMoreThan: 'a必须大于b',
lessThan100: 'a必须小于100'
}}
/>
<Field
name="b"
type="number"
component={InputField}
label="b"
value="3"
/>
</Form>
)
}
}
FormComponent = Form.createForm({
// 在这里定义的校验方法可以在定义 validations 时额外接收一个参数。也可以在独立文件中定义,跨页面共享校验方法。
formValidations: {
isMoreThan(values, value, otherField) {
return Number(value) > Number(values[otherField])
},
...
}
})(FormComponent);
```
### 表单提交
form 组件内部对表单提交的过程进行封装,可以把异步提交的过程封装在一个func里并返回一个**promise对象**,组件内部会根据promise对象的执行结果分别调用 `onSubmitSuccess` 和 `onSubmitFail` 方法,同时更新内部维护的 `isSubmitting` 属性(可以通过zentForm.isSubmitting()得到).
```js
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const submit = (values) => {
return sleep(1000)
.then(() => {
console.log(values)
}).catch((error) => {
console.log(error)
});
}
class SubmitForm extends React.Component {
render() {
const { zentForm, handleSubmit } = this.props
const isSubmitting = zentForm.isSubmitting()
return (
<Form onSubmit={handleSubmit(submit)}>
...
<button type="submit">{isSubmitting ? '提交中' : '提交'}</button>
</Form>
)
}
}
class FormContainer extends React.Component {
onSubmitSuccess(result) {
...
}
onSubmitFail(error) {
...
}
render() {
return (
<SubmitForm
onSubmitSuccess={this.onSubmitSuccess}
onSubmitFail={this.onSubmitFail}
/>
)
}
}
```
### 封装自定义的表单元素组件
前面已经说过,Field的展示完全由传入到component属性中的组件所控制。这个组件能够接收到所有从Field传入的props(包括Field中构造的一些隐含的props,具体看下方Field API)。
对于一些常用的zent表单组件,Form已经使用getControlGroup对其进行了封装(具体参考下方API)。如果产品设计上有一些特殊的需求,或者需要封装自定义的组件,也可以直接使用或者参考getControlGroup的方式来对组件进行封装。
**如果需要在一个Field中展示多个表单元素,可以将所有的表单元素封装在一个对象中传入Field的value中。具体可以参考“封装自定义组件”那个示例。**
### Field中 value 的生命周期
表单元素的初始值需要通过在Field中指定value值传入,如果value值的生命周期如下图所示:
```text
Field 中传入 value -> 使用 format() 格式化 value -> format 过的 value 传入 component 中渲染组件
↑ |
| ↓
| 用户操作改变 value
| |
| ↓
normalize 过的 value 写入 form 中维护, 用于数据提交 <- 使用 normalize() 格式化 value
```
如果传入Field的value值是一个动态值,在外部改变value后会重新开始value的生命周期。
## API
### Form
对 html 中 form 元素的一个简单封装, 提供默认的 className.
| 参数 | 说明 | 类型 | 默认值 | 是否必填 |
|------|------|------|--------|--------|
| className | 自定义额外类名 | string | `''` | 否 |
| prefix | 自定义前缀 | string | `'zent'` | 否 |
| horizontal | 水平排列布局 | boolean | `false` | 否 |
| inline | 行内排列布局 | boolean | `false` | 否 |
| onSubmit | 表单提交回调 | func(e:Event) | `noop` | 否 |
| style | 内联样式 | object | null | 否 |
### Form.createForm(options)(FormComponent)
使用方式如下:
#### options
`options` 支持的配置项如下:
| 参数 | 说明 | 类型 | 是否必填 |
|------|------|------|------|
| formValidations | 用于添加自定义校验方法, 通过这种方式添加的方法在 validations 中使用时可以传额外的参数 | object | 否 |
*项目中的通用校验方法,可以通过在一个文件中定义公共的formValidations对象来引入。*
#### createForm返回的组件可接收的props
参数 | 说明 | 类型 | 是否必填 |
|------|------|------|------|
| onChange | 任意表单元素修改后触发的回调,参数为所有表单元素值的对象 | func(values: Object) | 否 |
| onSubmitSuccess | 提交成功后的回调,参数是submit函数中promise的返回值 | func(submitResult: any) | 否 |
| onSubmitFail | 提交失败后的回调,参数要么是SubmissionError的一个实例,要么是undefined | func(submitError: SubmissionError) | 否 |
#### zentForm prop
经过 `ZentForm.createForm` 包装的组件通过 props 被添加了 zenForm 属性, 所以在被包装的组件中可以访问到 `this.props.zentForm` 属性, `this.props.zentForm` 提供的 API 如下:
| 参数 | 说明 | 类型 |
|------|------|------|
| getFormValues | 获取与 form 绑定的所有表单元素值 | func |
| getFieldError | 获取某个 Field 的错误信息, 没有报错信息返回空 | func(name: String) |
| setFormPristine | 设置所有 Field 的状态为非原始状态, 用于在提交表单时让 Field 把没有显示出来的错误显示出来 | func(isPristine: Boolean) |
| setFieldExternalErrors | 设置外部传入的错误信息(比如服务端校验错误),errors的key为Field的name,value为错误文案 | func(errors: Object) |
| resetFieldsValue | 把所有 Field 的值恢复到指定值或初始状态 | func(data: Object) |
| isValid | 表单的所有 Field 是否都通过了校验 | func |
| isSubmitting | 表单是否正在提交 | func |
| isValidating | 表单是否有 Field 在异步校验 | func |
| isFieldTouched | Field 是否变更过值 | func(name: String) |
| isFieldValidating | Field 是否 | func(name: String) |
#### handleSubmit prop
createForm还会为被包装的组件提供一个封装过的`handleSubmit`方法,具体使用可以参考上方**表单提交**中的内容
注意:
如果希望在`onSubmitFail`回调中正确的接收到error对象,需要在submit函数中throw SubmissionError类型的对象
```js
const { SubmissionError } = Form;
...
throw new SubmissionError('error message');
...
onSubmissionFail(submissionError) {
if (submissionError && submissionError.errors === 'error message') {
// do something
}
}
```
### Form.Field
所有需要维护value的表单元素组件都需要通过Field组件包装一下。
在Field组件上可以传入以下props,component以外的其他props(包括自定义的props),都会传入到component中所定义的表单元素组件中:
| 参数 | 说明 | 类型 | 是否必填 |
|------|------|------|------|
| name | 表单元素名 | string | 是 |
| component | 真正的表单元素组件,负责表单元素如何展示。可以是字符串(标准 html 元素名), 或者 React 组件 | string / React.Component | 是 |
| normalize | onChange 或者 onBlur 后格式化表单元素值 | func(value, previousValue, nextValues, previousValues) | 否 |
| format | 渲染前格式化表单元素值, 不影响真正存储的表单元素值 | func(value, previousValue, nextValues, previousValues) | 否 |
| onChange | value 值修改后的回调(自定义组件需要调用 `props.onChange()` 才会触发),会在Field中封装一层 | func(event, newValue, previousValue, preventSetValue) | 否 |
| onBlur | blur 后的回调(会在Field中封装一层) | func(event, newValue, previousValue, preventSetValue) | 否 |
| validations | 定义表单元素校验方法 | object | 否 |
| validationErrors | 定义表单元素检验方法对应的出错信息 | object | 否 |
| asyncValidation | 异步校验 func, 需要返回 Promise | func(values, value) | 否 |
| value | 表单元素初始值 | any | 是 |
除了上述参数之外,Field组件会隐含地向被包裹的表单元素组件中传入以下props:
| 参数 | 说明 | 类型 |
|------|------|------|
| isTouched | 表单元素值被改变过 | Boolean |
| isPristine | 表单元素值没有被改变过 | Boolean |
| error | 第一个校验错误文本信息(没有报错时为null) | String / Null |
| errors | 校验错误文本信息数组(没有错误时为空数组) | Array |
### Form.getControlGroup
getControlGroup是一个用来快速封装自定义组件的函数,它返回一个满足通用布局与样式要求(左侧label、右侧表单元素)的stateless functional component。同时支持将Field中的error信息展示出来。getControlGroup实现的比较简单,可以直接看源码。
```js
export default Control => ({ required = false, helpDesc = '', label = '', className = '', ...props }) => {
const showError = props.isTouched && props.error;
const groupClassName = cx({
'zent-form__control-group': true,
'has-error': showError,
[className]: true
});
return (
<div className={groupClassName}>
<label className="zent-form__control-label">
{required ? <em className="zent-form__required">*</em> : null}
{label}
</label>
<div className="zent-form__controls">
<Control {...props} />
{showError && <p className="zent-form__error-desc">{props.error}</p>}
{helpDesc && <p className="zent-form__help-desc">{helpDesc}</p>}
</div>
</div>
);
};
```
封装过的组件支持在Field上额外传入以下参数:
| 参数 | 说明 | 类型 | 是否必填 |
|------|------|------|------|
| label | 表单元素的label | String | 否 |
| className | 添加到control-group上的额外类名,可以用来覆盖子元素的样式 | String | 否 |
| helpDesc | 表单元素的说明性文字 | String | 否 |
| required | 为true时会在label前添加红色的"*" | Boolean | 否 |
### 内置对其他zent组件的封装
为了减少代码量,Form组件内置了对常用的表单元素组件(Input、Checkbox、CheckboxGroup、RadioGroup、Select)的封装。这些组件的封装使用了上述的getControlGroup函数。
使用方法:
```js
import { Form } from 'zent';
const { Field, InputField } = Form;
...
<Field
name="name"
type="text"
label="用户名:"
value="123"
component={InputField}
helpDesc={<span>我是说明<a href="https://youzan.com">我是链接</a></span>}
required
/>
...
```
### 内置validation rules
可以直接在Field的validations属性中使用
```js
<Field
...
validations={{
required: true,
matchRegex: /^\d+/,
equals: 'pangzi',
equalsField: 'fieldName'
}}
...
/>
```
| 规则名 | 说明 | 可传参数 |
|------|------|------|
| required | 是否必填 | 任意,传true是为了表意,传其他值也是当作必填,下同 |
| isExisty | 是否非null,非undefined | 任意 |
| matchRegex | 是否匹配指定正则表达式 | Regex |
| isEmail | 是否邮件类型字符串 | 任意 |
| isUrl | 是否url类型 | 任意 |
| isTrue | 是否true | 任意 |
| isFalse | 是否false | 任意 |
| isNumeric | 是否数字类型 | 任意 |
| isInt | 是否整数 | 任意 |
| isFloat | 是否小数 | 任意 |
| isLenght | 字符串或数组是否为指定长度 | 长度值(Number) |
| equals | 是否与指定值相等 | 指定值 |
| equalsField | 是否与表单中的其他元素值相等 | 其他Field的name(String) |
| maxLength | 字符串或数组不能超过指定长度 | 长度值(Number) |
| minLength | 字符串或数组不能小于指定长度 | 长度值(Number) |