有关于 redux-form 的 数据流、表单value的生命周期 信息请参考:react
官方文档:(下面示例代码在官网可查阅)redux
http://redux-form.com/6.8.0/docs/GettingStarted.md/segmentfault
中文翻译文档:promise
https://segmentfault.com/a/1190000010088546#articleHeader1服务器
本文介绍表单中经常使用的几处功能:app
[1.Field组件使用]less
全部须要与 store 数据链接的表单组件,均可以用 <Field/>。在正确使用它以前,有三条基本概念须要了解清楚:异步
1. 必须包含 name 属性。能够是简单的字符串,如 userName、password,也能够是复杂的结构,如 contact.billing.address[2].phones[1].areaCode。async
2. 必须包含 component 属性。能够是一个组件、无状态组件或者DOM所支持的默认的标签(input、textarea、select)。ide
3. 其余全部属性会经过prop传递到元素生成器中。如 className
# Field name属性:
a. 表示在Submit 表单时所带参数values对象中的属性名
如:
<Field name="username" type="text" component='input' label="Username"/>
<Field name="phone" type="text" component='input' label="Phone"/>
<Field name="age" type="text" component='input' label="Age"/>
values:{ username:'小明', phone:13510535555, age:20 }
b. 在初始化表单数据后的 initialValues 对象,该对象中的属性对应 Field name 的名称,值自动映射
如:具体见第2部分
initialValues : { username:'小明', phone:13510535555, age:20 }
c. 供选取表单所填值
具体见第3部分
# Field component 属性:组件class 定义
// MyCustomInput.js import React, { Component } from 'react' class MyCustomInput extends Component { render() { const { input: { value, onChange } } = this.props return ( <div>
<span>The current value is {value}.</span>
<button type="button" onClick={() => onChange(value + 1)}>Inc</button>
<button type="button" onClick={() => onChange(value - 1)}>Dec</button>
</div> ) } } // 调用 import MyCustomInput from './MyCustomInput'
<Field name="myField" component={MyCustomInput}/>
# Field component 属性:无状态组件
// outside your render() method
const renderField = (field) => ( <div className="input-row">
<input {...field.input} type="text"/>
{field.meta.touched && field.meta.error &&
<span className="error">{field.meta.error}</span>}
</div>
) // inside your render() method
<Field name="myField" component={renderField}/>
注:必须在你的 render() 方法外定义它,不然它每次渲染都会被重建,而且因为组件的 prop 会变,就会强制 <Field/> 进行渲染。若是你在 render() 内部定义无状态组件,不但会拖慢你的app,并且组件的input每次都会在组件从新渲染的时候失去焦点。
# Field component 属性:string: input, select, or textarea
好比建立一个文字输入框组件:
<Field component="input" type="text"/>
[2.初始化表单值设置]
a. 经过 initialValues 属性或 reduxForm() 配置的参数所提供的数据,被加载到表单 state 中,而且把这些初始化数据做为原始数据(pristine)。当 reset() 触发的时候,也会返回这些值 pristine。
如:示例 http://redux-form.com/6.8.0/examples/initializeFromState/
(图1)点击 Load Account 初始化数据并做为原始数据
(图2)修改 Age 为 45,点击Undo Changes 触发 reset() ,返回 pristine 值,以下(图3)
b. 除了保存这些 pristine 值,初始化您表单的这个操做也会替换表单里已经存在的值。
表单已存在下面值:
点击 Load Account 初始化数据后:原添加的表单数据被初始化数据替换
c. 在许多应用中,这些值多是来自服务器而且储存在其余 reducer 中的。想要获得这些值,你须要使用 connect() 去本身连接 state 而后映射这些数据到您的 initialValues 属性里。
如示例代码:
// 用reduxForm()装饰。它将读取connect()提供的initialValues支持
InitializeFromStateForm = reduxForm({ form: 'initializeFromState', })(InitializeFromStateForm); InitializeFromStateForm = connect( state => ({ //mapStateToProps
initialValues: state.account.data, }), { load: loadAccount }, //mapDispatchToProps,将load动做注入到reduxForm,派发后初始化数据
)(InitializeFromStateForm);
[3.选取表单所填值]
a. 经过formValueSelector 选取表单值
先经过 store 直接 connect() 表单的值,再经过 redux-form 提供的选择器formValueSelector 选取表单值。
如示例代码:
import { Field, reduxForm, formValueSelector } from 'redux-form'; // Decorate with reduxForm(). It will read the initialValues prop provided by connect()
SelectingFormValuesForm = reduxForm({ form: 'selectingFormValues',// a unique identifier for this form
})(SelectingFormValuesForm) // Decorate with connect to read form values
const selector = formValueSelector('selectingFormValues') // <-- same as form name
SelectingFormValuesForm = connect(state => { // can select values individually
const hasEmailValue = selector(state, 'hasEmail') const favoriteColorValue = selector(state, 'favoriteColor') // or together as a group
const { firstName, lastName } = selector(state, 'firstName', 'lastName') return { hasEmailValue, favoriteColorValue, fullName: `${firstName || ''} ${lastName || ''}` } })(SelectingFormValuesForm) export default SelectingFormValuesForm
注:hasEmail、favoriteColor、firstName、lastName 为表单 Field 属性 name 值
警告: 须要节制使用这个机制,由于这样的话,表单里的某一个值一旦发生改变,就会从新渲染您的组件。
b. 经过 Selectors 中的 getFormValues 选取表单值
redux-form 提供了一系列有用的 Redux state 拾取器,能够在app的任何地方任何表单内拾取 state 上的数据。
下列全部拾取器拥有统一的使用方法: 他们都(除了getFormNames)使用表单的名字,来建立一个拾取器,不管表单的 state是什么。
import { getFormValues, getFormInitialValues, getFormSyncErrors, getFormMeta, getFormAsyncErrors, getFormSyncWarnings, getFormSubmitErrors, getFormNames, isDirty, isPristine, isValid, isInvalid, isSubmitting, hasSubmitSucceeded, hasSubmitFailed } from 'redux-form' MyComponent = connect( state => ({ values: getFormValues('myForm')(state), initialValues: getFormInitialValues('myForm')(state), syncErrors: getFormSyncErrors('myForm')(state), fields: getFormMeta('myForm')(state), asyncErrors: getFormAsyncErrors('myForm')(state), syncWarnings: getFormSyncWarnings('myForm')(state), submitErrors: getFormSubmitErrors('myForm')(state), names: getFormNames('myForm')(state), dirty: isDirty('myForm')(state), pristine: isPristine('myForm')(state), valid: isValid('myForm')(state), invalid: isInvalid('myForm')(state), submitting: isSubmitting('myForm')(state), submitSucceeded: hasSubmitSucceeded('myForm')(state), submitFailed: hasSubmitFailed('myForm')(state) }) )(MyComponent)
[4.格式化值 Field Normalizing ]
当您须要在用户输入和 store 中的数据之间施加某些控制,你可使用 normalizer。normalizer 就是一个每当值改变是,能够在保存到 store 以前进行某些转换的一个函数。
一个经常使用的例子:你须要一个某些通过格式化的值,好比电话号码或信用卡号。
Normalizers 传递了4个参数:
● value - 你设置了 normalizer 字段的值
● previousValue - 这个值最近一次变化以前的一个值
● allValues - 表单中,全部字段当前的值
● previousAllValues - 表单中,全部字段在最近一次变化前的值
这些可使你基于表单中另一个字段而限制某个特定的字段。好比例子中的字段最小最大值:这里你不能设置 min 中的值比 max中的值大,不能设置 max 中的值比 min 的值更小(下面有代码)
const upper = value => value && value.toUpperCase() const lower = value => value && value.toLowerCase() const lessThan = otherField => (value, previousValue, allValues) => parseFloat(value) < parseFloat(allValues[otherField]) ? value : previousValue const greaterThan = otherField => (value, previousValue, allValues) => parseFloat(value) > parseFloat(allValues[otherField]) ? value : previousValue //下面是对电话号码处理的逻辑 const normalizePhone = value => { if (!value) { return value } const onlyNums = value.replace(/[^\d]/g, '') if (onlyNums.length <= 3) { return onlyNums } if (onlyNums.length <= 7) { return `${onlyNums.slice(0, 3)}-${onlyNums.slice(3)}` } return `${onlyNums.slice(0, 3)}-${onlyNums.slice(3, 6)}-${onlyNums.slice(6, 10)}` }
[5.数据验证,支持同步验证和异步验证]
a. 同步验证
同步的表单验证,包括了错误和警告型配置。如示例代码:
const validate = values => { const errors = {} if (!values.username) { errors.username = 'Required' } else if (values.username.length > 15) { errors.username = 'Must be 15 characters or less' } if (!values.email) { errors.email = 'Required' } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) { errors.email = 'Invalid email address' } if (!values.age) { errors.age = 'Required' } else if (isNaN(Number(values.age))) { errors.age = 'Must be a number' } else if (Number(values.age) < 18) { errors.age = 'Sorry, you must be at least 18 years old' } return errors } const warn = values => { const warnings = {} if (values.age < 19) { warnings.age = 'Hmm, you seem a bit young...' } return warnings } /** * renderField 接收的是一个对象(Field组件props): * { name, value, input, meta: {touched, error, warning} } */ const renderField = ({ input, label, type, meta: {touched, error, warning, valid } }) => ( <div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type}/>
{touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))} </div>
</div>
) /** * 同步验证 * @param props * @returns {XML} * @constructor */ const SyncValidationForm = (props) => { const { handleSubmit, pristine, reset, submitting } = props return ( <form onSubmit={handleSubmit}>
<Field name="username" type="text" component={renderField} label="Username"/>
<Field name="email" type="email" component={renderField} label="Email"/>
<Field name="age" type="number" component={renderField} label="Age"/>
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>Clear Values</button>
</div>
</form>
) } export default reduxForm({ form: 'syncValidation', validate, //redux-form的验证功能
warn, //redux-form的警告功能
})(SyncValidationForm)
注:
1. validate、warn 验证函数命名以及返回对象命名:errors、warnings是固定的,不可更改,最终返回的错误信息格式:{ field1: <String>, field2: <String> }
2. input、meta: {touched, error, warning } 参数属性命名固定写法,不可更改
如错误信息:{username:'用户名不能为空',email:'右键不能为空'}, username的error值对应 Field中属性meta.error, username的warning值对应 Field中属性meta.warning;
3. Field 属性 meta.valid 与 props.valid的区别:
前者验证 当前Field字段值是否经过。后者判断整个表单内的全部Field是否经过验证,其中有一个为false,则props.valid为false
4. meta.touched 是否触动结束(得到焦点时为false,表示正在输入;反之,失去焦点时为true)
b. 异步验证
服务器表单验证的方式比较推荐使用Submit Validation,可是可能存在当您填写表单的时候,同时须要服务器端来验证。有一个经典的例子是当一个用户选取一个值,好比用户名,它必须是您系统中惟一的一个值。
为了写一个异步的表单验证,须要给 redux-form 提供一个异步验证的函数(asyncValidation)用来提供一个能够从表单获取数据的一个对象,而后 Redux 分发这个函数,返回一个状态为拥有一个错误对象的 rejects或状态为 reslove 的 promise 对象。
您须要同时指定某几个字段,经过 asyncBlurFields 的属性配置,来标记是否须要在他们失去焦点的时候触发这个异步验证。
重点:
1.异步验证会在 onSubmit 以前被调用。
2.当一个字段的同步验证错误时,那它的失去焦点的时候将不会触发异步验证。
如示例代码:
export default reduxForm({ form: 'asyncValidation', // a unique identifier for this form
validate, //同步验证函数
asyncValidate, //异步验证函数
asyncBlurFields: ['username'], //指定须要异步验证的字段
})(AsyncValidationForm); asyncValidate.js: const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const asyncValidate = (values /*, dispatch */) => { return sleep(1000).then(() => { // simulate server latency
if (['john', 'paul', 'george', 'ringo'].includes(values.username)) { throw { username: 'That username is taken' }; //该用户名已被使用
} }); };
注:throw {} 抛出错误对象,只有在reduxForm()中定义了指定异步验证的字段才能捕获(caught)
以下图:即当同步验证密码输入值后失去焦点不会触发异步验证
[6.Submit 提交及 Submit Validation]
Form 组件对React的form组件进行了简单的封装,用以触发用 redux-form 修饰的组件的 onSubmit 函数。
在您表单组件内部,能够经过 onSubmit={this.props.handleSubmit(this.mySubmitFunction)} 执行您的提交。
如:示例代码
submit.js: function submit(values) { return sleep(1000).then(() => { // simulate server latency 模拟服务器延迟
if (!['john', 'paul', 'george', 'ringo'].includes(values.username)) { throw new SubmissionError({ username: 'User does not exist', _error: 'Login failed!', }); } else if (values.password !== 'redux-form') { throw new SubmissionError({ password: 'Wrong password', _error: 'Login failed!', }); } else { window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`); } }); } import submit from './submit'; const SubmitValidationForm = props => { const { error, handleSubmit, pristine, reset, submitting } = props; return ( <form onSubmit={handleSubmit(submit)}>
<Field name="username" type="text" component={renderField} label="Username"
/>
<Field name="password" type="password" component={renderField} label="Password"
/>
{error && <strong>{error}</strong>}
<div>
<button type="submit" disabled={submitting}>Log In</button>
<button type="button" disabled={pristine || submitting} onClick={reset}> Clear Values </button>
</div>
</form>
); };
注:
这个 SubmissionError 用于从 onSubmit 返回一个表单验证错误信息。目的是用来区分 promise 失败的缘由到底是验证错误、AJAX I/O错误仍是其余服务器错误。
若是它是因为表单里 { field1: 'error', field2: 'error' }产生的错误,那这个错误将会被添加到每个标记过错误属性的字段里,就像异步表单验证错误同样。
若是有一个错误没有指定的字段,但又适用于整个表单,你能够将此错误信息指定到 _error 字段并将它做为error prop传递到整个表单
【props.error】
在同步验证功能结果中,整个表单通常的错误(即props.error错误信息)由 _error key设定。
如代码:const { error } = props,设置 _error = '报错啦',那么代码中的 error = '报错啦'