代码演示 声明表单的多种写法 Semi Form 同时支持多种写法
基本写法 从 Form 中导出表单控件,给表单控件添加field
属性,将其放置于 Form 内部即可
还可以给每个表单控件设置label
属性,不传入时默认与 field 相同
label
可以直接传入字符串,亦可以以 object 方式声明,配置 extra
、required
、optional
等属性应对更复杂的场景
注意事项
对于Field级别组件来说,field 属性是必填项!
import React from 'react';
import { Form, Tooltip } from '@douyinfe/semi-ui';
import { IconHelpCircle } from '@douyinfe/semi-icons';
() => {
const { Option } = Form.Select;
return (
<Form layout='horizontal' onValueChange={values=>console.log(values)}>
<Form.Input field='UserName' label='用户名' style={{ width: 80 }}/>
<Form.Input
field='Password'
label={{
text: '密码',
extra: <Tooltip content='详情'><IconHelpCircle style={{ color: 'var(--semi-color-text-2)' }}/></Tooltip>
}}
style={{ width: 176 }}
/>
<Form.Select field="Role" label={{ text: '角色', optional: true }} style={{ width: 176 }}>
<Option value="admin">管理员</Option>
<Option value="user">普通用户</Option>
<Option value="guest">访客</Option>
</Form.Select>
</Form>
);
};
支持的其他写法 当你需要在 Form 结构内部直接获取到 formState
、formApi
、values
等值时,你还可以使用以下的写法
注意事项
注意,此处获取的 formState、values 等并没有经过 deepClone。你应该只做读操作,而不应该做写操作,否则会导致你可能意外修改了form内部的状态。所有对 Form 内部状态的更新都应该通过 formApi 去操作
通过 render 属性传入 即 render props
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => {
return (
<Form render={({ formState, formApi, values }) => (
<>
<Form.Select field="Role" label='角色' style={{ width: 176 }}>
<Form.Select.Option value="admin">管理员</Form.Select.Option>
<Form.Select.Option value="user">普通用户</Form.Select.Option>
<Form.Select.Option value="guest">访客</Form.Select.Option>
</Form.Select>
<Form.Input field='UserName' label='用户名' style={{ width: 80 }}/>
<Form.Input field='Password' label='密码' style={{ width: 176 }}/>
<code style={{ marginTop: 24 }}>{JSON.stringify(formState)}</code>
</>
)} layout='horizontal' onValueChange={values=>console.log(values)}>
</Form>
);
};
通过 child render function Form 的 children 是一个 function,return 出所有表单控件
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => {
return (
<Form layout='horizontal' onValueChange={values=>console.log(values)}>
{
({ formState, values, formApi }) => (
<>
<Form.Select field="Role" label='角色' style={{ width: 176 }}>
<Form.Select.Option value="admin">管理员</Form.Select.Option>
<Form.Select.Option value="user">普通用户</Form.Select.Option>
<Form.Select.Option value="guest">访客</Form.Select.Option>
</Form.Select>
<Form.Input field='UserName' label='用户名' style={{ width: 80 }} />
<Form.Input field='Password' label='密码' style={{ width: 176 }}/>
<code style={{ marginTop: 24 }}>{JSON.stringify(formState)}</code>
</>
)
}
</Form>
);
};
通过 props.component 通过 component 属性直接将整个内部结构以 ReactNode 形式传入
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => {
const fields = ({ formState, formApi, values }) => (
<>
<Form.Input field='Role' style={{ width: 176 }}/>
<Form.Input field='UserName' style={{ width: 80 }}/>
<Form.Input field='Password' style={{ width: 176 }}/>
<code style={{ marginTop: 24 }}>{JSON.stringify(formState)}</code>
</>
);
return <Form component={fields} layout='horizontal' onValueChange={values=>console.log(values)}/>;
};
已支持的表单控件 import React from 'react';
import { Form, Col, Row, Button, Space } from '@douyinfe/semi-ui';
import { IconUpload } from '@douyinfe/semi-icons';
() => {
const initValues = {
name: 'semi',
business: ['ulikeCam'],
role: 'ued',
switch: true,
files: [
{
uid: '1',
name: 'vigo.png',
status: 'success',
size: '130KB',
preview: true,
url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/vigo.png'
},
{
uid: '2',
name: 'resso.jpeg',
status: 'validateFail',
size: '222KB',
percent: 50,
preview: true,
fileInstance: new File([new ArrayBuffer(2048)], 'resso.jpeg', { type: 'image/jpeg' }),
url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/Resso.png'
},
{
uid: '3',
name: 'douyin.jpeg',
status: 'uploading',
size: '222KB',
percent: 50,
preview: true,
fileInstance: new File([new ArrayBuffer(2048)], 'dy.jpeg', { type: 'image/jpeg' }),
url: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/dy.png'
}
]
};
const { Section, Input, InputNumber, AutoComplete, Select, TreeSelect, Cascader, DatePicker, TimePicker, TextArea, CheckboxGroup, Checkbox, RadioGroup, Radio, Slider, Rating, Switch, TagInput } = Form;
const plainOptions = ['A', 'B', 'C'];
const style = { width: '90%' };
const treeData = [
{
label: '亚洲',
value: 'Asia',
key: '0',
children: [
{
label: '中国',
value: 'China',
key: '0-0',
children: [
{
label: '北京',
value: 'Beijing',
key: '0-0-0',
},
{
label: '上海',
value: 'Shanghai',
key: '0-0-1',
},
],
},
],
},
{
label: '北美洲',
value: 'North America',
key: '1',
}
];
return (
<Form
initValues={initValues}
style={{ padding: 10, width: '100%' }}
onValueChange={(v)=>console.log(v)}
>
<Section text={'基本信息'}>
<Row>
<Col span={12}>
<Input
field="name"
label="名称(Input)"
initValue={'mikeya'}
style={style}
trigger='blur'
/>
</Col>
<Col span={12}>
<DatePicker field="date" label='日期(DatePicker)' style={style} initValue={new Date()} placeholder='请选择生效日期' />
</Col>
</Row>
<Row>
<Col span={12}>
<Select field="role" style={style} label='角色(Select)' placeholder='请选择你的角色'>
<Select.Option value="operate">运营</Select.Option>
<Select.Option value="rd">开发</Select.Option>
<Select.Option value="pm">产品</Select.Option>
<Select.Option value="ued">设计</Select.Option>
</Select>
</Col>
<Col span={12}>
<Select
field="business"
multiple
style={style}
placeholder='请选择业务线'
label="业务线(多选Select)"
extraText={
<div style={{
color: 'rgba(var(--semi-blue-5), 1)',
fontSize: 14,
userSelect: 'none',
cursor: 'pointer'
}}>
没有找到合适的业务线?
</div>
}
>
<Select.Option value="abc">Semi</Select.Option>
<Select.Option value="ulikeCam">轻颜相机</Select.Option>
<Select.Option value="toutiao">今日头条</Select.Option>
</Select>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Cascader
placeholder="请选择所在地区"
treeData={treeData}
field='area'
label='地区(Cascader)'
style={style}
>
</Form.Cascader>
</Col>
<Col span={12}>
<Form.TreeSelect
field="tree"
style={style}
label='节点(TreeSelect)'
placeholder='请选择服务节点'
treeData={treeData}
filterTreeNode
>
</Form.TreeSelect>
</Col>
</Row>
<Row>
<Col span={12}>
<TagInput
field="product"
label='产品(TagInput)'
initValue={['abc', 'ulikeCam']}
placeholder='请输入产品'
style={style}
/>
</Col>
</Row>
<Row>
<Col span={24}>
<Form.Upload
field='files'
label='证明文件(Upload)'
action='//semi.design/api/upload'
>
<Button icon={<IconUpload />} theme="light">
点击上传
</Button>
</Form.Upload>
</Col>
</Row>
</Section>
<Section text='资源详情'>
<Row>
<Col span={12}>
<TextArea
style={{ ...style, height: 120 }}
field='description'
label='申请理由(TextArea)'
placeholder='请填写申请资源理由'
/>
</Col>
<Col span={12}>
<CheckboxGroup
field="type"
direction='horizontal'
label='申请类型(CheckboxGroup)'
initValue={['user', 'admin']}
rules={[
{ required: true }
]}
>
<Checkbox value="admin">admin</Checkbox>
<Checkbox value="user">user</Checkbox>
<Checkbox value="guest">guest</Checkbox>
<Checkbox value="root">root</Checkbox>
</CheckboxGroup>
<RadioGroup field="isMonopolize" label='是否独占资源(Radio)' rules={[
{ type: 'boolean' },
{ required: true, message: '必须选择是否独占 ' }
]}>
<Radio value={1}>是</Radio>
<Radio value={0}>否</Radio>
</RadioGroup>
</Col>
</Row>
<Row>
<Col span={12}>
<TimePicker field="time" label='截止时刻(TimePicker)' style={{ width: '90%' }}/>
</Col>
<Col span={12}>
<InputNumber field='number' label='申请数量(InputNumber)' initValue={20} style={style}/>
</Col>
</Row>
<Row>
<Col span={12}>
<Slider field="range" label='资源使用报警阈值(%)(Slider)' initValue={10} style={{ width: '90%' }}/>
</Col>
<Col span={12}>
<Switch field='switch' label='开关(Switch)'/>
</Col>
</Row>
<Row>
<Col span={12}>
<Rating field="rating" label='满意度(Rating)' initValue={2} style={{ width: '90%' }}/>
</Col>
</Row>
</Section>
<Checkbox value="false" field="agree" noLabel={true}>
我已阅读并清楚相关规定(Checkbox)
</Checkbox>
<Space>
<Button type="primary" htmlType="submit" className="btn-margin-right">提交(submit)</Button>
<Button htmlType="reset">重置(reset)</Button>
</Space>
</Form>
);
};
表单控件值的绑定 每个表单控件都需要以field
属性绑定一个字段名称,用于将表单项的值正确映射到FormState
values / errors / touched 中
字段可以是简单的字符串,可以是包含.
或者[]
的字符串, 支持多级嵌套
下面是字段名称以及他们在 FormState 中的映射路径的示例
Field Resolution username formState.values.username user[0] formState.values.user[0] siblings.1 formState.values.siblings[1] siblings['2'] formState.values.siblings[2] parents[0].name formState.values.parents[0].name parents[1]['name'] formState.values.parents[1].name
import React from 'react';
import { Form, Toast, Row, Col, TextArea } from '@douyinfe/semi-ui';
() => (
<Form
onSubmit={values => Toast.info({ content: JSON.stringify(values) })}
>
{
({ formState, values, formApi }) => (
<Row>
<Col span={12}>
<Form.Input field='username' placeholder='请尝试输入值'/>
<Form.Input field='user[0]' placeholder='请尝试输入值'/>
<Form.Input field='siblings.1' placeholder='请尝试输入值'/>
<Form.Input field="siblings['2']" placeholder='请尝试输入值'/>
<Form.Input field='parents[0].name' placeholder='请尝试输入值'/>
<Form.Input field="parents[1]['name']" placeholder='请尝试输入值'/>
</Col>
<Col span={10} offset={1} style={{ marginTop: 12 }}>
<Form.Label text='FormState实时映射值:'></Form.Label>
<TextArea value={JSON.stringify(formState.values)}></TextArea>
</Col>
</Row>
)
}
</Form>
);
表单布局 垂直布局:表单控件之间上下垂直排列(默认) Semi Design 更推荐表单采用垂直布局 import React from 'react';
import { Form, Toast, Button } from '@douyinfe/semi-ui';
() => {
const handleSubmit = (values) => {
console.log(values);
Toast.info('表单已提交');
};
return (
<Form onSubmit={values => handleSubmit(values)} style={{ width: 400 }}>
{({ formState, values, formApi }) => (
<>
<Form.Input field='phone' label='PhoneNumber' style={{ width: '100%' }} placeholder='Enter your phone number'></Form.Input>
<Form.Input field='password' label='Password' style={{ width: '100%' }} placeholder='Enter your password'></Form.Input>
<Form.Checkbox field='agree' noLabel>I have read and agree to the terms of service</Form.Checkbox>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<p>
<span>Or</span><Button theme='borderless' style={{ color: 'var(--semi-color-primary)', marginLeft: 10, cursor: 'pointer' }}>Sign up</Button>
</p>
<Button disabled={!values.agree} htmlType='submit' type="tertiary">Log in</Button>
</div>
</>
)}
</Form>
);
};
水平布局:表单控件之间水平排列
你可以通过设置 layout='horizontal'
来使用水平布局 import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form layout='horizontal'>
<Form.Input field='phone' label='PhoneNumber' placeholder='Enter your phone number'></Form.Input>
<Form.Input field='password' label='Password' placeholder='Enter your password'></Form.Input>
</Form>
);
labelPosition、labelAlign 你可以通过设置 labelPosition、labelAlign 控制 label 在 Field 中出现的位置,文本对齐的方向 import React from 'react';
import { Form, Select } from '@douyinfe/semi-ui';
class BasicDemo extends React.Component {
constructor() {
super();
this.state = {
labelPosition: 'left',
labelAlign: 'left',
labelWidth: '180px'
};
this.changeLabelPos = this.changeLabelPos.bind(this);
this.changeLabelAlign = this.changeLabelAlign.bind(this);
}
changeLabelPos(labelPosition) {
let labelWidth;
labelPosition === 'left' ? labelWidth = '180px' : labelWidth = 'auto';
this.setState({ labelPosition, labelWidth });
}
changeLabelAlign(labelAlign) { this.setState({ labelAlign }); }
render() {
const { labelPosition, labelAlign, labelWidth } = this.state;
return (
<>
<div style={{ borderBottom: '1px solid var(--semi-color-border)', paddingBottom: 12 }}>
<Form.Label style={{ marginLeft: 10 }}>切换Label位置:</Form.Label>
<Select onChange={this.changeLabelPos} value={labelPosition} style={{ width: 200 }} insetLabel='labelPosition'>
<Select.Option value='top'>top</Select.Option>
<Select.Option value='left'>left</Select.Option>
</Select>
<Form.Label style={{ marginLeft: 10 }}>切换Label文本对齐方向:</Form.Label>
<Select onChange={this.changeLabelAlign} value={labelAlign} style={{ width: 200 }} insetLabel='labelAlign'>
<Select.Option value='left'>left</Select.Option>
<Select.Option value='right'>right</Select.Option>
</Select>
</div>
<Form
labelPosition={labelPosition}
labelWidth={labelWidth}
labelAlign={labelAlign}
key={labelPosition + labelAlign}
style={{ padding: '10px', width: 600 }}>
<Form.Input
field="input"
label="手机号码"
trigger='blur'
style={{ width: 200 }}
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' }
]}
/>
<Form.Switch label="是否同意" field='agree'/>
<Form.InputNumber field='price' label='价格' style={{ width: 200 }}/>
<Form.Select label="姓名" field='name' style={{ width: 200 }}>
<Form.Select.Option value="mike">mike</Form.Select.Option>
<Form.Select.Option value="jane">jane</Form.Select.Option>
<Form.Select.Option value="kate">kate</Form.Select.Option>
</Form.Select>
<Form.CheckboxGroup label="角色" field='role' direction='horizontal'>
<Form.Checkbox value="admin">admin</Form.Checkbox>
<Form.Checkbox value="user">user</Form.Checkbox>
<Form.Checkbox value="guest">guest</Form.Checkbox>
<Form.Checkbox value="root">root</Form.Checkbox>
</Form.CheckboxGroup>
<Form.RadioGroup field="性别">
<Form.Radio value="1">man</Form.Radio>
<Form.Radio value="2">woman</Form.Radio>
</Form.RadioGroup>
</Form>
</>
);
}
}
更复杂的布局
你还可以结合 Grid 提供的 Row、Col,来对表单进行你想要的排列 import React from 'react';
import { Form, Col, Row } from '@douyinfe/semi-ui';
() => (
<Form
labelPosition='top'
getFormApi={this.getFormApi}
style={{ padding: '10px' }}>
<Row>
<Col span={8}>
<Form.Input
field="nickName1"
label="用户名"
style={{ width: '250px' }}
trigger='blur'
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' }
]}
/>
</Col>
<Col span={8}>
<Form.DatePicker field='date1' label='有效日期' style={{ width: '250px' }}/>
</Col>
<Col span={8}>
<Form.Select label="业务线" field='business1' style={{ width: '250px' }}>
<Form.Select.Option value="abc">Semi</Form.Select.Option>
<Form.Select.Option value="ulikeCam">轻颜相机</Form.Select.Option>
<Form.Select.Option value="toutiao">今日头条</Form.Select.Option>
</Form.Select>
</Col>
</Row>
<Row>
<Col span={6}>
<Form.Input
field="nickName2"
label="用户名"
style={{ width: '200px' }}
trigger='blur'
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' }
]}
/>
</Col>
<Col span={6}>
<Form.DatePicker field='date2' label='有效日期' style={{ width: '200px' }}/>
</Col>
<Col span={6}>
<Form.Select label="业务线" field='business2' style={{ width: '200px' }}>
<Form.Select.Option value="abc">Semi</Form.Select.Option>
<Form.Select.Option value="ulikeCam">轻颜相机</Form.Select.Option>
<Form.Select.Option value="toutiao">今日头条</Form.Select.Option>
</Form.Select>
</Col>
<Col span={6}>
<Form.Select field="role" label='角色' style={{ width: '200px' }}>
<Form.Select.Option value="operate">运营</Form.Select.Option>
<Form.Select.Option value="rd">开发</Form.Select.Option>
<Form.Select.Option value="pm">产品</Form.Select.Option>
<Form.Select.Option value="ued">设计</Form.Select.Option>
</Form.Select>
</Col>
</Row>
</Form>
);
表单分组 字段数量较多的表单应考虑对字段进行分组,可以使用Form.Section
对 Fields 进行分组(仅影响布局,不会影响数据结构)
import React from 'react';
import { Form, Button, Space } from '@douyinfe/semi-ui';
() => {
const { Section, Input, DatePicker, TimePicker, Select, Switch, InputNumber, Checkbox, CheckboxGroup, RadioGroup, Radio } = Form;
return (
<Form style={{ width: 560 }}>
<Section text={'基本信息'}>
<Input field='name' label='考试名称' initValue='TCS任务平台使用' style={{ width: 560 }}/>
</Section>
<Section text={'合格标准'} >
<div style={{ display: 'flex' }}>
<InputNumber field='pass' initValue={60} style={{ width: 80 }} label={{ text: '及格正确率', required: true }}/>
<InputNumber field='number' initValue={10} style={{ width: 80 }} label={{ text: '合格人数', required: true }}/>
</div>
</Section>
<Section text={'考试时间'} >
<DatePicker field='date' type='dateTime' initValue={new Date()} style={{ width: 272 }} label={{ text: '开始时间', required: true }}/>
<div style={{ display: 'flex' }}>
<Input field='time' label='考试时长' style={{ width: 176 }} initValue={'60'} addonAfter='分钟'/>
<Checkbox initValue={true} noLabel field='auto' style={{ paddingTop: 30, marginLeft: 12 }}>到时间自动交卷</Checkbox>
</div>
<RadioGroup
field="type"
label='有效时间'
direction='vertical'
initValue={'always'}
>
<Radio value="always">永久有效</Radio>
<Radio value="user">自定义有效期</Radio>
</RadioGroup>
<RadioGroup
field="answerTime"
label='答案放出时间'
direction='vertical'
initValue={'always'}
rules={[
{ required: true }
]}
>
<Radio value="always">自动放出</Radio>
<Radio value="user">
<div style={{ display: 'inline-block' }}>
自定义放出时间
<Form.DatePicker type='dateTimeRange' noLabel field='customTime' style={{ width: 464, display: 'inline-block' }}/>
</div>
</Radio>
</RadioGroup>
</Section>
<Section text={'考试人员'}>
<div style={{ display: 'flex' }}>
<Switch field='open' label={{ text: '对外开放', required: true }} checkedText='开' uncheckedText='关'></Switch>
</div>
<Select
field='users'
label={{ text: '考生', required: true }}
style={{ width: 560 }}
multiple
initValue={['1', '2', '3', '4']}
>
<Select.Option value='1'>曲晨一</Select.Option>
<Select.Option value='2'>夏可曼</Select.Option>
<Select.Option value='3'>曲晨三</Select.Option>
<Select.Option value='4'>蔡妍</Select.Option>
</Select>
</Section>
<Space>
<Button type='primary' theme='solid' style={{ width: 120, marginTop: 12, marginRight: 4 }}>创建考试</Button>
<Button style={{ marginTop: 12 }}>预览</Button>
</Space>
</Form>
);
};
wrapperCol / labelCol 需要为 Form 内的所有 Field 设置统一的布局时,可以在 Form 上设置 wrapperCol 、labelCol 快速生成布局,无需手动使用 Row、Col 手动布局
wrapperCol
、
labelCol
属性配置参考
Col 组件 import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form
wrapperCol={{ span: 20 }}
labelCol={{ span: 2 }}
labelPosition='left'
labelAlign='right'
>
<Form.Input field='name' style={{ width: 250 }} label='姓名' trigger='blur' placeholder='请输入姓名'/>
<Form.Select field="role" label='角色' placeholder='请选择角色' style={{ width: 250 }}>
<Form.Select.Option value="operate">运营</Form.Select.Option>
<Form.Select.Option value="rd">开发</Form.Select.Option>
<Form.Select.Option value="pm">产品</Form.Select.Option>
<Form.Select.Option value="ued">设计</Form.Select.Option>
</Form.Select>
</Form>
);
隐藏Label Form 会自动为 Field 控件插入 Label。如果你不需要自动插入 Label 模块, 可以通过在 Field 中设置noLabel=true
将自动插入 Label 功能关闭(此时 Field 仍然具备自动展示 ErrorMessage 的能力,因此 DOM 结构与原始控件依然会有区别)
如果你希望与原始控件保持 DOM 结构一致,可以使用 pure=true,此时除了数据流被接管外,DOM结构不会有任何变化(你需要自行负责 ErrorMessage的渲染,同时它也无法被 formProps.wrapperCol 属性影响)
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form onSubmit={(values) => console.log(values)} style={{ width: 400 }}>
<Form.Input field='name' label='姓名' trigger='blur' noLabel={true} style={{ width: 250 }} placeholder='请输入姓名'/>
<Form.Input field='purename' pure placeholder='DOM结构与普通 Input 组件完全一致'/>
</Form>
);
内嵌 Label 通过将 labelPosition 设为inset
,可以将 Label 内嵌在表单控件中。目前支持这项功能的组件有Input
、InputNumber
、DatePicker
、TimePicker
、Select
、TreeSelect
、Cascader
、TagInput
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => (
<Form labelPosition='inset' layout='horizontal'>
<Form.Input field='name' label='姓名' trigger='blur' style={{ width: 250 }} placeholder='请输入姓名' initValue='semi'/>
<Form.Select field="role" label='角色' style={{ width: '250px' }} initValue='rd'>
<Form.Select.Option value="operate">运营</Form.Select.Option>
<Form.Select.Option value="rd">开发</Form.Select.Option>
<Form.Select.Option value="pm">产品</Form.Select.Option>
<Form.Select.Option value="ued">设计</Form.Select.Option>
</Form.Select>
<Form.DatePicker field="date" label='开始日期' style={{ width: '250px' }} initValue={new Date()}>
</Form.DatePicker>
</Form>
);
导出 Label、ErrorMessage 使用 如果你需要 Form.Label、Form.ErrorMessage 模块自行组合使用,可以从 Form 中导出
例如:当自带的 Label、ErrorMessage 布局不满足业务需求,需要自行组合位置,但又希望能直接使用 Label、ErrorMessage 的默认样式时
import { Form } from '@douyinfe/semi-ui';
const { Label, ErrorMessage } = Form;
当你的自定义组件,需要与 Field 组件保持同样的布局样式时,你可以通过 Form.Slot 放置你的自定义组件
在 Form 组件上设置的
labelWidth
、
labelAlign
、
wrapperCol
、
labelCol
会自动作用在 Form.Slot 上
Slot 属性配置详见
Form.Slot import React from 'react';
import { Form } from '@douyinfe/semi-ui';
class AssistComponent extends React.Component {
render() {
return (
<Form
onChange={v=>console.log(v)}
onSubmit={v=>console.log(v)}
style={{ width: 600 }}
labelPosition='left'
labelWidth={100}
>
<Form.Input field='特效名称' style={{ width: 250 }}/>
<Form.Slot label={{ text: 'SlotA' }} error='我是SlotA的ErrorMessage'>
<div style={{ display: 'flex', alignItems: 'center', height: 32, marginTop: 8 }}>
我是Semi Form SlotA, 我是自定义的ReactNode
</div>
</Form.Slot>
<Form.Slot label={{ text: 'SlotB', width: 160, align: 'right' }}>
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
我是Semi Form SlotB, 我的Label Align、Width与众不同
</div>
</Form.Slot>
</Form>
);
}
}
可以通过helpText
放置自定义提示信息,与校验信息(error)公用同一区块展示,两者均有值时,优先展示校验信息。
可以通过extraText
放置额外的提示信息,当需要错误信息和提示文案同时出现时,可以使用这个配置,常显,位于 helpText/error 后
当传入 validateStatus 时,优先展示 validateStatus 值对应的 UI 样式。不传入时,以 field 内部校验状态为准。
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => {
const [helpText, setHelpText] = useState('');
const [validateStatus, setValidateStatus] = useState('default');
const formRef = useRef();
const validate = (val, values) => {
if (!val) {
setValidateStatus('error');
return <span>密码不能为空</span>;
} else if (val && val.length <= 3) {
setValidateStatus('warning');
setHelpText(<span style={{ color: 'var(--semi-color-warning)' }}>密码强度:弱</span>); // show helpText
return ''; // validate pass
} else {
setHelpText('');
setValidateStatus('success');
return '';
}
};
const random = () => {
let pw = (Math.random() * 100000).toString().slice(0, 5);
formRef.current.formApi.setValue('Password', pw);
formRef.current.formApi.setError('Password', '');
setHelpText('');
setValidateStatus('success');
};
return (
<Form
showValidateIcon={true}
ref={formRef}
onSubmit={(value) => console.log('submit success')}
onSubmitFail={(errors) => console.log(errors)}
>
<Form.Input
validate={validate}
field="Password"
validateStatus={validateStatus}
helpText={helpText}
extraText={
<div
style={{
color: 'var(--semi-color-link)',
fontSize: 14,
userSelect: 'none',
cursor: 'pointer'
}}
onClick={random}
>
没有想到合适的密码?点击随机生成一个
</div>
}
></Form.Input>
</Form>
);
};
通过配置 extraTextPosition
,你可以控制 extraText 的显示位置。可选值 bottom
、middle
例如如当你希望将 extraText 提示信息显示在 Label 与 Field 控件中间时
该属性可在 Form 上统一配置,亦可在每个 Field 上单独配置,同时传入时,以 Field 的配置为准。
import React from 'react';
import { Form } from '@douyinfe/semi-ui';
() => {
const options = [
{ label: '飞书通知', value: 'lark' },
{ label: '邮件通知', value: 'email' },
{ label: '顶部横幅通知', value: 'notification' }
];
const notifyText = '未勾选时,默认为红点提醒,消息默认进入收件人消息列表。对于重要通知,可同时勾选相应的通知方式。';
const forceText = '对于对话框通知,可指定该消息必须在指定时长后才可置为已读。';
return (
<Form extraTextPosition='middle'>
<Form.CheckboxGroup
direction='horizontal'
field='notify'
label='通知方式'
extraText={notifyText}
options={options}
/>
<Form.InputNumber field='force' label='强制读取(可选)' placeholder='秒' extraText={forceText} extraTextPosition='bottom'/>
</Form>
);
};
当你需要将一些表单控件组合起来使用时,你可以用
Form.InputGroup
将其包裹起来
当你给
Select
、
Input
等表单控件加上 field 属性时,
Form
会默认给每个 Field 控件自动插入
Label
而在
InputGroup
中一般仅需要一个属于整个 Group 的 Label,你可以在 InputGroup 中设置 label 属性,插入一个属于 Group 的
Label
label
可配置属性详见
Label import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
() => (
<Form onSubmit={(values) => console.log(values)} labelPosition='top' style={{ width: 400 }}>
<Form.InputGroup label={{ text: (<span>手机号码</span>), required: true }} labelPosition='top'>
<Form.Select style={{ width: 150 }} field='phonePrefix' initValue='+86' rules={[{ required: true }]} showClear>
<Form.Select.Option value='+1'>美国+1</Form.Select.Option>
<Form.Select.Option value='+852'>香港+852</Form.Select.Option>
<Form.Select.Option value='+86'>中国+86</Form.Select.Option>
<Form.Select.Option value='+81'>日本+81</Form.Select.Option>
</Form.Select>
<Form.Input initValue='18912345678' style={{ width: 250 }} field='phoneNumber' rules={[{ required: true }]} showClear />
</Form.InputGroup>
<Form.Input field='姓名' trigger='blur' initValue='Semi'></Form.Input>
<Button htmlType='submit'>提交</Button>
</Form>
);
Modal 弹出层中的表单 你可以将 Form 放置于 Modal 中,以弹窗形式承载
在提交时,通过 formApi.validate()对 Field 进行集中校验
import React from 'react';
import { Form, Modal, Button, Row, Col } from '@douyinfe/semi-ui';
class ModalFormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
};
this.showDialog = this.showDialog.bind(this);
this.handleOk = this.handleOk.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.getFormApi = this.getFormApi.bind(this);
}
showDialog() {
this.setState({ visible: true });
}
handleOk() {
this.formApi.validate()
.then((values) => {
console.log(values);
})
.catch((errors) => {
console.log(errors);
});
}
handleCancel() {
this.setState({ visible: false });
}
getFormApi(formApi) {
this.formApi = formApi;
}
render() {
const { visible } = this.state;
let message = '该项为必填项';
return (
<>
<Button onClick={this.showDialog}>打开弹窗</Button>
<Modal
title="新建"
visible={visible}
onOk={this.handleOk}
style={{ width: 600 }}
onCancel={this.handleCancel}
>
<Form
getFormApi={this.getFormApi}
>
<Row>
<Col span={5}>
<Form.Select
field='region'
label="国家/地区"
placeholder='请选择'
style={{ width: '100%' }}
rules={[
{ required: true, message },
]}
>
<Form.Select.Option value="China">中国</Form.Select.Option>
<Form.Select.Option value="US">美国</Form.Select.Option>
<Form.Select.Option value="Europe">欧洲</Form.Select.Option>
<Form.Select.Option value="Japan">日本</Form.Select.Option>
</Form.Select>
</Col>
<Col span={15} offset={2}>
<Form.Input
field='owner'
label="业务执行人"
trigger='blur'
rules={[
{ required: true, message },
]}
/>
</Col>
</Row>
<Row>
<Col span={5}>
<Form.Select
field='area'
label="投放区域"
placeholder='请选择'
style={{ width: '100%' }}
rules={[
{ required: true, message },
]}
>
<Form.Select.Option value="China">中国</Form.Select.Option>
<Form.Select.Option value="US">美国</Form.Select.Option>
<Form.Select.Option value="Europe">欧洲</Form.Select.Option>
<Form.Select.Option value="Japan">日本</Form.Select.Option>
</Form.Select>
</Col>
<Col span={15} offset={2}>
<Form.Input
field='department'
label="业务执行部门"
trigger='blur'
rules={[
{ required: true, message },
]}
/>
</Col>
</Row>
</Form>
</Modal>
</>
);
}
}
配置初始值与校验规则 你可以通过rules
为每个 Field 表单控件配置校验规则 Form 内部的校验库基于 async-validator,更多配置规则可查阅其官方文档 你可以通过 form 的initValues
为整个表单统一设置初始值,也可以在每个 field 中通过initValue
设置初始值(后者优先级更高) 可以通过 trigger 为每个 Field 配置不同的校验触发时机,默认为 change(即onChange触发时,自动进行校验)。还支持 change、blur、mount、custom 或以上的组合。v2.42 后支持通过 FormProps 统一配置, 若都配置时,以 FieldProps 为准 可以通过 stopValidateWithError 开关,决定使用 rules 校验时,当碰到第一个检验不通过的 rules 后,是否继续触发后续 rules 的校验。v2.42 后支持通过 FormProps 统一配置,若都配置时,以 FieldProps 为准 import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
() => {
const initValues = {
name: 'semi',
shortcut: 'se'
};
const style = { width: '100%' };
const { Select, Input } = Form;
return (
<Form initValues={initValues}>
<Input
field="name"
style={style}
trigger='blur'
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' },
{ validator: (rule, value) => value && value.startsWith('se'), message: 'should startsWith se' }
]}
/>
<Input
field="shortcut"
style={style}
stopValidateWithError
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' },
{ validator: (rule, value) => value && value.startsWith('se'), message: 'should startsWith se' }
]}
/>
<Button htmlType='submit'>提交</Button>
</Form>
);
};
你可以给Form
整体设置自定义校验函数 validateFields。submit 或调用formApi.validate()时会进行调用
注意
当配置了 Form级别校验器 validateFields 后,Field 级别的校验器(fieldProps.validate、fieldProps.rules 将不再生效)
同步校验 校验通过时,你应该返回一个空字符串;
校验失败时,你应该返回错误信息(Object,key 为 fieldName,value 为对应的错误信息)
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
class FormLevelValidateSync extends React.Component {
constructor() {
super();
this.syncValidate = this.syncValidate.bind(this);
}
syncValidate(values) {
const errors = {};
if (values.name !== 'mike') {
errors.name = 'you must name mike';
}
if (values.sex !== 'female') {
errors.sex = 'must be woman';
}
errors.familyName = [
{ before: 'before errror balabala ', after: 'after error balabala' },
'familyName[1] error balabala'
];
return errors;
}
render() {
return (
<Form validateFields={this.syncValidate} layout='horizontal'>
<Form.Input field='name' trigger='blur'></Form.Input>
<Form.Input field='familyName[0].before' trigger='blur'></Form.Input>
<Form.Input field='familyName[0].after' trigger='blur'></Form.Input>
<Form.Input field='familyName[1]' trigger='blur'></Form.Input>
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
<Button type="primary" htmlType="submit" className="btn-margin-right">
Submit
</Button>
<Button htmlType="reset">reset</Button>
</div>
</Form >
);
}
}
异步校验 异步校验时,你应当返回一个 promise,在 promise.then()中 你需要 return 对应的错误信息
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
class FormLevelValidateAsync extends React.Component {
constructor() {
super();
this.asyncValidate = this.asyncValidate.bind(this);
}
asyncValidate(values) {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
return sleep(2000).then(() => {
let errors = {};
if (values.name !== 'mike') {
errors.name = 'you must name mike';
}
if (values.sex !== 'female') {
errors.sex = 'sex not valid';
}
return errors;
});
}
render() {
return (
<Form validateFields={this.asyncValidate} layout='horizontal'>
<Form.Input field='name' trigger='blur'></Form.Input>
<Form.Input field='familyName[0].before' trigger='blur'></Form.Input>
<Form.Input field='familyName[1]' trigger='blur'></Form.Input>
<Form.Input field='sex' trigger='blur'></Form.Input>
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
<Button type="primary" htmlType="submit" className="btn-margin-right">
Submit
</Button>
<Button htmlType="reset">reset</Button>
</div>
</Form >
);
}
}
自定义校验(Field 级别) 你可以指定单个表单控件的自定义校验函数,支持同步、异步校验(通过返回 promise)
import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
class FieldLevelValidateDemo extends React.Component {
constructor() {
super();
this.validateName = this.validateName.bind(this);
this.asyncValidate = this.asyncValidate.bind(this);
}
validateName(val) {
if (!val) {
return '【sync】can\'t be empty';
} else if (val.length <= 5) {
return '【sync】must more than 5';
}
return '';
}
asyncValidate(val, values) {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
return sleep(2000).then(() => {
if (!val) {
return '【async】can\'t be empty';
} else if (val.length <= 5) {
return '【async】must more than 5';
} else {
return '';
}
});
}
render() {
return (
<Form>
<Form.Input field='name' label='【name】asyncValidate after 2s' validate={this.asyncValidate} trigger='blur'></Form.Input>
<Form.Input field='familyName' label='【familyName】syncValidate' validate={this.validateName} trigger='blur'></Form.Input>
<Button htmlType="reset">reset</Button>
</Form >
);
}
}
手动触发指定 Field 校验 当你希望手动触发某些特定 Field 的校验操作时,可以通过 formApi.validate 完成。不传入参数时,默认对全部 Field 进行校验,传入参数时,以参数指定为准
import React from 'react';
import { Form, Button, Space } from '@douyinfe/semi-ui';
class PartValidAndResetDemo extends React.Component {
constructor() {
super();
this.validate = this.validate.bind(this);
this.getFormApi = this.getFormApi.bind(this);
this.validatePartial = this.validatePartial.bind(this);
this.resetPartial = this.resetPartial.bind(this);
}
getFormApi(formApi) {
this.formApi = formApi;
}
validate(val) {
if (!val) {
return 'can\'t be empty';
} else if (val.length <= 5) {
return (<span>我是传入的reactNode</span>);
}
return;
}
validatePartial(type) {
let scope = this.formApi.getValue('validateScope');
!scope ? scope = [] : null;
type === 'all' ? scope = ['a', 'b', 'c', 'd', 'b.name'] : null;
this.formApi.validate(scope)
.then(values => {
console.log(values);
Toast.success('pass');
}).catch(error => {
Toast.error('error');
console.log(error);
});
}
resetPartial() {
let scope = this.formApi.getValue('resetScope');
this.formApi.reset(scope);
}
render() {
let options = ['a', 'b', 'c', 'd', 'b.name'].map(item => ({ label: item, value: item }));
return (
<Form getFormApi={this.getFormApi} autoScrollToError layout='horizontal'>
{
({ formState, values, formApi }) => (
<>
<div>
<Form.Input field="a[1]" validate={this.validate} trigger="blur" />
<Form.Input field="a[0]" validate={this.validate} trigger="blur" />
<Form.Input field="b.name[0]" validate={this.validate} trigger="blur" />
<Form.Input field="b.name[1]" validate={this.validate} trigger="blur" />
<Form.Input field="b.type" validate={this.validate} trigger="blur" />
<Form.Input field="c" validate={this.validate} trigger="blur" />
<Form.Input field="d" validate={this.validate} trigger="blur" />
</div>
<div>
<Form.CheckboxGroup options={options} field="validateScope" label="当前希望校验的Field" initValue={['a', 'b']} direction="horizontal" />
<Form.CheckboxGroup options={options} field="resetScope" label="当前需要Reset的Field" direction="horizontal" />
<Space>
<Button htmlType="reset">reset</Button>
<Button onClick={() => this.validatePartial('all')}>all validate</Button>
<Button onClick={() => this.validatePartial()}>partial validate {JSON.stringify(values.validateScope)}</Button>
<Button onClick={this.resetPartial}>partial reset</Button>
</Space>
</div>
</>
)
}
</Form>
);
}
}
表单联动 你可以通过监听 Field 的 onChange 事件,然后使用 formApi 进行相关修改,来使 Field 之间达到联动
import React from 'react';
import { Form, Button, Row } from '@douyinfe/semi-ui';
() => {
const formRef = useRef();
const handleSelectChange = (value) => {
let text = value === 'male' ? 'Hi male' : 'Hi female!';
formRef.current.formApi.setValue('Note', text);
};
return (
<Form ref={formRef} onValueChange={values => console.log(values) } style={{ width: 250 }}>
<span>Note will change after Sex select</span>
<Form.Input field="Note" style={{ width: 250 }}/>
<Form.Select field="Sex" onChange={handleSelectChange} style={{ width: 250 }}>
<Form.Select.Option value="female">female</Form.Select.Option>
<Form.Select.Option value="male">male</Form.Select.Option>
</Form.Select>
<Row>
<Button type="primary" htmlType="submit" className="btn-margin-right">
Submit
</Button>
<Button htmlType="reset">reset</Button>
</Row>
</Form>
);
};
动态表单 动态删减表单项 import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
() => (
<Form style={{ width: 450 }}>
{({ formState }) => (
<React.Fragment>
<Form.Input field="name" label='用户名称:' />
<Form.RadioGroup field="isAnchor" label='是否已注册主播'>
<Form.Radio value="yes">yes</Form.Radio>
<Form.Radio value="no">no</Form.Radio>
</Form.RadioGroup>
{formState.values.isAnchor === 'yes' ? (
<Form.Input field="liveRoom" label='直播间名称' />
) : null}
<Button htmlType="submit">提交</Button>
</React.Fragment>
)}
</Form>
);
使用 ArrayField 针对动态增删的数组类表单项,我们提供了 ArrayField 作用域来简化 add/remove 的操作
ArrayField 自带了 add、remove、addWithInitValue 等 api 用来执行新增行,删除行,新增带有初始值的行等操作
ArrayField 详细的 API请查阅下方
ArrayField Props
注意:ArrayField 的 initValue 类型必须是数组
import React from 'react';
import { ArrayField, TextArea, Form, Button, useFormState } from '@douyinfe/semi-ui';
import { IconPlusCircle, IconMinusCircle } from '@douyinfe/semi-icons';
class ArrayFieldDemo extends React.Component {
constructor() {
super();
this.state = {
data: [
{ name: 'Semi D2C', role: 'Engineer' },
{ name: 'Semi C2D', role: 'Designer' },
]
};
}
render() {
let { data } = this.state;
const ComponentUsingFormState = () => {
const formState = useFormState();
return (
<TextArea style={{ marginTop: 10 }} value={JSON.stringify(formState)} />
);
};
return (
<Form style={{ width: 800 }} labelPosition='left' labelWidth='100px' allowEmpty>
<ArrayField field='rules' initValue={data}>
{({ add, arrayFields, addWithInitValue }) => (
<React.Fragment>
<Button onClick={add} icon={<IconPlusCircle />} theme='light'>Add new line</Button>
<Button icon={<IconPlusCircle />} onClick={() => {addWithInitValue({ name: 'Semi DSM', type: 'Designer' });}} style={{ marginLeft: 8 }}>Add new line with init value</Button>
{
arrayFields.map(({ field, key, remove }, i) => (
<div key={key} style={{ width: 1000, display: 'flex' }}>
<Form.Input
field={`${field}[name]`}
label={`${field}.name`}
style={{ width: 200, marginRight: 16 }}
>
</Form.Input>
<Form.Select
field={`${field}[role]`}
label={`${field}.role`}
style={{ width: 120 }}
optionList={[
{ label: 'Engineer', value: 'Engineer' },
{ label: 'Designer', value: 'Designer' },
]}
>
</Form.Select>
<Button
type='danger'
theme='borderless'
icon={<IconMinusCircle />}
onClick={remove}
style={{ margin: 12 }}
/>
</div>
))
}
</React.Fragment>
)}
</ArrayField>
<ComponentUsingFormState />
</Form>
);
}
}
嵌套 ArrayField ArrayField 支持多级嵌套,如下是一个两级嵌套的例子
import { Form, ArrayField, Button, Card, Typography, } from "@douyinfe/semi-ui";
import { IconPlusCircle, IconMinusCircle } from "@douyinfe/semi-icons";
import React from "react";
const selectOption = [
{ label: '发件人地址', value: 'address' },
{ label: '邮件主题', value: 'title' },
{ label: '发送时间', value: 'sendTime' },
{ label: '接收时间', value: 'receiveTime' },
{ label: '正文', value: 'main' },
{ label: '附件名称', value: 'attachmentName' },
];
const initValue = {
group: [
{
name: "收信规则1",
rules: [
{ ruleType: "address", type: "include", text: "bytedance.com" },
{ ruleType: "title", type: "exclude", text: "更新日志" },
],
},
{
name: "收信规则2",
rules: [
{ ruleType: "sendTime", type: "include", text: "2019" }
],
},
]
};
const NestedField = (props) => {
const rowStyle = {
marginTop: 12,
marginLeft: 12,
};
return (
<ArrayField field={`${props.field}.rules`}>
{({ add, arrayFields, addWithInitValue }) => (
<React.Fragment>
{arrayFields.map(({ field, key, remove }, i) => (
<div style={{ display: "flex" }} key={key}>
<Form.Select
field={`${field}[ruleType]`}
label={`${field}.ruleType`}
noLabel
optionList={selectOption}
style={{ width: 120, marginRight: 12 }}
></Form.Select>
<Form.Select
field={`${field}[type]`}
label={`${field}.type`}
noLabel
style={{ width: 100, marginRight: 12 }}
optionList={[
{ label: "包含", value: "include" },
{ label: "不包含", value: "exclude" },
]}
></Form.Select>
<Form.Input
field={`${field}[text]`}
label={`${field}.text`}
noLabel
style={{ width: 200 }}
></Form.Input>
<Button
type="danger"
theme="borderless"
style={rowStyle}
icon={<IconMinusCircle />}
onClick={remove}
/>
<Button
icon={<IconPlusCircle />}
style={rowStyle}
disabled={i !== arrayFields.length - 1}
onClick={() => {
addWithInitValue({
ruleType: `条件${arrayFields.length + 1}`,
type: "include",
});
}}
/>
</div>
))}
</React.Fragment>
)}
</ArrayField>
);
};
const NestArrayFieldDemo = () => {
return (
<Form
onValueChange={(values) => console.log(values)}
initValues={initValue}
labelPosition="left"
style={{ textAlign: "left" }}
allowEmpty
>
<ArrayField field="group" >
{({ add, arrayFields, addWithInitValue }) => (
<React.Fragment>
<Button
icon={<IconPlusCircle />}
theme="solid"
onClick={() => {
addWithInitValue({
name: "新规则名称",
rules: [
{ ruleType: "main", type: "include", text: "" },
{ ruleType: "attachmentName", type: "include", text: "" },
],
});
}}
>
新增收信规则
</Button>
{arrayFields.map(({ field, key, remove }, i) => (
<div
key={key}
style={{ width: 1000, display: "flex", flexWrap: "wrap" }}
>
<Form.Input
field={`${field}[name]`}
labelPosition="top"
label={"规则名称"}
style={{ width: "600px" }}
></Form.Input>
<Button
type="danger"
style={{ margin: "36px 0 0 12px" }}
icon={<IconMinusCircle />}
onClick={remove}
/>
<Typography.Text strong style={{ flexBasis: "100%" }}>
当邮件到达,满足以下条件时:
</Typography.Text>
<Card
shadow="hover"
style={{
width: 620,
margin: "12px 0 0 24px",
}}
>
<NestedField field={field} />
</Card>
</div>
))}
</React.Fragment>
)}
</ArrayField>
</Form>
);
};
render(NestArrayFieldDemo);
Hooks 的使用 我们提供了四个 Hooks,使你在不需要通过 props 传递的情况下,也能在放置于 Form 结构内部的 Functional Component 中也能轻易访问到 Form 内部状态数据,以及调用 Form、Field 的相关 api
import { useFormApi, useFormState, useFieldApi, useFieldState } from '@douyinfe/semi-ui';
useFormApi 允许你通过 hook,在 Functional Component 内直接访问父级 Form 组件的 formApi
import React from 'react';
import { useFormApi, Form, Button } from '@douyinfe/semi-ui';
const ComponentUsingFormApi = () => {
const formApi = useFormApi();
const change = () => {
formApi.setValue('name', Math.random());
};
return (
<Button onClick={change}>ChangeName By【formApi】</Button>
);
};
class UseFromApiDemo extends React.Component {
render() {
return (
<Form>
<Form.Input field='name' initValue='mike'></Form.Input>
<ComponentUsingFormApi />
</Form>
);
}
}
render(UseFromApiDemo);
useFormState 允许你通过 hook,在 Functional Component 内直接访问父级 Form 组件的 formState
import React from 'react';
import { useFormState, Form } from '@douyinfe/semi-ui';
const ComponentUsingFormState = () => {
const formState = useFormState();
return (
<pre>
<code>{JSON.stringify(formState)}</code>
</pre>
);
};
class UseFromStateDemo extends React.Component {
render() {
return (
<Form>
<Form.Input field='name' initValue='mike'></Form.Input>
<h5>FormState read by 【useFormState】:</h5>
<ComponentUsingFormState />
</Form>
);
}
}
render(UseFromStateDemo);
useFieldApi useFieldApi 允许你通过 hook,在 Functional Component 内直接调用指定 Field 的 api
import React from 'react';
import { useFieldApi, Form, Button } from '@douyinfe/semi-ui';
const ComponentUsingFieldApi = () => {
const nameFieldApi = useFieldApi('name');
const change = () => {
nameFieldApi.setValue(Math.random());
};
return (
<Button onClick={change}>Click Me!!! changeNameBy【fieldApi】</Button>
);
};
class UseFieldApiDemo extends React.PureComponent {
render() {
return (
<Form>
<Form.Input field='name' initValue='mike'></Form.Input>
<ComponentUsingFieldApi />
</Form>
);
}
}
render(UseFieldApiDemo);
useFieldState useFieldState 允许你通过 hook,在 Functional Component 内直接访问指定 Field 的 State
import React from 'react';
import { useFieldState, Form } from '@douyinfe/semi-ui';
const ComponentUsingFieldState = props => {
const fieldState = useFieldState(props.field);
return (
<div>
<span>【{props.field}】FieldState read by 【useFieldState】:</span>
<code>{JSON.stringify(fieldState)}</code>
</div>
);
};
class UseFieldStateDemo extends React.PureComponent {
render() {
return (
<Form layout='horizontal'>
<div style={{ width: 400 }}>
<Form.Input field='name' initValue='mike'></Form.Input>
<Form.Input field='country' initValue='china'></Form.Input>
</div>
<div style={{ width: 500, marginTop: 30 }}>
<ComponentUsingFieldState field='name' />
<ComponentUsingFieldState field='country' style={{ marginTop: 40 }} />
</div>
</Form>
);
}
}
render(UseFieldStateDemo);
HOC 的使用 我们提供了两个 HOC: withFormApi
、withFormState
,可以在其他组件内部访问到 Form 的 formApi 以及内部状态 formState
提供了 HOC: withField
,用于将自定义组件封装成符合 Semi Form 数据流的表单控件
import { withFormApi, withFormState, withField } from '@douyinfe/semi-ui';
你可以通过 withFormApi HOC 来封装组件,使得该组件内部可以直接调用父级 Form 组件的 formApi
注意封装后的组件必须放置于 Form 结构内部
import React from 'react';
import { withFormApi, Form, Button } from '@douyinfe/semi-ui';
const SomeComponetInsideForm = props => (
<Button onClick={() => {
props.formApi.setValue('name', Math.random());
}}>Click Me!!! ChangeName By【formApi】</Button>
);
const ComponentWithFormApi = withFormApi(SomeComponetInsideForm);
class WithFormApiDemo extends React.Component {
render() {
return (
<Form>
<Form.Input field='name' initValue='semi'></Form.Input>
<Form.Input field='familyName' initValue='design'></Form.Input>
<Button htmlType='submit' style={{ marginRight: 4 }}>submit</Button>
<ComponentWithFormApi />
</Form>
);
}
}
render(WithFormApiDemo);
你可以通过 withFormState HOC 来封装组件,使得该组件内部可直接访问到父级 Form 组件的 FormState
注意封装后的组件必须放置于 Form 结构内部 (因其强依赖 Context 机制进行消费)
import React from 'react';
import { withFormState, Form } from '@douyinfe/semi-ui';
const SomeComponentInsideForm = props => (
<code>{JSON.stringify(props.formState)}</code>
);
const ComponentWithFormState = withFormState(SomeComponentInsideForm);
class WithFormStateDemo extends React.Component {
render() {
return (
<Form>
<Form.Input field='name' initValue='semi'></Form.Input>
<Form.Input field='familyName' initValue='design'></Form.Input>
<ComponentWithFormState />
</Form>
);
}
}
render(WithFormStateDemo);
withField 封装自定义表单控件 通过 withField,你可以将其他自定义组件扩展成为表单控件,由 Form 接管其行为
注意
自定义组件必须为受控组件,只有受控组件,才能被 Form 接管,正确地进行值的更新
withField 主要做了以下事情
负责接管组件的 value(或者 valueKey 指定的其他属性)、onChange(或 onKeyChangeFnName 指定的其他回调函数) 负责在表单控件上方插入 Field 的<Form.Label>
负责在表单控件下方插入 Field 的<ErrorMessage>
负责在表单控件下方插入 Field 的 extraText 你的自定义受控组件需要做以下事情:
值发生变化时,调用props.onChange (或 onKeyChangeFnName 指定的其他回调函数) 并且将最新的值作为入参 响应props.value(或者 valueKey 指定的其他属性)的变化,并更新你的组件UI渲染结果 withField(YourComponent, withFieldOption);
import React from 'react';
import { withField, Form } from '@douyinfe/semi-ui';
// 这里将html原生的input封装
const htmlInput = (props) => {
let value = props.value || '';
let { validateStatus, ...rest } = props; // prevent props being transparently transmitted to DOM
return <input {...rest} value={value} />;
};
const CustomInput = withField(htmlInput, { valueKey: 'value', onKeyChangeFnName: 'onChange', valuePath: 'target.value' });
// 观察formState,看input的数据流是否已被form接管
const ComponentUsingFormState = () => {
const formState = useFormState();
return (
<pre>
<code>{JSON.stringify(formState)}</code>
</pre>
);
};
class WithFieldDemo1 extends React.Component {
render() {
return (
<Form>
<CustomInput field='name' />
<ComponentUsingFormState />
</Form>
);
}
}
render(WithFieldDemo1);
import React from 'react';
import { withField, Input, Select, Form } from '@douyinfe/semi-ui';
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 (
<div className='customField'>
<Input insetLabel='名称' value={name} onChange={v => handleChange(v, 'name')} style={{ width: 180, marginRight: 12 }} />
<Select
insetLabel='角色'
value={role}
onChange={v => handleChange(v, 'role')}
style={{ width: 200 }}
optionList={[{ value: 'rd', label: '开发' }, { value: 'UED', label: '设计师' }]}
/>
</div>
);
};
const CustomField = withField(MyComponent, { valueKey: 'value', onKeyChangeFnName: 'onChange' });
const ComponentUsingFormState = () => {
const formState = useFormState();
return (
<pre>
<code>{JSON.stringify(formState)}</code>
</pre>
);
};
class WithFieldDemo2 extends React.Component {
render() {
return (
<Form>
<CustomField field='baseInfo' label={{ text: '基本信息', required: true }} />
<ComponentUsingFormState />
</Form>
);
}
}
render(WithFieldDemo2);