表单数据管理组件。
涉及到表单数据操作、校验的地方都可以用Field来管理数据。和组件关联后可以自动对表单数据进行回写、读取、校验。
init
过的组件, value
onChange
必须放在 init 的第三个参数, 否则有可能被 init 覆盖。Form
已经和Field
在数据获取
和自动校验提示
方面做了深度优化,建议在Form
中使用Field
, 请查看 Form demo。自动卸载的组件
数据请关闭此项parseName=true
可以通过 getValues
获取到结构化的数据, 但是 getValue 还是必须传完整 key 值PureComponent
中无法使用,除非你开启 forceUpdate
功能,但是会带来性能问题class Demo extends React.Component {
field = new Field(this); // 实例创建
onClick = () => {
console.log(this.field.getValue('name'));
};
render() {
const init = this.field.init;
// 注意:initValue只会在组件第一次初始化的时候被赋值,如果你是异步赋值请用setValue
return (
<div>
<Input {...init('name', { initValue: 'first value' })} />
<button onClick={this.onClick}>获取数据</button>
</div>
);
}
}
class Demo extends React.Component {
field = new Field(this);
onClick = () => {
this.field.setValue('name', 'newvalue'); // 赋值会自动触发render
};
render() {
const init = this.field.init;
return (
<div>
<Input {...init('name')} />
<button onClick={this.onClick}>设置数据</button>
</div>
);
}
}
class Demo extends React.Component {
field = new Field(this);
// 在组件挂载之前把数据设置进去(可以用initValue替代这种用法)
componentWillMount() {
this.field.setValue('name', 'init Name');
}
// 接收来自props的数据
componentWillReceiveProps(nextProps) {
this.field.setValue('name', nextProps.name);
}
render() {
const init = this.field.init;
return (
<div>
<Input {...init('name')} />
</div>
);
}
}
class Demo extends React.Component {
field = new Field(this);
onClick = () => {
Ajax({
url: '/demo.json',
success: json => {
// 回调事件中赋值更新
this.field.setValue('name', json.name);
},
});
};
render() {
const init = this.field.init;
return (
<div>
<Input {...init('name')} />
<button onClick={this.onClick}>设置数据</button>
</div>
);
}
}
两种用法:
class Demo extends React.Component {
field = new Field(this, {
onChange: (name, value) => {
switch (name) {
case 'name1':
this.field.setValue('name2', 'value set by name1');
break;
case 'name2':
this.field.setValue('name1', 'value set by name2');
break;
}
},
});
render() {
const init = this.field.init;
return (
<div>
<Input {...init('name1')} />
<Input {...init('name2')} />
</div>
);
}
}
class Demo extends React.Component {
render() {
const init = this.field.init;
return (
<div>
<Input
{...init('name1', {
props: {
onChange: v => {
this.field.setValue('name2', 'value set by name1');
},
},
})}
/>
<Input
{...init('name2', {
props: {
onChange: v => {
this.field.setValue('name1', 'value set by name2');
},
},
})}
/>
</div>
);
}
}
详细请查看demo演示 #关联控制。
getValue
setValue
reset
的使用
import React from 'react';
import ReactDOM from 'react-dom';
import { Input, Button, Field } from '@alifd/next';
class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this);}onGetValue() {console.log(this.field.getValue('input'));}render() {const { init, setValue, reset } = this.field;return (<div className="demo"><Input {...init('input', { initValue: 'test' })} /><br /><br /><Button type="primary" onClick={this.onGetValue.bind(this)}>getValue</Button><Button type="primary" onClick={() => setValue('input', 'set me by click')}>setValue</Button><Button onClick={() => reset()}>reset</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
组件之间的关联控制. onChange
统一管理。
import React from 'react';
import ReactDOM from 'react-dom';
import { Input, Select, Range, Field } from '@alifd/next';
class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this, {onChange: (name, value) => {console.log(this.field.getValues());switch (name) {case 'input':this.field.setValue('sync', `change to: ${value}`);break;case 'select':this.field.setValue('sync', `${value} is coming`);break;case 'range':this.field.setValue('sync', ` (${value.join(',')}) ready`);break;}},});}render() {const { init, getValue } = this.field;const layout = {marginBottom: 10,width: 400,};return (<div><Input placeholder="controlled by onChange" {...init('input')} style={layout} /><br /><Input placeholder="under control" {...init('input')} style={layout} /><br /><Select style={layout} {...init('select', { initValue: 'lucy' })}><Select.Option value="jack">jack</Select.Option><Select.Option value="lucy">lucy</Select.Option><Select.Option value="disabled" disabled>disabled</Select.Option><Select.Option value="hugo">hugo</Select.Option></Select><br />{getValue('select') !== 'hugo' ? (<Rangestyle={{ ...layout, marginTop: 30 }}slider="double"marks={10}{...init('range', {initValue: [20, 40],trigger: 'onProcess',})}/>) : null}<br /><hr style={{ marginBottom: 10 }} /><Input placeholder="everyone can control me" {...init('sync')} style={layout} /><br /></div>);}}ReactDOM.render(<App />, mountNode);
当组件返回的数据和最终期望提交的格式不一致的时候,可以使用 getValueFormatter
和 setValueFormatter
两个函数做转换。
比如 switch 组件期望上报 0/1, date-picker 组件期望上报 YYYY-MM-DD 这种字符串格式
{}
import React from 'react';
import ReactDOM from 'react-dom';
import { Field, DatePicker, Switch } from '@alifd/next';
import moment from 'moment';
class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this);}render() {const init = this.field.init;return (<div><Switch{...init('switch', {getValueFormatter: value => (value ? 1 : 0),setValueFormatter: value => value === 1,})}/><br /><DatePicker{...init('time', {getValueFormatter: value => value && value.format('YYYY-MM-DD'),setValueFormatter: value => moment(value, 'YYYY-MM-DD'),})}/><br /><pre style={{ marginTop: 8 }}>{JSON.stringify(this.field.getValues(), null, 2)}</pre></div>);}}ReactDOM.render(<App />, mountNode);
自己控制组件的errors
import React from 'react';
import ReactDOM from 'react-dom';
import { Input, Button, Field } from '@alifd/next';
class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this);this.validate = () => {console.log(this.field.getErrors());this.field.validate((error, values) => {alert(JSON.stringify(error));});};}render() {const { init, getError, setError, setErrors } = this.field;return (<div className="demo"><Input{...init('input', {rules: [{required: true,pattern: /hello/,message: 'must be hello',},],})}/><br /><span style={{ color: 'red' }}>{getError('input')}</span><br /><ButtononClick={() => {setError('input', 'set error 1');}}>setError</Button><ButtononClick={() => {setErrors({ input: 'set error 2' });}}>setErrors</Button><ButtononClick={() => {setErrors({ input: '' });}}>clear</Button><br /><br /><Input {...init('input2')} /><br /><span style={{ color: 'red' }}>{getError('input2')}</span><br /><ButtononClick={() => {setError('input2', 'errors will be removed by onChange and shown on validate');}}>setError</Button><Button onClick={this.validate}>validate</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
校验的错误信息需要用getError
获取
注意
:Form 和 Field 做了深度结合,在 Form 中使用Field,错误信息不需getError
获取会自动展现。
请参考 validatorPromise demo,以使用 Promise 而不是回调
import React from 'react';
import ReactDOM from 'react-dom';
import { Input, Button, Checkbox, Field } from '@alifd/next';
const CheckboxGroup = Checkbox.Group;const list = [{value: 'apple',label: 'apple',},{value: 'pear',label: 'pear',},{value: 'orange',label: 'orange',},];class App extends React.Component {constructor() {super(...arguments);this.state = {checkboxStatus: true,};this.field = new Field(this, { scrollToFirstError: -10 });}isChecked(rule, value, callback) {if (!value) {return callback('consent agreement not checked ');} else {return callback();}}userName(rule, value, callback) {if (value === 'frank') {setTimeout(() => callback('name existed'), 200);} else {setTimeout(() => callback(), 200);}}render() {const { init, validate, reset } = this.field;return (<div className="demo"><Input {...init('input', { initValue: 'delete all', rules: { required: true } })} />{this.field.getError('input') ? (<span style={{ color: 'red' }}>{this.field.getError('input').join(',')}</span>) : ('')}<br /><br /><Inputplaceholder="try onBlur"{...init('input1', {rules: [{required: true,message: 'can not be empty',trigger: ['onBlur', 'onChange'],},],})}/>{this.field.getError('input1') ? (<span style={{ color: 'red' }}>{this.field.getError('input1').join(',')}</span>) : ('')}<br /><br /><InputdefaultValue=""placeholder="try frank"{...init('username', {rules: [{validator: this.userName,trigger: ['onBlur', 'onChange'],},],})}/>{this.field.getState('username') === 'loading' ? 'validating...' : ''}{this.field.getError('username') ? (<span style={{ color: 'red' }}>{this.field.getError('username').join(',')}</span>) : ('')}<br /><br />{'agreement:'}<Checkbox{...init('checkbox', {valueName: 'checked',rules: [{ validator: this.isChecked }],})}/>{this.field.getError('checkbox') ? (<span style={{ color: 'red' }}>{this.field.getError('checkbox').join(',')}</span>) : ('')}<br /><br /><Input.TextAreaplaceholder=">3 and <10"{...init('textarea', {rules: [{required: true,minLength: 3,maxLength: 10,},],})}/>{this.field.getError('textarea') ? (<span style={{ color: 'red' }}>{this.field.getError('textarea').join(',')}</span>) : ('')}<br /><br />{this.state.checkboxStatus ? (<div>{'Array validate:'}<CheckboxGroupdataSource={list}{...init('checkboxgroup', {rules: [{required: true,type: 'array',message: 'choose one please',},],})}style={{ marginBottom: 10 }}/>{this.field.getError('checkboxgroup') ? (<span style={{ color: 'red' }}>{this.field.getError('checkboxgroup').join(',')}</span>) : ('')}</div>) : null}<br /><br /><Buttontype="primary"onClick={() => {validate((errors, values) => {console.log(errors, values);});}}>validate</Button><ButtononClick={() => {reset();}}>reset</Button><ButtononClick={() => {if (this.state.checkboxStatus) {this.setState({ checkboxStatus: false });this.field.remove('checkboxgroup');} else {this.setState({ checkboxStatus: true });}}}>{this.state.checkboxStatus ? 'delete' : 'restore'}</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
使用 Promise 的方式作为校验返回
import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button, Checkbox, Field } from '@alifd/next';
class App extends React.Component {constructor() {super(...arguments);this.state = {checkboxStatus: true,};this.field = new Field(this, { scrollToFirstError: -10 });}isChecked(_rule, value) {if (!value) {return Promise.reject('consent agreement not checked ');} else {return Promise.resolve(null);}}userName(_rule, value) {if (value === 'frank') {return new Promise((_resolve, reject) => {setTimeout(() => reject('name existed'), 200);});} else {return new Promise(resolve => {setTimeout(() => resolve(null), 200);});}}render() {const init = this.field.init;return (<div className="demo"><InputdefaultValue=""placeholder="try frank"{...init('username', {rules: [{validator: this.userName,trigger: ['onBlur', 'onChange'],},],})}/>{this.field.getState('username') === 'loading' ? 'validating...' : ''}{this.field.getError('username') ? (<span style={{ color: 'red' }}>{this.field.getError('username').join(',')}</span>) : ('')}<br /><br />{'agreement:'}<Checkbox{...init('checkbox', {valueName: 'checked',rules: [{ validator: this.isChecked }],})}/>{this.field.getError('checkbox') ? (<span style={{ color: 'red' }}>{this.field.getError('checkbox').join(',')}</span>) : ('')}<br /><br /><Buttontype="primary"onClick={() => {this.field.validatePromise().then(({ errors, values }) => {console.log(errors, values);});}}>validate</Button><ButtononClick={() => {this.field.reset();}}>reset</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
在 redux 中使用, 在 componentWillReceiveProps
更新
SyntaxError: Unexpected token (24:28) 20 : this.field = new Field(this, { 21 : onChange: (name, value) => { 22 : console.log('onChange', name, value); 23 : this.field.setValue('newlen', value.length); 24 : this.props.dispatch?.({ ^
import React from 'react';
import ReactDOM from 'react-dom';
import { Input, Button, Field } from '@alifd/next';
import { combineReducers, createStore } from 'redux';
import { Provider, connect } from 'react-redux';
function formReducer(state = { email: 'frankqian@qq.com' }, action) {switch (action.type) {case 'save_fields':return {...state,...action.payload,};default:return state;}}class Demo extends React.Component {constructor() {super(...arguments);this.field = new Field(this, {onChange: (name, value) => {console.log('onChange', name, value);this.field.setValue('newlen', value.length);this.props.dispatch?.({type: 'save_fields',payload: {[name]: value,},});},});}componentDidUpdate(prevProps) {if (prevProps.email !== this.props.email) {this.field.setValues({email: this.props.email,newlen: this.props.email.length,});}}setEmail() {this.props.dispatch?.({type: 'save_fields',payload: {email: 'qq@gmail.com',},});}render() {const init = this.field.init;const newLen = init('newlen', { initValue: this.props.email.length });return (<div><Input{...init('email',{ initValue: this.props.email },{rules: [{ required: true, type: 'email', message: 'at least 5 chars' }],})}/>{'now length is:'}{newLen.value}<p>{'email: '}{this.props.email}</p><Button onClick={this.setEmail.bind(this)}>set</Button></div>);}}const ReduxDemo = connect(state => {return {email: state.formReducer.email,};})(Demo);const store = createStore(combineReducers({formReducer,}));ReactDOM.render(<Provider store={store}><div><ReduxDemo /></div></Provider>,mountNode);
autoUnmount 默认为 true,当组件被 unmount 的时候会自动删除数据. autoUnmount 设置为 false 后,会一直保存数据.
import React from 'react';
import ReactDOM from 'react-dom';
import { Input, Button, Field } from '@alifd/next';
class Demo extends React.Component {constructor() {super(...arguments);this.state = {show: true,show2: true,};this.field = new Field(this);this.field2 = new Field(this, { autoUnmount: false });}render() {return (<div>{this.state.show ? (<Input {...this.field.init('name', { initValue: 'autoUnmount = true' })} />) : null}<ButtononClick={() => {console.log('value auto delete', this.field.getValues());}}style={{ marginLeft: 4 }}></Button><Button onClick={() => this.setState({ show: false })} warning style={{ marginLeft: 4 }}>delete</Button><br /><br />{this.state.show2 ? (<Input {...this.field2.init('name2', { initValue: 'autoUnmount = false' })} />) : null}<ButtononClick={() => {console.log('value always exist', this.field2.getValues());}}style={{ marginLeft: 4 }}></Button><Button onClick={() => this.setState({ show2: false })} warning style={{ marginLeft: 4 }}>delete</Button></div>);}}ReactDOM.render(<Demo />, mountNode);
通过 deleteArrayValue/addArrayValue
可以往数组格式的数据里面 删除/添加 数据, 并且自动订正其他 name 的 偏移问题
id | input | operation |
---|---|---|
0 | ||
1 | ||
2 | ||
3 |
[ { "id": 0, "input": 0 }, { "id": 1, "input": 1 }, { "id": 2, "input": 2 }, { "id": 3, "input": 3 } ]
import React from 'react';
import ReactDOM from 'react-dom';
import { Button, Input, Table, Field } from '@alifd/next';
class Demo extends React.Component {constructor() {super(...arguments);this.idx = 3;this.field = new Field(this, {parseName: true,values: {name: [0, 1, 2, 3].map(i => {return { id: i, input: i };}),},});this.getValues = () => {const values = this.field.getValues();console.log(values);};this.input = (value, index) => <Input {...this.field.init(`name.${index}.input`)} />;this.op = (value, index) => {return (<span><Button type="primary" onClick={this.addItem.bind(this, index + 1)}>add</Button><Button warning onClick={this.removeItem.bind(this, index)} style={{ marginLeft: 4 }}>delete</Button></span>);};}addItem(index) {++this.idx;this.field.addArrayValue('name', index, { id: this.idx, input: this.idx });}removeItem(index) {this.field.deleteArrayValue('name', index);}render() {const dataSource = this.field.getValue('name');return (<div><Table dataSource={dataSource}><Table.Column title="id" dataIndex="id" /><Table.Column title="input" dataIndex="id" cell={this.input} /><Table.Column title="operation" cell={this.op} width={150} /></Table><pre style={{ marginTop: 8 }}>{JSON.stringify(dataSource, null, 2)}</pre></div>);}}ReactDOM.render(<Demo />, mountNode);
多组件混合使用
import React from 'react';
import ReactDOM from 'react-dom';
import { Button, Checkbox, Input, Radio, Select, Range, DatePicker, TimePicker, Field,} from '@alifd/next';
const CheckboxGroup = Checkbox.Group;const RadioGroup = Radio.Group;const list = [{value: 'apple',label: 'apple',},{value: 'pear',label: 'pear',},{value: 'orange',label: 'orange',},];const layout = {marginBottom: 10,width: 400,};class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this);}render() {const { init, getValue } = this.field;return (<div className="demo"><div style={{ marginBottom: 10 }}><RadioGroup {...init('radiogroup', { initValue: 'a' })}><Radio value="a">A</Radio><Radio value="b">B</Radio><Radio value="c">C</Radio><Radio value="d">D</Radio></RadioGroup></div>{getValue('radiogroup') !== 'd' ? (<Select {...init('name', { initValue: 'lucy' })} style={layout}><Select.Option value="jack">jack</Select.Option><Select.Option value="lucy">lucy</Select.Option><Select.Option value="disabled" disabled>disabled</Select.Option><Select.Option value="hugohua">hugohua</Select.Option></Select>) : (<Input {...init('name', { initValue: 'frankqian' })} />)}<br /><Rangestyle={{ ...layout, marginTop: 30 }}slider="double"marks={10}{...init('range', { initValue: [20, 40] })}/><div style={{ marginBottom: 10 }}><CheckboxGroup dataSource={list} {...init('checkboxgroup', { initValue: ['apple'] })} /></div><div style={{ marginBottom: 10 }}><DatePicker {...init('datepicker')} /></div><div style={{ marginBottom: 10 }}><DatePicker.RangePicker {...init('rangepicker')} /></div><div style={{ marginBottom: 10 }}><TimePicker {...init('timepicker')} /></div><Buttontype="primary"onClick={() => {console.log(this.field.getValues());}}>getValues</Button><ButtononClick={() => {this.field.setValues({name: 'hugohua',range: [30, 50],checkboxgroup: ['orange'],radiogroup: 'd',});}}>setValues</Button><ButtononClick={() => {this.field.reset();}}>reset</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
valueName 的默认值为 value,如果为其他需要用 valueName 指定
import React from 'react';
import ReactDOM from 'react-dom';
import { Button, Checkbox, Radio, Switch, Field } from '@alifd/next';
class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this);}render() {const init = this.field.init;return (<div className="demo"><Radio {...init('radio', { initValue: false, valueName: 'checked' })}> {'checked'}</Radio><br /><Checkbox {...init('checkbox', { valueName: 'checked', initValue: true })}>defaultChecked</Checkbox><br /><Switch{...init('switch', { valueName: 'checked', initValue: false })}style={{ marginTop: 10, marginBottom: 10 }}/><br /><Buttontype="primary"onClick={() => {console.log(this.field.getValues());}}>getValues</Button><ButtononClick={() => {this.field.setValues({radio: true,switch: true,checkbox: false,});}}>{' '}{'setValues'}{' '}</Button><ButtononClick={() => {this.field.reset();}}>reset</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
自己的组件如何接入Field。
最低标准
: 组件支持 onChange
读取组件数据。达到效果
:Field 可以 getValue,但是 setValue 无效
完全支持
: 组件支持受控, 也就是支持两个api:value
onChange
. value: 设置组件的数据; onChange: 在组件修改的时候在第一个数暴露数据
import React from 'react';
import ReactDOM from 'react-dom';
import { Button, Field } from '@alifd/next';
class Custom extends React.Component {constructor() {super(...arguments);this.onAdd = () => {const { value = [] } = this.props;const newValue = value.concat('new');this.props.onChange(newValue);};}render() {const { value = [] } = this.props;return (<div className="custom">{value.map((v, i) => {return <Button key={i}>{v}</Button>;})}<Button type="primary" onClick={this.onAdd.bind(this)}>{'Add +'}{' '}</Button></div>);}}class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this);}onGetValue() {console.log(this.field.getValue('custom'));}render() {const { init, setValue, reset } = this.field;return (<div className="demo"><Custom {...init('custom', { initValue: ['test'] })} /><br /><br /><Button type="primary" onClick={this.onGetValue.bind(this)}>getValue</Button><Button type="primary" onClick={() => setValue('custom', ['test', 'setValue'])}>setValue</Button><Button onClick={() => reset()}>reset</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
.custom {
border: 1px dashed;
padding: 4px;
display: inline-block;
}
通过配置 parseName=true
,可以实现如下效果:
把 init('obj.b')
的数据转换成 obj={obj:{b:'value'}}
;
把 init('arr.0')
的数据转换成 obj={arr:['']}
;
{ "objWithDefaults": { "a": 1, "b": 2 }, "obj": { "b": "test1", "c": "test2" }, "arr": [ "0", "1" ] }
import React from 'react';
import ReactDOM from 'react-dom';
import { Input, Button, Field } from '@alifd/next';
class App extends React.Component {constructor() {super(...arguments);this.field = new Field(this, {parseName: true,values: {objWithDefaults: {a: 1,b: 2,},},});}onGetValue() {console.log(this.field.getValues());}onSetValue() {this.field.setValues({obj: {b: 'b',c: 'c',},arr: ['first', 'second'],objWithDefaults: {a: 100,b: 200,},});}render() {const { init, reset, resetToDefault } = this.field;return (<div className="demo"><h3>Object transfer</h3>{'obj.b: '}<Input {...init('obj.b', { initValue: 'test1' })} /> <span style={{ marginRight: 20 }} />{'obj.c: '}<Input {...init('obj.c', { initValue: 'test2' })} /><br /><h3>Array transfer</h3>{'arr.0: '}<Input {...init('arr.0', { initValue: '0' })} /> <span style={{ marginRight: 20 }} />{'arr.1: '}<Input {...init('arr.1', { initValue: '1' })} /><br /><br /><h3>Object with Defaults</h3>{'objWithDefaults.a: '}<Input {...init('objWithDefaults.a')} /> <span style={{ marginRight: 20 }} />{'objWithDefaults.b: '}<Input {...init('objWithDefaults.b')} /><br /><br />{'result:'}<pre>{JSON.stringify(this.field.getValues(), null, 2)}</pre><br /><br /><Button type="primary" onClick={this.onGetValue.bind(this)}>getValues</Button><Button onClick={this.onSetValue.bind(this)}>setValues</Button><Button onClick={() => reset()}>reset</Button><Button onClick={() => resetToDefault()}>resetToDefault</Button></div>);}}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
在 functional component 里可使用 Field.useField
支持 hooks. 依赖: react@^16.8
import ReactDOM from 'react-dom';
import { Input, Button, Field } from '@alifd/next';
function App() {const field = Field.useField();const { init, getError } = field;function onGetValue() {console.log(field.getValue('input'));}function onSetValue() {field.setValue('input', 'xyz');}return (<div className="demo"><Input{...init('input', {initValue: 'test',rules: [{required: true,pattern: /hello/,message: 'must be hello',},],})}/><span style={{ color: 'red' }}>{getError('input')}</span><br /><br /><Button onClick={onSetValue}> setValue </Button><Button onClick={onGetValue}> getValue </Button><br /><br /></div>);}ReactDOM.render(<App />, mountNode);
使用 field.watch
或 Field.useWatch
来监听字段值变化. Field.useWatch
是一个react hook, 依赖 react@^16.8
.
import { Component, useState } from 'react';
import ReactDOM from 'react-dom';
import { Switch, Input, Button, Field } from '@alifd/next';
function DemoForm({ showInput, field }) {const init = field.init;function onGetValue() {console.log(field.getValues());}function onSetValue() {field.setValues({switch: true,input: 'xyz',});}function onReset() {field.reset();}return (<div className="demo"><Switch {...init('switch', { valueName: 'checked' })} />{showInput && <Input {...init('input', { initValue: 'test' })} />}<br /><br /><Button onClick={onSetValue}> setValue </Button><Button onClick={onGetValue}> getValue </Button><Button onClick={onReset}> reset </Button><br /><br /></div>);}class ClassComponent extends Component {constructor(props) {super(props);this.state = {showInput: false,};this.field = new Field(this);this.unwatch = this.field.watch(['switch', 'input'], (name, value, oldValue, triggerType) => {console.group('[detect change]');console.log('name: ', name);console.log('value: ', oldValue, ' -> ', value);console.log('triggerType: ', triggerType);console.groupEnd();if (name === 'switch') {this.setState({ showInput: value });}});}componentWillUnmount() {this.unwatch();}render() {return <DemoForm field={this.field} showInput={this.state.showInput} />;}}function FunctionComponent() {const [showInput, setShowInput] = useState(false);const field = Field.useField();Field.useWatch(field, ['switch', 'input'], (name, value, oldValue, triggerType) => {console.group('[detect change]');console.log('name: ', name);console.log('value: ', oldValue, ' -> ', value);console.log('triggerType: ', triggerType);console.groupEnd();if (name === 'switch') {setShowInput(value);}});return <DemoForm field={field} showInput={showInput} />;}function App() {return (<div><h3>Class component</h3><ClassComponent /><h3>Function component</h3><FunctionComponent /></div>);}ReactDOM.render(<App />, mountNode);
.demo .next-btn {
margin-right: 5px;
}
let myfield = new Field(this [,options]);
// 或者使用hooks
let myfield = Field.useField([options]); // 要求 react 版本 > 16.8
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
this | 传入调用class的this | React.Component | 必须设置 | |
options | 一些事件配置, 详细参数如下 | Object | 非必须 |
options
配置项 #参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
onChange | 所有组件的change都会到达这里[setValue不会触发该函数] | Function(name,value) | |
parseName | 是否翻译init(name) 中的name (getValues会把带. 的字符串转换成对象) |
Boolean | false |
forceUpdate | 仅建议PureComponent的组件打开此强制刷新功能,会带来性能问题(500个组件为例:打开的时候render花费700ms, 关闭时候render花费400ms) | Boolean | false |
scrollToFirstError | field.validate的时候滚动到第一个出错的组件, 如果是整数会进行偏移, 浏览器兼容性关注scrollIntoViewIfNeeded |
Boolean/Number | true |
autoUnmount | 自动删除Unmout元素,如果想保留数据可以设置为false | Boolean | true |
autoValidate | 是否修改数据的时候就自动触发校验, 设为 false 后只能通过 validate() 来触发校验 | Boolean | true |
values | 初始化数据 | Object | - |
new
之后的对象提供的api接口 (例:myfield.getValues()
)(set
开头的api函数不要在render里面操作, 可能会触发死循环)
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
init | 初始化每个组件,详细参数如#init函数 | Function(name:String, option:Object) | ||
getValues | 获取一组输入控件的值,如不传入参数,则获取全部组件的值 | Function([names: String[]]) | ||
getValue | 获取单个输入控件的值 | Function(name: String) | ||
setValues | 设置一组输入控件的值(会触发render,请遵循react时机使用) | Function(obj: Object) | ||
setValue | 设置单个输入控件的值 (会触发render,请遵循react时机使用) | Function(name: String, value) | ||
validate | 校验并获取一组输入域的值与 Error | Function([names: String[]], callback: Function(errors, values)) | ||
getError | 获取单个输入控件的 Error | Function(name: String) | ||
getErrors | 获取一组输入控件的 Error | Function([name: String]) | ||
setError | 设置单个输入控件的 Error | Function(name: String, errors:String/Array[String]) | ||
setErrors | 设置一组输入控件的 Error | Function(obj: Object) | ||
reset | 重置一组输入控件的值、清空校验 | Function([names: String[]]) | ||
resetToDefault | 重置一组输入控件的值为默认值 | Function([names: String[]]) | ||
getState | 判断校验状态 | Function(name: String) | 'error' 'success' 'loading' '' | '' |
getNames | 获取所有组件的key | Function() | ||
remove | 删除某一个或者一组控件的数据,删除后与之相关的validate/value都会被清空 | Function(name: String/String[]) | ||
addArrayValue | 添加 name 是数组格式的数据, 并且自动处理其他 name 的数组错位问题 | Function(key: String, index: Number, value1, value2, ...) | ||
deleteArrayValue | 删除 name 是数组格式的数据, 并且自动处理其他 name 的数组错位问题 | Function(key: String, index: Number, howmany) | ||
watch | 监听字段值变化 | Function(names: String[], callback: Function(name: String, value: any, oldValue: any, triggerType: 'init' | 'change' | 'setValue' | 'unmount' | 'reset')) |
init(name, options, props);
返回值
{
id, value, onChange;
}
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
name | 必填输入控件唯一标志 | String | ||
options.valueName | 组件值的属性名称,如 Checkbox 的是 checked ,Input是 value |
String | 'value' | |
options.initValue | 组件初始值(组件第一次render的时候才会读取,后面再修改此值无效),类似defaultValue | any | ||
options.trigger | 触发数据变化的事件名称 | String | 'onChange' | |
options.rules | 校验规则 | Array/Object | ||
options.getValueFormatter | 自定义从组件获取 value ,详细用法查看demo 自定义数据获取 |
Function(value,...args) 参数顺序和组件是完全一致的 | ||
options.setValueFormatter | 自定义转换 value 到组件 ,详细用法查看demo 自定义数据获取 |
Function(value) | ||
props | 组件自定义的事件可以写在这里 | Object | ||
autoValidate | 是否修改数据的时候自动触发校验单个组件的校验, 设为 false 后只能通过 validate() 来触发校验 | Boolean | true |
{
rules: [{ required: true }];
}
多个rule
{
rules: [
{ required: true, trigger: 'onBlur' },
{ pattern: /abcd/, message: 'abcd不能缺', trigger: 'onChange' },
{
validator: (rule, value, callback) => {
callback('出错了');
},
},
];
}
参数 | 说明 | 类型 | 可选值 | 使用类型 |
---|---|---|---|---|
required | 不能为空 | Boolean | true | undefined/null/“”/[] 会触发此规则) |
pattern | 校验正则表达式 | 正则 | ||
minLength | 字符串最小长度 / 数组最小个数 | Number | String/Number/Array | |
maxLength | 字符串最大长度 / 数组最大个数 | Number | String/Number/Array | |
length | 字符串精确长度 / 数组精确个数 | Number | String/Number/Array | |
min | 最小值 | Number | String/Number | |
max | 最大值 | Number | String/Number | |
format | 对常用 pattern 的总结 | String | url、email、tel、number | String |
validator | 自定义校验, 校验的结果通过用户传递给 callback 的参数决定(校验成功的时候不要忘记执行 callback() ,否则会校验不返回): - callback() 无参数表示校验成功 - callback('this is a error msg') 有参数表示校验失败,并且参数为错误信息 |
Function(rule,value,callback) | ||
trigger | 触发校验的事件名称 | String/Array | onChange/onBlur/onFocus/... | onChange |
message | 出错时候信息 | String |
field.watch
的 react hook 实现
type WatchTriggerType = 'init' | 'change' | 'setValue' | 'unmount' | 'reset';
interface WatchCallback {
(name: string, value: unknown, oldValue: unknown, triggerType: WatchTriggerType): void;
}
class Field {
static useWatch: (field: Field, names: string[], callback: WatchCallback) => void;
}
必须
建议
比如有Process表示进展中的状态,建议增加API onProcess
;如果有Start表示启动状态,建议增加API onStart
value={undefined}
的时候清空数据, field 的 reset 函数会给所有组件下发 undefined 数据 建议
componentWillReceiveProps(nextProps) {
if ('value' in nextProps ) {
this.setState({
value: nextProps.value === undefined? []: nextProps.value // 设置组件的被清空后的数值
})
}
}
this.field.validate
的时候进不了回调函数? #可能是自定义了validator方法,确保callback
在任何分支下都能被执行到。