代码演示 声明表单的多种写法 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 } from '@douyinfe/semi-ui';
import { IconUpload } from '@douyinfe/semi-icons';
class BasicDemoWithInit extends React.Component {
constructor() {
super();
this.state = {
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: 'dy.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'
}
]
}
};
}
render() {
const { Section, Input, InputNumber, AutoComplete, Select, TreeSelect, Cascader, DatePicker, TimePicker, TextArea, CheckboxGroup, Checkbox, RadioGroup, Radio, Slider, Rating, Switch, TagInput } = Form;
const { initValues } = this.state;
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>
<Button type="primary" htmlType="submit" className="btn-margin-right">提交(submit)</Button>
<Button htmlType="reset">重置(reset)</Button>
</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: 'shoulde 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
设置初始值(后者优先级更高) import React from 'react';
import { Form, Button } from '@douyinfe/semi-ui';
class BasicDemoWithInit extends React.Component {
constructor() {
super();
this.state = {
initValues: {
name: 'semi',
role: 'rd'
}
};
this.getFormApi = this.getFormApi.bind(this);
}
getFormApi(formApi) { this.formApi = formApi; }
render() {
const { Select, Input } = Form;
const style = { width: '100%' };
return (
<Form initValues={this.state.initValues}>
<Input
field="name"
label="名称(Input)"
style={style}
trigger='blur'
rules={[
{ required: true, message: 'required error' },
{ type: 'string', message: 'type error' },
{ validator: (rule, value) => value === 'semi', message: 'should be semi' }
]}
/>
<Select field="role" style={style} label='角色' placeholder='请选择你的角色' initValue={'pm'}>
<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>
<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>
);
}
}
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并且将最新的值作为入参 响应props.value的变化,并更新你的组件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);