React中沒有相似Angular那樣的雙向數據綁定,在作一些表單複雜的後臺類頁面時,監聽、賦值、傳遞、校驗時編碼相對複雜,滿屏的樣板代碼傷痛欲絕,故引入能夠解決這些問題的
redux-form
(v6) 模塊。本文大體翻譯了官方文檔一些比較重要的地方,結合官方Demo加入了一些特性,有些官方跑不起來的地方也進行了優化。javascript
項目地址: https://github.com/tedyuen/react-redux-form-v6-examplehtml
在線演示地址java
本地演示方法: npm install && npm run start
react
redux-form
以前,須要具有如下基礎:redux-form
的三個主要模塊:formReducer reducer
: 表單的各類操做以 Redux action 的方式,經過此 reducer 來促使 Redux store 數據的變化。npm
reduxForm() HOC
: 此高階組件用以整合 Redux action 綁定的用戶交互與您的組件,並返回一個新的組件供以使用。json
<Field/>
: 用此代替您本來的 <input/>
組件,能夠與redux-form的邏輯相鏈接。redux
在大部分狀況下您不須要關心如何建立action,一切都是自動的。下圖展現了一個簡易的數據流:
舉個簡單的例子,咱們有一個被 reduxForm()
建立的表單組件,裏面有一個用 <Field/>
建立的 <input/>
組件,數據流大概是這個樣子的:
用戶點擊這個 <input/>
組件,
"Focus action" 被觸發,
formReducer 更新了對應的狀態,
這個狀態被傳回 <input/>
組件中。
與此相似的在這個 <input/>
中輸入文字、更改狀態、提交表單,也是遵循以上這個流程。
redux-form
還能基於此流程處理許多事情,諸如:表單驗證與格式化,多參數與action的建立。基於如下的嚮導,請自助挖掘更深層次的功能。
store須要知道組件如何發送action,所以咱們須要在您的store中註冊 formReducer
,他能夠服務於整個app中你定義的全部表單組件,所以只須要註冊一次。
import { createStore, combineReducers } from 'redux' import { reducer as formReducer } from 'redux-form' const rootReducer = combineReducers({ // ...your other reducers here // you have to pass formReducer under 'form' key, // for custom keys look up the docs for 'getFormState' form: formReducer }) const store = createStore(rootReducer)
注: 在reducer中合併的formReducer的key必須命名爲"form"。若是您因某些緣由須要自定義key,請移步 getFormState config查看詳情。
爲了使您的表單組件能夠與store進行交互,咱們須要使用高價函數 reduxForm()
來包裹您的組件。他能夠在您執行提交表單等操做的時候,以props的方式提供表單內的state。
import React from 'react' import { Field, reduxForm } from 'redux-form' let ContactForm = props => { const { handleSubmit } = props return ( <form onSubmit={ handleSubmit }> { /* form body*/ } </form> ) } ContactForm = reduxForm({ // a unique name for the form form: 'contact' })(ContactForm) export default ContactForm;
如今咱們已經有一個表單組件了,讓咱們添加一些input組件。
注: 若是您以爲 ()() 這類的語法很迷惑,您能夠把它分兩步來看:
// ... // create new, "configured" function createReduxForm = reduxForm({ form: 'contact' }) // evaluate it for ContactForm component ContactForm = createReduxForm( ContactForm ) export default ContactForm;
<Field/>
Components<Field/>
組件能夠鏈接全部input類型組件的數據到store中,基本用法以下:
<Field name="inputName" component="input" type="text" />
它建立了一個text類型的<input/>
組件,還提供了諸如 value
onChange
onBlur
等屬性,用於跟蹤和維護此組件的各類狀態。
注: <Field/>
組件很強大,除了基本的類型,還能夠配置類或者無狀態組件,欲瞭解更多,請移步Field usage。
import React from 'react' import { Field, reduxForm } from 'redux-form' const ContactForm = props => { const { handleSubmit } = props return ( <form onSubmit={ handleSubmit }> <div> <label htmlFor="firstName">First Name</label> <Field name="firstName" component="input" type="text" /> </div> <div> <label htmlFor="lastName">Last Name</label> <Field name="lastName" component="input" type="text" /> </div> <div> <label htmlFor="email">Email</label> <Field name="email" component="input" type="email" /> </div> <button type="submit">Submit</button> </form> ) } ContactForm = reduxForm({ // a unique name for the form form: 'contact' })(ContactForm) export default ContactForm;
從如今開始,表單上的操做數據已經能夠填充至store,並能夠執行提交表單操做了。
提交的數據以JSON對象的形式注入了此表單組件的 onSubmit
方法裏了,能夠打印出來看:
import React from 'react' import ContactForm from './ContactForm' class ContactPage extends React.Component { submit = (values) => { // print the form values to the console console.log(values) } render() { return ( <ContactForm onSubmit={this.submit} /> ) } }
本節對理解您的組件value經過 redux-form
的流向很重要
redux-form
提供了3個 value 生命週期鉤子函數,經過props傳遞給Field組件,而且都是可選的。
format(value:Any) => String
格式化從store裏拿出來的數據渲染到組件裏,一般會在store保留原來的數據類型,只是在組件中使用的時候進行格式化。
parse(value:String) => Any
把用戶輸入的string類型的數據進行格式轉化,放入store供你使用,也會在store保留轉化後類型的數據。
normalize(value:Any, previousValue:Any, allValues:Object, previousAllValues:Object) => Any
容許您對當前字段數據添加某些約束的邏輯,好比能夠約束 midDate
的日期在 maxDate
以前等。若是你添加了這些邏輯,經過 normalize()
的value將會被解析。
限於篇幅問題,在此只列舉每一種api經常使用的使用方法,具體請移步官方API文檔
經過配置一些參數建立一個可讓你配置你的表單的修飾器。諸如配置如何作表單驗證、提交成功或失敗的回調、獲取或失去焦點的action發送、prop命名空間等,具體例子會在以後的demo中介紹。
var reduxForm = require('redux-form').reduxForm; // ES5 import { reduxForm } from 'redux-form'; // ES6
必要參數
form : String[required]
: 用於命名您的表單,在store生成此命名的數據節點。
可選參數
onChange : Function [optional]
: 表單觸發 onChange 事件後的回調。
onSubmit : Function [optional[
: 表單提交配置,能夠配置須要提交哪些參數,還有提交時觸發的 dispatch
等。
onSubmitSuccess : Function [optional]
& onSubmitFail : Function [optional]
: 提交表單成功和失敗的回調。
shouldValidate(params) : boolean [optional]
: 同步驗證。
shouldAsyncValidate(params) : boolean [optional]
: 異步驗證。
touchOnBlur : boolean [optional]
& touchOnChange : boolean [optional]
: 標識 onBlur
或 onChange
的觸發。
列出所有當前頁面由 redux-form
生成用於修飾此表單組件的props。
若是你但願用嚴格模式來編寫 PropTypes, redux-form
會導出此處全部的 propTypes,你須要引用他們並能夠添加本身的propTypes,像這樣:
import {reduxForm, propTypes} from 'redux-form'; class SimpleForm extends Component { static propTypes = { ...propTypes, // other props you might be using } // ... }
pristine
: true
表示表單數據爲原始數據沒被修改過,反之爲 dirty
。
submitting
: 用於表示您的表單提交狀態,他只會在您的表單提交後返回一個 promise
對象時起做用。 false
表示 promise
對象爲 resolved
或 rejected
狀態。
handleSubmit(eventOrSubmit) : Function
: 提交表單的函數,若是表單須要驗證,驗證方法會被執行(包括同步和異步)。調用方法有兩種:
組件內部直接調用 <form onSubmit={handleSubmit}>
賦值給prop外部調用 <MyDecoratedForm onSubmit={data => {//do something with data.}}/>
全部您須要與 store
數據鏈接的表單組件,均可以用 <Field/>
。在正確使用它以前,有三條基本概念您須要瞭解清楚:
必須包含 name
屬性。能夠是簡單的字符串,如 userName
、password
,也能夠是複雜的結構,如 contact.billing.address[2].phones[1].areaCode
。
必須包含 component
屬性。能夠是一個組件、無狀態組件或者DOM所支持的默認的標籤(input、textarea、select)。
其餘全部屬性會經過prop傳遞到元素生成器中。如 className
var Field = require('redux-form').Field; // ES5 import { Field } from 'redux-form'; // ES6
1.組件
能夠是任何自定義的 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}/>
2.無狀態組件
這是一個很是靈活的使用 <Field/>
的方法,使用方法和 redux-form
的前一個版本很類似。但必須在你的 render()
方法外定義它,不然它每次渲染都會被重建,而且因爲組件的 prop
會變,就會強制 <Field/>
進行渲染。若是你在 render()
內部定義無狀態組件,不但會拖慢你的app,並且組件的input每次都會在組件從新渲染的時候失去焦點。
// 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}/>
3.string: input, select, or textarea
好比建立一個文字輸入框組件
<Field component="input" type="text"/>
與 Field
類似,可是它同時使用多個fields。<Fields/>
在 name
屬性中使用一組表單name的數組,而不是用單一一個 name
屬性來表示。
重要: 請節制使用 <Fields/>
,其內部任何表單組件數據變化時,都會從新渲染整個 <Fields/>
。所以會成爲您app的性能瓶頸。除非你真的須要這麼作,最好仍是用 <Field/>
來一個個自定義您的表單組件
var Fields = require('redux-form').Fields; // ES5 import { Fields } from 'redux-form'; // ES6
與 <Field/>
差很少,有2種使用方式,組件與無狀態組件,這裏不詳細介紹。
這個組件可讓你定義一系列的表單,它的工做原理和 <Field/>
同樣。經過 <Field/>
,給它一個 name
,就能夠映射到 Redux state
中的指定位置。組件也能夠經過鏈接到 Redux state
的 props
進行渲染。
經過 <FieldArray/>
,你也須要和 <Field/>
同樣給它一個 name
。而你注入 <FieldArray/>
的組件會經過字段數組收到一系列的 props
,用以查詢、更新和迭代。
var FieldArray = require('redux-form').FieldArray; // ES5 import { FieldArray } from 'redux-form'; // ES6
後面Demo裏會具體介紹
Form
組件對React的form組件進行了簡單的封裝,用以觸發用 redux-form
修飾的組件的 onSubmit
函數。
您能夠在如下場景中使用它:
在您表單組件內部,能夠經過 onSubmit={this.props.handleSubmit(this.mySubmitFunction)}
執行您的提交。
或者
經過 submit() Instance API來啓動您的提交內容。(即,在引用您的表單組件的地方直接調用)
經過 dispatch
一個 action
的方式啓動調用。請參考 Remote Submit Example
若是您只是將 onSubmit
函數做爲你的配置或屬性,那麼你不須要用到這個組件。
var Form = require('redux-form').Form; // ES5 import { Form } from 'redux-form'; // ES6
只須要將您組件中全部 <form>
替換成 <Form>
便可。
FormSection
能夠很簡單地將現有的表單組件分割成更小的組件,用以在複雜的表單中進行復用。它是經過明確規定好的 Field
、Fields
和FieldArray
字組件 name
的前綴來完成此功能的。
這個例子所描述的業務是一個購買人與收件人視角的訂單用戶信息表單結構。購買人與收件人擁有相同的字段結構,所以把這個部分拆分紅一個名爲 Party
的組件是有意義的。假設如今 Party
包含 givenName
middleName
surname
address
這幾個字段,而後將 address
部分再度拆分紅可重用的組件 Address
。代碼以下:
//Address.js class Address extends Component { render() { return <div> <Field name="streetName" component="input" type="text"/> <Field name="number" component="input" type="text"/> <Field name="zipCode" component="input" type="text"/> </div> } } //Party.js class Party extends Component { render() { return <div> <Field name="givenName" component="input" type="text"/> <Field name="middleName" component="input" type="text"/> <Field name="surname" component="input" type="text"/> <FormSection name="address"> <Address/> </FormSection> </div> } } //OrderForm.js class OrderForm extends Component { render() { return <form onsubmit={...}> <FormSection name="buyer"> <Party/> </FormSection> <FormSection name="recipient"> <Party/> </FormSection> </form> } } //don't forget to connect OrderForm with reduxForm()
字段完整的名字最後將變成如 buyer.address.streetName
的形式,結果結構以下:
{ "buyer": { "givenName": "xxx", "middleName": "yyy", "surname": "zzz", "address": { "streetName": undefined, "number": "123", "zipCode": "9090" } }, "recipient": { "givenName": "aaa", "middleName": "bbb", "surname": "ccc", "address": { "streetName": "foo", "number": "4123", "zipCode": "78320" } } }
相似 Address
的組件不多更改它的 name
,爲了使組件繼承 FormSection
而不是 Component
,須要設置一個默認的 name
以下:
class Address extends FormSection { //ES2015 syntax with babel transform-class-properties static defaultProps = { name: "address" } render() { return <div> <Field name="streetName" component="input" type="text"/> <Field name="number" component="input" type="text"/> <Field name="zipCode" component="input" type="text"/> </div> } } //Regular syntax: /* Address.defaultProps = { name: "address" } */
做爲一個修飾,能夠讀取當前表單的 value
。當表單子組件的 onChange
依賴於當前表單裏的值,頗有用。
var formValues = require('redux-form').formValues; // ES5 import { formValues } from 'redux-form'; // ES6
const ItemList = formValues('withVat')(MyItemizedList) const ItemList = formValues({showVat: 'withVat'})(MyItemizedList)
這些裝飾組件如今分別擁有了 withVat
與showVat
的 props
。
formValueSelector
的API能夠很方便的 connect()
state
的值到表單的 value
裏。它能夠經過表單的 name
爲你的表單建立一個 value
拾取器。
var formValueSelector = require('redux-form').formValueSelector; // ES5 import { formValueSelector } from 'redux-form'; // ES6
首先須要按照你表單的 name
建立一個 selector
。
const selector = formValueSelector('myFormName')
而後有幾種方法使用 selector
:
1.拾取個別的字段
connect( state => ({ firstValue: selector(state, 'first'), secondValue: selector(state, 'second') }) )(MyFormComponent)
2.在分好組的 prop
中按組的方式拾取多個字段
connect( state => ({ myValues: selector(state, 'first', 'second') }) )(MyFormComponent)
3.把 selector
看成 mapStateToProps
來使用
若是你不須要 state
中其餘的屬性值,selector
做爲mapStateToProps
能夠自動完成這個工做。
connect( state => selector(state, 'first', 'second') )(MyFormComponent)
表單的reducer
用來安裝您的 Redux state
到您的表單中。
若是您使用 Immutablejs
來管理您的 Redux state
,你必須這麼從 redux-form/immutable
中導入 reducer
模塊。
var redux = require('redux'); var formReducer = require('redux-form').reducer; // Or with Immutablejs: // var formReducer = require('redux-form/immutable').reducer; var reducers = { // ... your other reducers here ... form: formReducer }; var reducer = redux.combineReducers(reducers); var store = redux.createStore(reducer);
import { createStore, combineReducers } from 'redux'; import { reducer as formReducer } from 'redux-form'; // Or with Immutablejs: // import { reducer as formReducer } from 'redux-form/immutable'; const reducers = { // ... your other reducers here ... form: formReducer }; const reducer = combineReducers(reducers); const store = createStore(reducer);
表單中返回一個經過附加指定功能 reducers
用以接受 action
的reducer
。 它的參數應該是一個能映射 formName
和一個(state, action) => nextState
reducer
關係的一個對象。經過每個 reducer
的state只能是屬於那個表單的一個片斷。
flux
體系中最美的一部分應該是全部 reducers
(或者 Flux
中的標準術語 stores
)能夠接受全部 actions
,他們能夠修改基於這些 action
來修改數據。舉個例子,你有一個登陸的表單,當你提交失敗的時候,你想清楚密碼輸入框內的數據,哪怕你的登陸的提交信息是屬於另外一個 reducer/actions
體系,你的表單依然能夠作出本身的響應。
而不是使用 redux-form
中一個普通的 reducer
,你能夠經過調用 plugin()
函數來增強你的 reducer
。
注:這是一個增強功能的操做用來修改你內部的 redux-form
state
的片斷,若是你不當心使用,會把事情搞砸。
下面這個例子的做用是,當 AUTH_LOGIN_FAIL
的 action
被分發時,能夠清除登陸表單裏的密碼輸入框:
import { createStore, combineReducers } from 'redux' import { reducer as formReducer } from 'redux-form' import { AUTH_LOGIN_FAIL } from '../actions/actionTypes' const reducers = { // ... your other reducers here ... form: formReducer.plugin({ login: (state, action) => { // <----- 'login' is name of form given to reduxForm() switch(action.type) { case AUTH_LOGIN_FAIL: return { ...state, values: { ...state.values, password: undefined // <----- clear password value }, fields: { ...state.fields, password: undefined // <----- clear field state, too (touched, etc.) } } default: return state } } }) } const reducer = combineReducers(reducers) const store = createStore(reducer)
這個 throwable error
用於從 onSubmit
返回一個表單驗證錯誤信息。目的是用來區分 promise
失敗的緣由到底是驗證錯誤、AJAX I/O錯誤仍是其餘服務器錯誤。若是它是因爲表單裏 { field1: 'error', field2: 'error' }
產生的錯誤,那這個錯誤將會被添加到每個標記過錯誤屬性的字段裏,就像異步表單驗證錯誤同樣。若是有一個錯誤沒有指定的字段,可是應用到了整個表單,你須要繼續傳遞它,就好像是某個字段調用的 _error
同樣,而後他會給出一個錯誤的屬性。(就是無論他往外拋)
var SubmissionError = require('redux-form').SubmissionError; // ES5 import { SubmissionError } from 'redux-form'; // ES6
<MyForm onSubmit={values => ajax.send(values) // however you send data to your server... .catch(error => { // how you pass server-side validation errors back is up to you if(error.validationErrors) { throw new SubmissionError(error.validationErrors) } else { // what you do about other communication errors is up to you } }) }/>
redux-form
對外開放了全部的內部 action creators
,容許你按找你的意願來完成對分發 action
的控制。進而,官方推薦您在完成您大部分需求的時候,對於那些表單裏指定需求的字段的 action
來講,看成這些 action
已經綁定到 dispatch
同樣,直接將這些 action
經過 props
傳遞。
具體 action
請參考官方文檔。
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)
這個例子把表單全部基本的元素都列了出來,和官方Demo有所區別的是,增長了2個 type
爲 file
的 Field
(直接在 Field
中使用 file
的類型會有點問題),一個是使用了jQuery的 dropify 編寫的上傳單個文件的組件 MyDropify
,一個是使用了 dropzone
編寫的上傳多個文件的組件 MyDropzone
(在這裏使用了 react-dropzone 和 redux-form
的組合)。官方的例子不單獨介紹了,主要貼一下兩個自定義 Field
。
注:因爲reducer設計之初是純函數,而提交文件的表單最後取得的值是一個 file
對象,當您使用了 redux-immutable-state-invariant 之類的檢測工具,對其中諸如 lastModifiedDate
的值會報錯,具體請看。在此,咱們暫時先不考慮immutable的問題。
src/components/demo/simple/
src/components/utils/MyDropify.js
代碼:
import React, { Component } from 'react'; const $ = window.$; require('dropify'); class MyDropify extends Component { componentDidMount(){ $('.dropify').dropify(); } render() { const { input,dataAllowedFileExtensions } = this.props const onAttachmentChange = (e) => { e.preventDefault(); const files = [...e.target.files]; input.onChange(files); }; return ( <div> <input type="file" onChange={onAttachmentChange} className="dropify" data-allowed-file-extensions={dataAllowedFileExtensions} /> </div> ) } } export default MyDropify;
使用方法:
<div className="form-group"> <div className="input-group"> <label>Dropify</label> <Field component={MyDropify} name="inputfile1" dataAllowedFileExtensions="doc docx txt pdf xls xlsx jpg png bmp"></Field> </div> </div>
dropify 的具體用法請參考其官方文檔。
src/components/utils/MyDropify.js
代碼:
import React, { Component } from 'react'; import Dropzone from 'react-dropzone'; class MyDropzone extends Component { render() { const { input,desc,accept } = this.props const onDrop = (files) => { input.onChange(files); }; return ( <Dropzone onDrop={onDrop} accept={accept}> {({ isDragActive, isDragReject, acceptedFiles, rejectedFiles }) => { if (isDragActive) { return "This file is authorized"; } if (isDragReject) { return "This file is not authorized"; } return acceptedFiles.length || rejectedFiles.length ? `Accepted ${acceptedFiles.length}, rejected ${rejectedFiles.length} files` : desc; }} </Dropzone> ) } } export default MyDropzone;
使用方法:
<div className="form-group"> <div className="input-group"> <label>Dropzone</label> <Field component={MyDropzone} name="inputfile2" desc="My Dropzone" accept="image/png,image/jpeg"></Field> </div> </div>
react-dropzone
和jQuery版本的有所區別,使用過 dropzone
的應該都知道選擇文件能夠渲染到框體內,react版本的 dropzone
原聲不帶這個功能,但它提供了詳盡的方法能夠本身實現不少功能,好比選擇完文件能夠渲染到組件中,有時間我再完善此功能。
同步的表單驗證,包括了錯誤和警告型配置。官方Demo中只演示了輸入框的驗證,而這裏準備了包括 radio
select
textarea
的驗證方式(checkbox
我會在單獨的一章講解),調用方法能夠參見本文的源代碼。
src/components/demo/syncValidation/
src/components/utils/validation/radioField.js
import React from 'react'; const inputField = ({ input, label, type, meta: { touched, error, warning } }) => ( <div className={touched && error ? 'has-error form-group':'form-group'}> <div className="input-group"> <span className="input-group-addon">{label}</span> <input {...input} placeholder={label} type={type} className="form-control"/> </div> {touched && ((error && <div className="help-block with-errors">{error}</div>) || (warning && <div className="help-block with-errors">{warning}</div>))} </div> ) export default inputField;
src/components/utils/validation/selectField.js
import React from 'react'; const selectField = ({ input, label, selects, meta: { touched, error, warning } }) => ( <div className={touched && error ? 'has-error form-group':'form-group'}> <div className="input-group"> <span className="input-group-addon">{label}</span> <select {...input} className="form-control"> { selects.map((item, i) => ( <option key={i} value={item.value}>{item.text}</option> )) } </select> </div> {touched && ((error && <div className="help-block with-errors">{error}</div>) || (warning && <div className="help-block with-errors">{warning}</div>))} </div> ) export default selectField;
src/components/utils/validation/textareaField.js
import React from 'react'; const textareaField = ({ input, label, type, cols, rows, meta: { touched, error, warning } }) => ( <div className={touched && error ? 'has-error form-group':'form-group'}> <label>{label}</label> <textarea {...input} cols={cols} rows={rows} className="form-control"></textarea> {touched && ((error && <div className="help-block with-errors">{error}</div>) || (warning && <div className="help-block with-errors">{warning}</div>))} </div> ) export default textareaField;
除了提供一個驗證方法一塊兒驗證表單裏的值這種方法以外,還能夠對每個 <Field/>
或 <FieldArray/>
分別作驗證。官方給的Demo已經足夠說明問題了,在這裏只針對上面的 Sync Validation
做簡單的改寫。具體請看代碼。
一種服務器表單驗證較好的方法是在調用 onSubnit
以後返回一個 rejected
的 promise
對象。當您的表單被提交時,有2種方法提供給 redux-form
這個函數。
把他看成一個 onSubmit
的 prop
傳遞給您的裝飾組件。那樣的話,你能夠在您的裝飾組件中使用 onSubmit={this.props.handleSubmit}
確保當用戶點擊提交按鈕的時候觸發這個函數。
把他看成一個參數傳遞給您裝飾組件內的 this.props.handleSubmit
函數。這種狀況下,你須要使用 onClick={this.props.handleSubmit(mySubmit)}
來確保當用戶點擊提交按鈕的時候觸發這個函數。
這個錯誤信息的顯示方式和同步驗證(Synchronous Validation)後的錯誤信息同樣,但他是經過 onSubmit
函數返回一個封裝過的 SubmissionError
對象。這個驗證錯誤就像HTTP的400或500錯誤同樣,和I/O錯誤是有區別的,而且他還會是這個提交的 promise
對象的狀態置爲 rejected
。
DEMO中沒什麼花頭,和官方同樣,就是基於 SyncValidation
把表單驗證的邏輯放在了提交後的邏輯中,並拋出了一個 SubmissionError
。
服務器表單驗證的方式比較推薦使用Submit Validation,可是可能存在當您填寫表單的時候,同時須要服務器端來驗證。有一個經典的例子是當一個用戶選取一個值,好比用戶名,它必須是您系統中惟一的一個值。
爲了寫一個異步的表單驗證,須要給 redux-form
提供一個異步驗證的函數(asyncValidation)用來提供一個能夠從表單獲取數據的一個對象,而後 Redux
分發這個函數,返回一個狀態爲擁有一個錯誤對象的 rejects
或狀態爲 reslove
的 promise
對象。
您須要同時指定某幾個字段,經過 asyncBlurFields
的屬性配置,來標記是否須要在他們失去焦點的時候觸發這個異步驗證。
異步驗證會在 onSubmit
以前被調用,因此若是你關心的是 onSubmit
驗證,你須要使用 Submit Validation
當一個字段的同步驗證錯誤時,那它的失去焦點的時候將不會觸發異步驗證。
Demo中的自定義 <Field/>
的 meta
中有一個 asyncValidating
,來標識異步驗證的 promise
對象的 Pending
狀態。
經過 initialValues
屬性或 reduxForm()
配置的參數所提供的數據,被加載到表單 state
中,而且把這些初始化數據做爲原始數據(pristine)。當 reset()
觸發的時候,也會返回這些值。除了保存這些 pristine
值,初始化您表單的這個操做也會替換表單裏已經存在的值。
在許多應用中,這些值多是來自服務器而且儲存在其餘 reducer
中的。想要獲得這些值,你須要使用 connect()
去本身連接 state
而後映射這些數據到您的 initialValues
屬性裏。
默認狀況下,你只須要經過 initialValues
初始化您的表單組件一次便可。目前有2種方法能夠經過新的 pristine
值從新初始化表單。
傳遞一個 enableReinitialize
屬性或配置 reduxForm()
中的參數爲true就可讓表單在每次 initialValues
屬性變化的時候從新初始化,生成一個新的 pristine
值。若是想要在從新初始化的時候保持已改變過的表單的值,能夠設置 keepDirtyOnReinitialize
爲true。默認狀況下,從新初始化會將 pristine
值替換掉已改變過的表單的值。
發出一個 INITIALIZE
action(用 redux-form
action生成器生成)。
此Demo較之官方Demo,增長了 enableReinitialize
和 keepDirtyOnReinitialize
的用法。如下是代碼片斷。
InitializeFromStateForm = reduxForm({ form: 'initializeFromState',// a unique identifier for this form enableReinitialize:true, keepDirtyOnReinitialize:true,// 這個值表示從新初始化表單後,不替換已更改的值,能夠用clear來測試 })(InitializeFromStateForm)
有時候您但願訪問表單組件中某些字段的值,你須要在 store
中直接 connect()
表單的值。在通常的使用狀況下,redux-form
經過 formValueSelector
提供了一個方便的選擇器。
警告: 須要節制使用這個機制,由於這樣的話,表單裏的某一個值一旦發生改變,就會從新渲染您的組件。
代碼片斷:
// 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
這個例子展現了怎樣構建一個字段組,包括擁有一個字段的和擁有一組字段的字段組。在這個表單裏,每個俱樂部的成員都有姓和名,還有一個興趣的列表。如下這些數組的操做 insert, pop, push, remove, shift, swap, unshift
行爲是被容許的:(更多詳細的內容能夠參考FieldArray Docs)
一個 action
的原始構造
經過您表單的 this.props.array
對象綁定的 action
同時綁定表單和經過 FieldArray
組件得到的對象上的數組的 action
這個例子演示了一個表單如何從一個無關的組件或中間件中發送的一個 SUBMIT
的action來執行提交邏輯。
這個例子裏你所看到的的提交按鈕,不是直接與表單組件直接連接的,它的做用只是經過 Redux
發送的一個提交的 action
。
要注意它的工做方式,這個提交函數必須經過 reduxForm()
配置參數的傳遞或經過 prop
提供給表單組件。如下是發送這個action的方式:
import React from 'react' import { connect } from 'react-redux' import { submit } from 'redux-form' const style = { padding: '10px 20px', width: 140, display: 'block', margin: '20px auto', fontSize: '16px' } const RemoteSubmitButton = ({ dispatch }) => ( <button type="button" style={style} onClick={() => dispatch(submit('remoteSubmit'))} > Submit </button> ) // remoteSubmit 爲表單的名字 export default connect()(RemoteSubmitButton)
當您須要在用戶輸入和 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)}` }
一種常見的UI設計模式是把一個單一的表單分割成幾組分開的表單形式,最爲熟知的就是 Wizard
。使用 redux-form
的話有好多方式能夠來作這種設計,但最簡單和最推薦的方式是遵循一下幾種指示:
把每個頁面都用同一個表單名字鏈接到 reduxForm()
指定 destroyOnUnmount
爲 false
就能夠在表單組件卸載的時候保存表單數據
你能夠爲整個表單指定一個同步驗證函數
使用 onSubmit
來觸發進入下一步,由於它強制運行驗證函數
須要由你本身來實現的:
在提交成功以後手動調用 props.destory()
例子裏的代碼主要列出控制 Wizard
的組件,其餘組件的用法已被咱們熟知。
import React, { Component } from 'react' import PropTypes from 'prop-types' import WizardFormFirstPage from './WizardFormFirstPage' import WizardFormSecondPage from './WizardFormSecondPage' import WizardFormThirdPage from './WizardFormThirdPage' class WizardForm extends Component { constructor(props) { super(props) this.nextPage = this.nextPage.bind(this) this.previousPage = this.previousPage.bind(this) this.state = { page: 1 } } nextPage() { this.setState({ page: this.state.page + 1 }) } previousPage() { this.setState({ page: this.state.page - 1 }) } render() { const { onSubmit } = this.props const { page } = this.state return ( <div> {page === 1 && <WizardFormFirstPage onSubmit={this.nextPage} />} {page === 2 && <WizardFormSecondPage previousPage={this.previousPage} onSubmit={this.nextPage} />} {page === 3 && <WizardFormThirdPage previousPage={this.previousPage} onSubmit={onSubmit} />} </div> ) } } WizardForm.propTypes = { onSubmit: PropTypes.func.isRequired } export default WizardForm