Redux-Form 基礎使用

有關於 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或狀態爲 reslovepromise 對象。

您須要同時指定某幾個字段,經過 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 = '報錯啦'

相關文章
相關標籤/搜索