1. 程式人生 > >Redux-Form 基礎使用

Redux-Form 基礎使用

names 取數 val 每一個 存在 nor ajax ror iou

有關於 redux-form 的 數據流、表單value的生命周期 信息請參考:

官方文檔:(下面示例代碼在官網可查閱)

http://redux-form.com/6.8.0/docs/GettingStarted.md/

中文翻譯文檔:

https://segmentfault.com/a/1190000010088546#articleHeader1

本文介紹表單中常用的幾處功能:

[1.Field組件使用]

所有需要與 store 數據連接的表單組件,都可以用 <Field/>。在正確使用它之前,有三條基本概念需要了解清楚:

1. 必須包含 name 屬性。可以是簡單的字符串,如 userName、password,也可以是復雜的結構,如 contact.billing.address[2].phones[1].areaCode。

2. 必須包含 component 屬性。可以是一個組件、無狀態組件或者DOM所支持的默認的標簽(input、textarea、select)。

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. 同步驗證

同步的表單驗證,包括了錯誤和警告型配置。如示例代碼:

e = 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 = ‘報錯啦‘

Redux-Form 基礎使用