有關於 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 = '報錯啦'