`
- 负责在表单控件下方插入 Field 的 extraText
withFieldOption 具体配置可参考[withField Option](#withFieldOption)
你的自定义受控组件需要做以下事情:
值发生变化时,调用props.onChange并且将最新的值作为入参
响应props.value的变化,并更新你的组件UI渲染结果
```jsx
withField(YourComponent, withFieldOption);
```
```jsx live=true dir="column" hideInDSM
import React from 'react';
import { withField, Form } from '@douyinfe/semi-ui';
class withFieldDemo1 extends React.Component {
constructor() {
super();
}
render() {
// 这里将html原生的input封装
const htmlInput = (props) => {
let value = props.value || '';
let { validateStatus, ...rest } = props; // prevent props being transparently transmitted to DOM
return ;
};
const CustomInput = withField(htmlInput, { valueKey: 'value', onKeyChangeFnName: 'onChange', valuePath: 'target.value' });
// 观察formState,看input的数据流是否已被form接管
const ComponentUsingFormState = () => {
const formState = useFormState();
return (
{JSON.stringify(formState)}
);
};
return (
);
}
}
```
```jsx live=true dir="column" hideInDSM
import React from 'react';
import { withField, Input, Select, Form } from '@douyinfe/semi-ui';
class withFieldDemo2 extends React.Component {
constructor() {
super();
}
render() {
// 此处纯粹是为了在同一个playground中演示,将组件的声明放在了render函数中。
// 实际业务上,建议将组件声明以及withField封装抽离至外部,作为单独组件声明
const MyComponent = (props) => {
const { onChange, value } = props;
const { name, role } = value || {};
const handleChange = (v, type) => {
let newValue = { ...value, [type==='name' ? 'name' : 'role']: v };
onChange(newValue);
};
return (
handleChange(v, 'name')} style={{ width: 180, marginRight:12 }} />
);
};
const CustomField = withField(MyComponent, { valueKey: 'value', onKeyChangeFnName: 'onChange' });
const ComponentUsingFormState = () => {
const formState = useFormState();
return (
{JSON.stringify(formState)}
);
};
return (
);
}
}
```
## API 参考
## Form Props
| 属性 | 说明 | 类型 | 默认值 |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | ---------- |
| autoScrollToError | 若为 true,submit 或者调用 formApi.validate()校验失败时,将会自动滚动至出错的字段。object 型配置参考[options](https://github.com/stipsan/scroll-into-view-if-needed#options)
**在 v0.33.0 开始提供** | boolean\| object | false |
| className | form 标签的 classname | string |
| getFormApi | form mounted 时会回调该函数,将 formAPI 作为参数传入。formApi 可用于修改 form 内部状态(值、校验状态、错误信息) | function(formApi:object) | |
| initValues | 用于统一设置表单初始值(仅会在组件挂载时消费一次),例如{fieldA:'hello', fieldB:['arr1', 'arr2']} | object | |
| onChange | form 更新时触发,包括表单控件挂载/卸载/值变更/blur/验证状态变更/错误提示变更, 入参为 formState | function(formState:object) | |
| onValueChange | form 的值被更新时触发,仅在表单控件值发生变化时触发。第一个入参为 formState.values,第二个入参为当前发生变化的 field | function(values:object, changedValue: object) | |
| onReset | 点击 reset 按钮或调用 `formApi.reset()`时的回调函数 | function() | |
| onSubmit | 点击 submit 按钮或调用 `formApi.submitForm()`,数据验证成功后的回调函数 | function(values:object) | |
| onSubmitFail | 点击 submit 按钮或调用 `formApi.submitForm()`,数据验证失败后的回调函数 | function(errors:object, values:object) | |
| validateFields | Form 级别的自定义校验函数,submit 时或 formApi.validate 时会被调用(配置Form级别校验器后,Field级别校验器在submit或formApi.validate()时不会再被触发)。支持同步校验、异步校验 | function(values) | |
| component | 用于声明表单控件,不可与 render、props.children 同时使用 | ReactNode | |
| render | 用于声明表单控件,不可与 component、props.children 同时使用 | function |
| allowEmpty | 是否保留values中为空值的field的key,true时保留key,false时移除key | boolean | false |
| layout | Form 表单控件间的布局,目前支持水平(horizontal)、垂直(vertical)两种 | string | 'vertical' |
| labelPosition | 统一配置Field 中 label 的位置,可选'top'、'left'、'inset'(inset 标签内嵌仅部分组件支持) | string | 'top' |
| labelWidth | 统一配置label 宽度 | string\|number | |
| labelAlign | 统一配置label 的 text-align 值 | string | 'left' |
| style | 可将内联样式传入 form 标签 | object |
| wrapperCol | 统一应用在每个 Field 上的布局,同[Col 组件](/zh-CN/basic/grid#Col),设置`span`、`offset`值,如{span: 20, offset: 4} | object |
| labelCol | 统一应用在每个 Field 的 label 标签布局,同[Col 组件](/zh-CN/basic/grid#Col),设置`span`、`offset`值,如{span: 6, offset: 2} | object |
| disabled | 统一应用在每个 Field 的 disabled 属性
**在 v0.35.0 开始提供** | boolean | false |
| showValidateIcon | Field 内的校验信息区块否自动添加对应状态的 icon 展示
**在 v1.0.0 开始提供** | boolean | true |
| extraTextPosition | 统一应用在每个 Field 上的extraTextPosition属性,控制extraText的显示位置,可选`middle`(垂直方向以Label、extraText、Field主体的顺序显示)、`bottom` (垂直方向以Label、Field主体、extraText的顺序显示)
**在 v1.9.0 开始提供** | string | 'bottom' |
## FormState
FormState 存储了所有 Form 内部的状态值,包括各表单控件的值,错误信息、touched 状态
进行表单提交时,实际提交的就是 formState.values
| Name | 说明 | 初始值 | 示例 |
| ------- | ------------------------------------------------------------------- | ------ | ------------------------------- |
| values | 表单的值 | {} | { fieldA: 'str', fieldB: true } |
| errors | 表单错误信息集合,你可以通过判断是否有错误信息来决定是否允许用户提交 | {} | { fieldA: 'length not valid'} |
| touched | 用户点击过的 field 集合 | {} | { fieldA: true } |
### 如何访问 formState
- 通过调用 formApi.getFormState()
- 通过[child render function 方式声明表单](#支持的其他写法),formState 会作为参数注入
- 通过[render props 方式声明表单](#支持的其他写法),formState 会作为参数注入
- 通过[useFormState](#useFormState) hook
- 通过[withFormState](#HOC-withFormState) HOC
## FormApi
我们提供了 FormApi。你在 Form 内部、外部都可以很方便地获取到 formApi,它允许你使用 getter 和 setter 来获取和操作 formState 的值。
下面的表格描述了 formApi 中可用的功能。
| Function | 说明 | example |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| getFormState | 获取 FormState | formApi.getFormState() |
| submitForm | 可手动触发 submit 提交操作 | formApi.submitForm() |
| reset | 可手动对 form 进行重置 | formApi.reset() |
| validate | 可手动触发对表单的校验,不传参时默认触发整全体Field的校验(配置Form级别校验器后,Field级别校验器在submit或formApi.validate()时不会再被触发),若想触发部分field的校验,将目标field数组传入即可 | formApi.validate()
.then(values=>{})
.catch(errors=>{})
或 formApi.validate(\['fieldA','fieldB'\])
|
| setValues | 设置整个表单的值。第二个参数中的 isOverride 默认为 false
默认情况下只会从`newValues`中取 Form 中已存在的 field 的值更新到`formState.values`中。
当 isOverride 为`true`时,会直接以 newValues 覆盖赋值给 formState.values | formApi.setValues(newValues: object, { isOverride: boolean }) |
| setValue | 提供直接修改 formState.values 方法,与 setValues 的区别是它仅修改单个 field | formApi.setValue(field: string, newFieldValue: any) |
| getValue | 获取 单个 Field 的值 | formApi.getValue()
formApi.getValue(field: string) |
| getValues | 获取 所有 Field 的值
**在 v0.35.0 开始提供 | formApi.getValues() |
| setTouched | 修改 formState.touched | formApi.setTouched(field: string, isTouched: boolean)
|
| getTouched | 获取 Field 的 touched 状态 | formApi.getTouched(field: string) |
| setError | 修改 某个 field 的 error 信息 | formApi.setError(field: string, fieldErrorMessage: string) |
| getError | 获取 Field 的 error 状态 | formApi.getError(field: string) |
| getFieldExist | 获取 Form 中是否存在对应的 field | formApi.getFieldExist(field: string) |
| scrollToField | 滚动至指定的 field
**在 v0.33.0 开始提供** | formApi.scrollToField(field: string, scrollOpts: object |
### 如何获取 formApi
- Form 组件在 ComponentDidMount 阶段,会执行 props 传入的 getFormApi 回调,你可以在回调函数中保存 formApi 的引用,以便后续进行调用(**示例如下代码**)
除此之外,我们还提供了其他方式获取 formApi,你可以根据喜好选择不同的调用方式
- 通过ref的方式获取Form组件实例,直接访问实例上的formApi
- 通过[child render function 方式声明表单](#支持的其他写法),formApi 会作为参数注入
- 通过[render props 方式声明表单](#支持的其他写法),formApi 会作为参数注入
- 通过[useFormApi](#useFormApi) hook
- 通过[withFormApi](#HOC-withFormApi) HOC
```jsx
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
class FormApiDemo extends React.Component {
constructor() {
super();
this.getFormApi = this.getFormApi.bind(this);
this.formBRef = React.createRef();
}
getFormApi(formApi) {
this.formApi = formApi;
// 获取到formApi对象后,你可以使用它来对表单进行任何你想做的修改 ~
}
changeValues() {
// 使用 FormA的 formApi
this.formApi.setValues({ a: 1});
// 使用 FormB的 formApi
this.formBRef.current.formApi.setValues({ b: 2});
}
render() {
return (
<>
{/* 通过getFormApi回调获取并保存formApi */}
{/* 通过ref直接获取Form组件实例上的formApi */}
>
);
}
}
```
```jsx
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
() => {
// 函数式组件通过useRef存储formApi
const api = useRef();
return (
);
};
```
## Field Props
v1.30.0之前的版本,Field组件并不会做ref转发
v1.30后可直接通过ref获取底层控件实例,例如给Form.Input、Form.Select指定ref,直接获取到底层原始Input、Select组件的ref引用
| 属性 | 说明 | 类型 | 默认值 |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------- |
| field | 该表单控件的值在 formState.values 中的映射路径,Form 会使用该值来区分内部的表单控件
**必填!!!** 示例:[Bindding Syntax](#表单控件值的绑定) | string | |
| label | 该表单控件的 label 标签文本,不传的时候默认与 field 同名, 传入 object 时会将其透传给 Form.Label,具体配置请参考[Label](#Form.Label) | string\|object |
| labelPosition | 该表单控件的 label 位置,可选'top'/'left'/'inset'。在Form与Field上同时传入时,以Field props为准
**v0.27.0 开始提供** | string |
| labelAlign | 该表单控件的 label 文本的 text-align。在Form与Field上同时传入时,以Field props为准
**v0.27.0 开始提供** | string |
| labelWidth | 该表单控件的 label 文本的 width。在Form与Field上同时传入时,以Field props为准
**v0.27.0 开始提供** | string\|number |
| noLabel | 当你不需要自动添加 label 时,可以将该值置为 true | boolean |
| noErrorMessage | 当你不需要自动添加 ErrorMessage 模块时,可以将该值置为 true,注意此时 helpText 也不会被展示 | boolean |
| name | 控件名称,传入时会自动在对应 field 的 div 中追加对应的 className,如:money => '.semi-form-field-money' | string |
| fieldClassName | 整个 fieldWrapper 的 className,作用与 name 参数一致,区别是不会自动追加前缀 | string |
| fieldStyle | 整个 fieldWrapper 的 内联样式
**v1.15.0开始提供** | object |
| initValue | 该表单控件的初始值(仅在 Field mounted 时消费一次,后续更新无效),相比 Form 的 initValues 中的值,它的优先级更高 | any(类型取决于当前组件,详细见各组件的 api) |
| validate | 该表单控件的的自定义校验函数。支持同步、异步校验。
设置了 validate 时,rules 不会生效
使用示例:(fieldValue, values) => fieldValue >= 5 ? 'value not valid': '' | function(fieldValue, values) | |
| rules | 校验规则,校验库基于[async-validator](https://github.com/yiminghe/async-validator)
使用示例:const rules=\[{ required: true, message: 'can't be null ' },
{ max: 10, message: 'can't more than 10 word' }\] | array | |
| validateStatus | 该表单控件的校验结果状态(仅影响样式),可选值:`success`/`error`/`warning`/`default` | string | 'default' |
| trigger | 触发校验的时机,可选值:`blur`/`change`/`custom`/`mount`,或以上值的组合\['blur','change'\]
1、设置为 custom 时,仅会由 formApi/fieldApi 触发校验时被触发
2、mount(挂载时即触发一次校验) | string/array | 'change' |
| onChange | 值变化时触发的回调 | function(filedValue: any \| ev: { target: { value: any }}) (具体参见各组件的 onChange 方法) |
| onBlur | 失去焦点时触发的回调 | function() (具体参见各组件的 onBlur 方法) |
| transform | 校验前转换字段值,转换后的值仅会在校验时被消费,对 formState 无影响
使用示例: (value) => Number | function(fieldValue) | |
| convert | field 值改变后,在 rerender 前,对 filed 的值进行二次更新
使用示例: (value) => newValue | function(fieldValue) | |
| allowEmptyString | 是否允许值为空字符串。默认情况下值为''时,该 field 对应的 key 会从 values 中移除,如果你希望保留该 key,那么需要将 allowEmptyString 设为 true | boolean | false |
| stopValidateWithError | 为 true 时,使用 rules 校验,碰到第一个检验不通过的 rules 后,将不再触发后续 rules 的校验
**v0.35.0 开始提供** | boolean | false |
| helpText | 自定义提示信息,与校验信息公用同一区块展示,两者均有值时,优先展示校验信息
**v1.0.0 开始提供** | ReactNode | |
| extraText | 额外的提示信息,当需要错误信息和提示文案同时出现时,可以使用这个,位于 helpText/errorMessage 后
**v1.0.0 开始提供** | ReactNode | |
| pure | 是否仅接管数据流,为 true 时不会自动插入 ErrorMessage、Label、extraText 等模块,样式、DOM 结构与原始的组件保持一致
**v1.1.0 开始提供** | boolean | false |
| extraTextPosition | 控制extraText的显示位置,可选`middle`(垂直方向以Label、extraText、Field主体的顺序显示)、`bottom` (垂直方向以Label、Field主体、extraText的顺序显示);在Form与Field上同时传入时,以Field props为准
**v1.9.0 开始提供** | string | 'bottom' |
| ...other | 组件的其他可配置属性,与上面的属性平级一并传入即可,例如 Input 的 size/placeholder,**Field 会将其透传至组件本身** | |
## Form.Section
```jsx
import { Form } from '@douyinfe/semi-ui';
const { Section } = Form;
```
| 属性 | 说明 | 类型 | 版本|
| --------- | -------- | --------- |---- |
| text | 段落标题 | ReactNode | v1.0.0|
| className | 样式类名 | string | v1.0.0|
| style | 内联样式 | object | v1.0.0|
| children | 段落内容 | ReactNode | v1.0.0|
## Form.Label
默认情况下,Label 会由 Form 自行插入到每个 Field 中。如果你需要在其他地方自行插入 Label,我们提供了 Label 组件可以导出
```jsx
import { Form } from '@douyinfe/semi-ui';
const { Label } = Form;
```
| 属性 | 说明 | 类型 | 默认值 | 版本|
| --------- | ------------------------ | --------- | ------ |--- |
| text | Label 内容 | ReactNode | | |
| required | 是否展示必填的\*号 | boolean | false | |
| extra | 跟随在 required 后的内容 | ReactNode | | v0.33.0 |
| align | text-align | string | 'left' | |
| className | 样式类名 | string | | |
| style | 内联样式 | string | | |
| width | label 宽度 | number | | |
## Form.Slot
> Form.Slot 在 v0.27.0 开始提供
```jsx
import { Form } from '@douyinfe/semi-ui';
const { Slot } = Form;
```
| 属性 | 说明 | 类型 |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| label | slot 的[Label 配置](#Form.Label), 例如{ text: 'semi', align: 'left' };也可以直接传入 string,Slot 内部会自动封装成合法 Label 格式 | object\|string |
| labelPosition | slot 的 label 位置,默认情况下继承自 form props,也可单独覆盖。可选'top'、'left' | string | |
| className | slot 样式类名 | string |
| style | slot 内联样式 | object |
| children | slot 的主体内容 | ReactNode |
## Form.ErrorMessage
> Form.ErrorMessage 在 v0.27.0 开始提供
```jsx
import { Form } from '@douyinfe/semi-ui';
const { ErrorMessage } = Form;
```
- 当 error 为 ReactNode、String、boolean 时,直接渲染
- 当 error 为数组时,会自动执行 join 操作聚合数组内的错误信息
| 属性 | 说明 | 类型 |
| ---------------- | --------------------------------------------------------- | ------------------------ |
| error | 错误信息内容 | string\|array\|ReactNode\|undefined\|boolean |
| className | 样式类名 | string |
| style | 内联样式 | object |
| showValidateIcon | 自动加上 validateStatus 对应的 icon | boolean |
| validateStatus | 信息所属的校验状态,可选 default/error/warning/success(success一般建议与default样式相同) | boolean |
## withFieldOption
| key | 描述 | 默认值 |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| valueKey | 组件表示值的属性,如 Switch、Radio 的是'checked',Input 的是'value' | 'value' |
| onKeyChangeFnName | 组件值变化时的回调函数,一般为'onChange' | 'onChange' |
| valuePath | 值属性在回调函数中第一个参数的路径,如 Radio 的 onChange(e.target.checked),那么该值需要设为 target.checkd;RadioGroup 的 onChange(e.target.value),该值为'target.value';若第一个参数就是值本身,无需再往下取值,该项不需要设 | |
| maintainCursor | 是否需要保持光标,用于 Input 类组件 | false |
## Accessibility
### ARIA
- [aria-labelledby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby)、for
- Field 组件,会自动添加 label DOM。label 的 `for` 属性与 `props.id` 或 `props.name` 或 `props.field` 相同 ;label 的id 属性由 `props.id` 或 `props.name` 或 `props.field` 决定,值格式为 `${props.field}-label`;
- 当 Form 或者 Field 的 props.labelPosition 设置为 inset时,此时不存在 label 标签,而是 div 标签。insetLabel 对应的 div 标签会被自动追加 id,值与上述 label 的 id 相同,对应 Field 组件的 `aria-labelledby`
- Field 组件会被自动追加 `aria-labelledby`,值与上述 label 的id 相同
- [aria-required](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-required_attribute)
- 当 Field 配置了必填时(即 props.rules中包含 require: true 或 props.label配置了required: true),Field 组件会被自动追加 aria-required = true(Form.Switch、Form.CheckboxGroup 除外)
- [aria-invalid](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-invalid_attribute) 、[aria-errormessage](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-errormessage)
- 当 Field 校验未通过时,Field 组件会被自动添加 `aria-invalid` = true 属性,Form.CheckboxGroup 除外。
- 当 Field 校验未通过时,Field 组件会被自动追加 `aria-errormessage` 属性,值为 errorMessage 所对应DOM元素的 id (格式: `${props.field}-errormessage`),Form.CheckboxGroup 除外。
- [aria-describedby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-describedby_attribute)
- 当 Field 配置了 `helpText` 或 `extraText` 时,Field 组件会被自动添加 `aria-describedby` 属性,值为 helpText、extraText 所对应DOM元素的 id (格式:`${props.field}-helpText` 、`${props.field}-extraText`)
## 设计变量
## FAQ
- **为什么我声明了表单,对值进行了修改,数据没有自动映射到 formState.values 中?**
请检查是否正确传入了 field,Field 上的`field`属性是必填项!!!
- **为什么传入了 defaultValue、defaultChecked 不生效?**
请参考文档开头[表单控件](#声明表单的多种写法),Form.Field 组件对默认值做了统一处理,你应该使用`initValue`或者`initValues`来传入默认值
- **为什么异步更新了 initValue、initValues 后,组件没有发生变化,值没有生效?**
`initValue`、`initValues`只在 Field、Form mounted 时进行消费,后续做的异步更新并不会起效。
如果你的初始值需要从远程取,那么你可以在获取到值之后,使用`formApi.setValue/setValues`进行更新。
或者直接给 Form、Field 传入一个新的`key`强制它重新挂载
- **为什么调用了 formApi.setValues 更新 fields 的值,但是实际渲染并没有更新?**
setValues 默认情况下对尚未存在的 field 进行赋值不会生效。如果你的 fields 是动态加载的话,请检查在 setValues 时,该 field 是否已 mounted。
如有需要,可以使用 override 模式 `formApi.setValues(newValue, { isOverride: true })`
- **为什么 rules 中的 validator 校验失败,但是对应的错误信息没有被展示?**
async-validator 的自定义 validator 返回值必须是 boolean 类型,否则它不执行任何回调,semi 后续的钩子也不会被调用。建议通过加 !! 或者 Boolean() 强制转换返回类型
- **为什么 getValues 拿不到某个 field?**
field 没有初始值的话,`getValues` 获取不到这一项。可以设置 `initValues`/`initValue` 或者给 form 设置 `allowEmpty` 属性。
- **[🔍 🧾 更多Form FAQ补充 & 问题自查手册](https://bytedance.feishu.cn/docs/doccnNKaGhZMqyu0FufD1JGHOjf)**