Field


开发指南

何时使用

涉及到表单数据操作、校验的地方都可以用 Field 来管理数据。和组件关联后可以自动对表单数据进行回写、读取、校验。

使用注意

基本使用

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>
    }
}

props 更新

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>
    }
}

ajax 更新

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>
    }
}

onChange 更新监控

两种用法:

  1. 统一管理
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>
    }
}
  1. 各自管理
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 演示 关联控制

API

初始化

let myfield = new Field(this [,options]);
参数 说明 类型 可选值 默认值
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 的时候滚动到第一个出错的组件, 如果是整数会进行偏移 Boolean/Number true
autoUnmount 自动删除 Unmout 元素,如果想保留数据可以设置为 false Boolean true
autoValidate 是否修改数据的时候就自动触发校验, 设为 false 后只能通过 validate() 来触发校验 Boolean true
values 初始化数据 Object

API 接口

new之后的对象提供的 api 接口 (例:myfield.getValues())(set 开头的 api 函数不要在 render 里面操作, 可能会触发死循环)

参数 说明 类型 可选值 默认值
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[]], [options: Object], 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)

init

init(name, options, props)
参数 说明 类型 可选值 默认值
id 自定义的表单域 id,如不提供则使用 name 替代 String
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) 参数顺序和组件的 onChange 完全一致
options.setValueFormatter 自定义转换 value 为组件需要的数据 ,详细用法查看 demo 自定义数据获取 Function(value)
props 组件自定义的事件可以写在这里 Object
autoValidate 是否修改数据的时候自动触发校验单个组件的校验, 设为 false 后只能通过 validate() 来触发校验 Boolean true

返回值

{id,value,onChange}

rules

{
    rules:[{ required: true }]
}

多个 rule

{
    rules:[{required:true,trigger:'onBlur'},{pattern:/abcd/,message:'abcd不能缺'},{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(),否则会校验不返回) Function(rule,value,callback)
trigger 触发校验的事件名称 String/Array onChange/onBlur/... onChange
message 出错时候信息 String

自定义组件接入 Field 标准

componentWillReceiveProps(nextProps) {
    if ('value' in nextProps ) {
        this.setState({
           value: nextProps.value === undefined? []: nextProps.value   //  设置组件的被清空后的数值
        })
    }
}

已知问题

DEMO 列表

基本 - basic

getValue setValue reset 的使用


usage of getValue setValue reset

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button } from '@alifd/next';
import Field from '@alifd/field';



class App extends React.Component {
    field = new Field(this, {values: { input: 0}});

    onGetValue() {
        console.log(this.field.getValue('input'));
    }

    render() {
        const { init, setValue, reset } = this.field;

        return (<div className="demo">
            <Input {...init('input')} />
            <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;
}

关联控制 - controlled

组件之间的关联控制. onChange 统一管理。


manage value by onChange

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Select, Range } from '@alifd/next';
import Field from '@alifd/field';



class App extends React.Component {
    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' ?
                    <Range
                        style={{...layout, marginTop: 30}}
                        slider={'double'} scales={10} 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);

自定义返回值 - custom value

当组件返回的数据和最终期望提交的格式不一致的时候,可以使用 getValueFormattersetValueFormatter 两个函数做转换。

比如 switch 组件期望上报 0/1, date-picker 组件期望上报 YYYY-MM-DD 这种字符串格式


custom get value by api getValueFormatter custom set value by api setValueFormatter

import ReactDOM from 'react-dom';
import React from 'react';
import { Button, DatePicker, Switch } from '@alifd/next';
import Field from '@alifd/field';
import moment from 'moment';

class App extends React.Component {

    field = new Field(this);

    render() {
        const init = this.field.init;

        return (<div>
            <Switch {...init('switch', { 
                getValueFormatter: (value) => {return value === true? 1:0},
                setValueFormatter: (value) => {return value===1? true: false}
                })}/>
            <br/><br/>
            <DatePicker {...init('time', { 
                getValueFormatter: (value) => value.format('YYYY-MM-DD'),
                setValueFormatter: (value) => moment(value, 'YYYY-MM-DD')
                })} />
            <br/><br/>
            <Button type="primary" onClick={() => {
                console.log(this.field.getValues());
            }}>getValues</Button>
        </div>);
    }
}


ReactDOM.render(<App/>, mountNode);

自定义错误 - custom errors

自己控制组件的errors


set errors of component by yourself

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button } from '@alifd/next';
import Field from '@alifd/field';



class App extends React.Component {
    field = new Field(this);

    validate = () => {
        console.log(this.field.getErrors());
      this.field.validate((error, values) => {
        // eslint-disable-next-line no-alert
        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/>
            <Button onClick={() => {
                setError('input', 'set error 1');
            }}>setError</Button>

            <Button onClick={() => {
                setErrors({input: 'set error 2'});
            }}>setErrors</Button>

            <Button onClick={() => {
                setErrors({input: ''});
            }}>clear</Button>

            <br/><br/>
            <Input {...init('input2')} /><br/>
            <span style={{color: 'red'}}>{getError('input2')}</span><br/>

            <Button onClick={() => {
                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;
}

校验 - validatePromise

校验的错误信息需要用getError获取

注意:Form 和 Field 做了深度结合,在 Form 中使用Field,错误信息不需getError获取会自动展现。


you can easily use validate in Form, or you can use getError to set errors where you want to put

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button, Checkbox } from '@alifd/next';
import Field from '@alifd/field';



const CheckboxGroup = Checkbox.Group;

const list = [
    {
        value: 'apple',
        label: 'apple'
    }, {
        value: 'pear',
        label: 'pear'
    }, {
        value: 'orange',
        label: 'orange'
    }
];

class App extends React.Component {
    state = {
        checkboxStatus: true
    }
    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, reject) => {
                setTimeout(() => reject(), 200);
            })
        }
    }

    render() {
        const init = this.field.init;

        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/>

            <Input placeholder="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/>

            <Input defaultValue="" 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.TextArea placeholder=">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:
                <CheckboxGroup dataSource={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/>

            <Button type="primary" onClick={() => {
                this.field.validatePromise().then(({errors, values}) => {
                    console.log(errors, values);
                });
            }}>validate</Button>
            <Button onClick={() => {
                this.field.reset();
            }}>reset</Button>

            <Button onClick={() => {
                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;
}

校验 - validate

校验的错误信息需要用getError获取

注意:Form 和 Field 做了深度结合,在 Form 中使用Field,错误信息不需getError获取会自动展现。


you can easily use validate in Form, or you can use getError to set errors where you want to put

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button, Checkbox } from '@alifd/next';
import Field from '@alifd/field';


const CheckboxGroup = Checkbox.Group;

const list = [
    {
        value: 'apple',
        label: 'apple'
    }, {
        value: 'pear',
        label: 'pear'
    }, {
        value: 'orange',
        label: 'orange'
    }
];

class App extends React.Component {
    state = {
        checkboxStatus: true
    }
    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 = this.field.init;

        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/>

            <Input placeholder="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/>

            <Input defaultValue="" 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.TextArea placeholder=">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:
                <CheckboxGroup dataSource={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/>

            <Button type="primary" onClick={() => {
                this.field.validateCallback((errors, values) => {
                    console.log(errors, values);
                });
            }}>validate</Button>
            <Button onClick={() => {
                this.field.reset();
            }}>reset</Button>

            <Button onClick={() => {
                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;
}

redux 中使用 - with redux

在 redux 中使用, 在 componentWillReceiveProps 更新


set value in componentWillReceiveProps

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button } from '@alifd/next';
import Field from '@alifd/field';
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 {
    componentWillReceiveProps(nextProps) {
        this.field.setValues({
            email: nextProps.email,
            newlen: nextProps.email.length
        });
    }

    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
                }
            });
        }
    });

    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);

自动卸载 - auto unmount

autoUnmount 默认为 true,当组件被 unmount 的时候会自动删除数据. autoUnmount 设置为 false 后,会一直保存数据.


autoUnmount is true by default, and data will be deleted automatically. Field will keep data while autoUnmount is set to false.

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button } from '@alifd/next';
import Field from '@alifd/field';


class Demo extends React.Component {
    state = {
        show: true,
        show2: true
    }
    field = new Field(this);
    field2 = new Field(this, { autoUnmount: false });

    render() {
        return (
            <div>
                {this.state.show ? < Input {...this.field.init('name', { initValue: 'autoUnmount = true' })} /> : null}
                <Button
                    onClick={() => {
                        console.log('value auto delete', this.field.getValues());
                    }}
                    style={{marginLeft: 4}}
                >
                    print
                </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}
                <Button
                    onClick={() => {
                        console.log('value always exist', this.field2.getValues());
                    }}
                    style={{marginLeft: 4}}
                >
                    print
                </Button>
                <Button
                    onClick={() => this.setState({ show2: false })}
                    warning
                    style={{marginLeft: 4}}
                >
                    delete
                </Button>
            </div>
        );
    }
}

ReactDOM.render(<Demo />, mountNode);

动态表格 - dynamic

通过 deleteArrayValue/addArrayValue 可以往数组格式的数据里面 删除/添加 数据, 并且自动订正其他 name 的 偏移问题


by use deleteArrayValue/addArrayValue could delete and add array , and fix keys offset problem

import ReactDOM from 'react-dom';
import React from 'react';
import { Button, Input, Table } from '@alifd/next';
import Field from '@alifd/field';

const CustomInput = (props) => {
    return <Input {...props} />
}

class Demo extends React.Component {
    constructor(props) {
        super(props);

        this.idx = 3;

        this.field = new Field(this, {
            parseName: true,
            values: {
                name: [0, 1, 2, 3].map(i => {
                    return { id: i, input: i, custom: i };
                })
            }
        });
    }

    getValues = () => {
        const values = this.field.getValues();
        console.log(values);
    }

    addItem(index){
        ++this.idx;
        this.field.addArrayValue('name', index, {id: this.idx, input: this.idx, custom: this.idx});
         console.log(this.field.getNames())
    }

    removeItem(index) {
        this.field.deleteArrayValue('name', index);
        console.log(this.field.getNames())
    }

    input = (value, index) => <Input  {...this.field.init(`name.${index}.input`)} />;
    customInput = (value, index) => <CustomInput  {...this.field.init(`name.${index}.custom`)} />;
    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>
    }

    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="custom" dataIndex="id" cell={this.customInput} />
                    <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);

组合使用 - mix usage

多组件混合使用


multi type of component

import ReactDOM from 'react-dom';
import React from 'react';
import { Button, Checkbox, Input, Radio, Select, Range, DatePicker, TimePicker } from '@alifd/next';
import Field from '@alifd/field';


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 {
    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/>

            <Range style={{...layout, marginTop: 30}} slider={'double'} scales={10}
                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>
            <Button type="primary" onClick={() => {
                console.log(this.field.getValues());
            }}>getValues</Button>
            <Button onClick={() => {
                this.field.setValues({
                    name: 'hugohua',
                    range: [30, 50],
                    checkboxgroup: ['orange'],
                    radiogroup: 'd'
                });
            }}>setValues</Button>
            <Button onClick={() => {
                this.field.reset();
            }}>reset</Button>
        </div>);
    }
}


ReactDOM.render(<App/>, mountNode);
.demo .next-btn {
    margin-right: 5px;
}

自定义受控字段 - custom valueName

valueName 的默认值为 value,如果为其他需要用 valueName 指定


default valueName is value

import ReactDOM from 'react-dom';
import React from 'react';
import { Button, Checkbox, Radio, Switch } from '@alifd/next';
import Field from '@alifd/field';



class App extends React.Component {

    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/>

            <Button type="primary" onClick={() => {
                console.log(this.field.getValues());
            }}>getValues</Button>
            <Button onClick={() => {
                this.field.setValues({
                    radio: true,
                    switch: true,
                    checkbox: false
                });
            }}> setValues </Button>
            <Button onClick={() => {
                this.field.reset();
            }}>reset</Button>
        </div>);
    }
}

ReactDOM.render(<App/>, mountNode);
.demo .next-btn {
    margin-right: 5px;
}

自定义组件 - custom

自己的组件如何接入Field。

最低标准: 组件支持 onChange 读取组件数据。达到效果:Field 可以 getValue,但是 setValue 无效

完全支持: 组件支持受控, 也就是支持两个api:value onChange. value: 设置组件的数据; onChange: 在组件修改的时候在第一个数暴露数据


must: has api of onChange, so you can use getValue but you can't setValue perfect support: has api of value onChange. value: set data for component; onChange: return first param for component

import ReactDOM from 'react-dom';
import React from 'react';
import { Button } from '@alifd/next';
import Field from '@alifd/field';


class Custom extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            value: typeof props.value === 'undefined' ? [] : props.value
        };
    }

    // update value
    componentWillReceiveProps(nextProps) {
        if ('value' in nextProps) {
            this.setState({
                value: typeof nextProps.value === 'undefined' ? [] : nextProps.value
            });
        }
    }

    onAdd = () => {
        const value = this.state.value.concat([]);
        value.push('new');

        this.setState({
            value
        });
        this.props.onChange(value);
    }

    render() {
        return (<div className="custom">
            {this.state.value.map((v, i) => {
                return <Button key={i} >{v}</Button>;
            })}
            <Button type="primary" onClick={this.onAdd.bind(this)}>Add + </Button>
        </div>);
    }
}

/* eslint-disable react/no-multi-comp */
class App extends React.Component {
    field = new Field(this, {
        deepReset: true
    });

    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;
}
.custom span {
    border: 1px solid green;
    padding: 0px 5px;
    height: 24px;
    display: inline-block;
    margin-right: 2px;
}

结构化解析 - Parse Array or Object

init('obj.b') 的数据转换成 obj={obj:{b:'value'}}

init('arr.0') 的数据转换成 obj={arr:['']}


from init('obj.b') to obj={obj:{b:'value'}}

from init('arr.0') to obj={arr:['']}

import ReactDOM from 'react-dom';
import React from 'react';
import { Input, Button } from '@alifd/next';
import Field from '@alifd/field';



class App extends React.Component {
    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'})} /> &nbsp;
            obj.c: <Input {...init('obj.c', {initValue: 'test2'})} />

            <br/>

            <h3>Array transfer</h3>
            arr.0: <Input {...init('arr.0', {initValue: '0'})} /> &nbsp;
            arr.1: <Input {...init('arr.1', {initValue: '1'})} />
            <br/><br/>

            <h3>Object with Defaults</h3>
            objWithDefaults.a: <Input {...init('objWithDefaults.a')} /> &nbsp;
            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;
}

Hooks

getUseField requires useState and useMemo implementation from React or Rax. Extend the Field Component and add a useField static method.

static useField(...args) { return this.getUseField(useState)(...args); }

import ReactDOM from 'react-dom';
import React, { useState, useMemo } from 'react';
import { Input, Button } from '@alifd/next';
import Field from '@alifd/field';


class myField extends Field {
    static useField(...args) {
        return this.getUseField({useState, useMemo})(...args);
    }
}

 
function NewApp() {
    const field = myField.useField();

    const { init, setValue, reset } = field;

    function onGetValue() {
        console.log(field.getValue('input'));
    }

    function onSetValue() {
        field.setValue('input', 'xyz');
    }

    return (
        <div className="demo">
            <Input {...init('input', {initValue: 'test'})} />
            <Button onClick={onSetValue}> setValue </Button>
            <Button onClick={onGetValue}> getValue </Button>
            <br/><br/>
        </div>);
 }
 
 
ReactDOM.render(<NewApp/>, mountNode);