Formik與antd-mobile的移動端的表單實踐(上)

概覽

本文主要用於記錄該次使用Formik時用到的相關接口,而側重點不在antd-mobile,對antd-mobile會貼出對應組件API。react

文章須要基礎知識點:git

  1. React基本知識
  2. ES6基本知識

文章實踐環境爲:github

  • antd-mobile@2.2.3
  • formik@1.1.1
  • react@16.4.1

文章最後成果:redux

  • 熟悉Formik的使用方法
  • 封裝一個簡單的自用表單組件

Formik

開源的輕量級 React 表單組件。
GitHub地址:Formik
該表單組件主要解決如下三個問題:antd

  1. 表單的輸入輸出
  2. 驗證表單輸入並提供錯誤信息
  3. 控制表單的提交

至於爲何不用 redux-form 做者也給出瞭解釋:app

  1. 表單的狀態是短暫而局部的,因此在Redux(或任何類型的Flux庫)中追蹤它是沒必要要的。
  2. Redux-Form 在你每輸入一個字母都會調用一次頂級的Reducer,當項目逐漸變大,這一點會致使輸入的延遲變得明顯。
  3. Redux-Form 通過gzip壓縮後22.5 kB(而 Formik 僅有7.8 kB)

antd-mobile

ant-design的移動版UI組件庫。
官網:antd-mobile
不須要多作介紹了,每一個寫React的人都應該據說或用過antd系列的UI組件庫吧。異步

Formik 使用方法

  1. withFormik() HOC方法
  2. <Formik /> React組件

二者使用內在實際上是相同,在可控性上React組件方式會更好,對此能夠不須要太在乎,你能夠隨時在兩種使用方法間作轉換。
本文上篇主要講述Formik的HOC方法下的基本封裝,下篇則側重於封裝經常使用組件並對Formik兩種使用方法的切換作一次簡單的總結。async

官方示例解讀

// Higher Order Component
import React from 'react';
import { withFormik } from 'formik';

// Our inner form component which receives our form's state and updater methods as props
// 這就是個組件,傳入的props是由withFormik這個HOC函數內部作處理。
const InnerForm = ({
  values, // 表單中的值,爲一個對象{}
  errors, // 用於報錯提示的信息,爲一個對象{}
  touched, // 用於檢查用戶是否點擊過該表單項,爲一個對象{}
  handleChange, 
/* handleChange:默認的值修改回調函數,傳入參數爲 e: React.ChangeEvent<any> 對象。
因此若是使用antd-mobile組件,因爲某些組件傳入參數爲 value 值,所以須要對此進行必定程度上的封裝*/
  handleBlur,
/* handleBlur:失去焦點時默認的回調函數,傳入參數爲 event 對象。
 須要自定義時也須要對應的封裝。這個函數和handleChange都是DOM-only的函數 */
  handleSubmit, // 傳入參數爲 e: React.FormEvent<HTMLFormEvent> 對象
  isSubmitting, // isSubmitting表示表單提交的狀態
}) => (
  <form onSubmit={handleSubmit}>
    <input
      type="email"
      name="email"
      onChange={handleChange}
      onBlur={handleBlur}
      value={values.email}
    />
    // 判斷該項 被點擊過、具備錯誤信息 的狀況下,顯示錯誤消息<div>{errors.email}</div>。
    {touched.email && errors.email && <div>{errors.email}</div>}
    <input
      type="password"
      name="password"
      onChange={handleChange}
      onBlur={handleBlur}
      value={values.password}
    />
    {touched.password && errors.password && <div>{errors.password}</div>}
    
    <button type="submit" disabled={isSubmitting}>
      Submit
    </button>
  </form>
);

// Wrap our form with the using withFormik HoC
// 具體參數能夠看這裏
const MyForm = withFormik({
  // Transform outer props into form values
  // 將外部傳入props設爲表單的值,如 email: props.email 就可設置表單的值
  mapPropsToValues: props => ({ email: '', password: '' }),
  // Add a custom validation function (this can be async too!)
  // 在submit前會調用該函數對值進行檢查,若是errors爲{}則會執行handleSubmit
  validate: (values, props) => {
    const errors = {};
    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';
    }
    return errors;
  },
  // Submission handler
  // 傳入參數爲表單提交時的值與一個FormikBag對象,該對象具備傳入的props和method,具體能夠查看對應文檔描述
  handleSubmit: (
    values,
    {
      props,
      setSubmitting,
      setErrors /* setValues, setStatus, and other goodies */,
    }
  ) => {
    // 具體submit後執行函數,LoginToMyApp沒必要過多關心。
    // 示例就是進行了一個異步請求並對返回的結果作處理。
    LoginToMyApp(values).then(
      user => {
        setSubmitting(false);
        // do whatevs...
        // props.updateUser(user)
      },
      errors => {
        setSubmitting(false);
        // Maybe even transform your API's errors into the same shape as Formik's!
        setErrors(transformMyApiErrors(errors));
      }
    );
  },
})(InnerForm);

// Use <MyForm /> anywhere
const Basic = () => (
  <div>
    <h1>My Form</h1>
    <p>This can be anywhere in your application</p>
    <MyForm />
  </div>
);

export default Basic;

官方示例的使用方法比較清晰,咱們在該基礎上逐步修改便可。函數

掌握官方示例中涉及的使用方法

示例中,咱們能夠看出主要須要掌握的不多,const InnerForm僅僅是一個無狀態組件。const MyForm是經過HOC方法返回的一個表單組件。ui

const InnerForm = ({
    values,
    errors,
    touched,
    handleBlur,
    handleChange,
    handleSubmit,
    isSubmitting
}) => (<form></form>)

其中前三項values爲表單值、errors爲錯誤記錄、touched爲是否點擊過對應表單記錄。
而handleBlur和handleChange都是DOM-only的回調函數,接受傳入參數爲 Event 對象,若是咱們在表單中使用第三方組件,則須要用到另外兩個方法。

  1. setFieldValue(field: string, value: any, shouldValidate?: boolean)
  2. setFieldTouched(field: string, isTouched: boolean, shouldValidate?: boolean)

其中第三個參數是設置是否跳過驗證。

再看

const MyForm = withFormik({
    mapPropsToValues,
    validate,
    handleSubmit
})

經過HOC方法,實現了對InnerForm表單設置默認值、表單值驗證、表單提交三個方法。

  1. mapPropsToValues: props=>({...}),傳入組件props,返回InnerForm中的values對象
  2. validate: (values,props)=>({...}),傳入InnerForm提交的values對象與組件props,返回errors對象
  3. handleSubmit: (values,FormikBag)=>(void),傳入values對象與FormikBag對象,不需返回值

FormikBag對象屬性:

props
resetForm
setErrors
setFieldError
setFieldTouched
setFieldValue
setStatus
setSubmitting
setTouched
setValues

值得注意的是,FormikBag 不包含全部事件處理函數與errors,status,touchedFormikBag提供的是在表單submit後,對錶單的處理所需的函數,好比經過setFieldError函數,提供如 用戶名重複 之類的錯誤信息。

初步與antd-mobile組件進行組合

從上一步能夠得知,咱們主要的渲染部分放在了InnerForm組件中,因此此次咱們把目光放在InnerForm組件中便可。

該步驟使用到的antd-mobile組件爲:

首先寫入InnerForm組件中咱們須要的參數和基本骨架:

const InnerForm = ({
    values,
    errors,
    touched,
    handleBlur,
    setFieldTouched,
    setFieldValue,
    handleSubmit,
    isSubmitting
}) => (<form onSubmit={handleSubmit}>
    <WingBlank>
        <List>
        </List>
    </WingBlank>
</form>)

接下來咱們添加一個輸入框用於接收用戶輸入的email地址。
<List></List>中插入InputItem組件以下:

<List>
    <InputItem 
        onChange={(value)=>setFieldValue('email',value)}
        onBlur={()=>{setFieldTouched('email',true)}}
        value={values.email}
        touched={touched.email}
        errors={errors.email}
    />    
</List>

這裏值得注意,首先咱們看看InputItem組件的文檔,onChangeonBlur的默認參數都是string,而不是Event對象,在這裏咱們須要棄用官方文檔中的handleBlurhandleChange兩個回調參數,手動的調用setFieldValuesetFieldTouched來完成相應的效果。

爲組件設置錯誤提示

若是使用官方文檔使用的

{touched.password && errors.password && <div>{errors.password}</div>}

進行錯誤提醒,那麼重複的代碼工做量會略大了,在這裏咱們把錯誤提示作成一個組件。
具體代碼以下:

const MyErrorItem = props => 
    props.touched && props.errors ? (
        <List.Item style={{ backgroundColor: "#eee" }}>
            <span style={{ color: "red", fontSize: ".7rem"}}>* {props.errors}</span>
        </List.Item>
    ) : null;

那麼錯誤提醒的代碼就縮減成了

<MyErrorItem touched={touched.password} errors={errors.password} />

再次經過HOC咱們能夠將整個InputItem組件與MyErrorItem組件組合在一塊兒。

function HOCErrorInItem(NormalComponent, ErrorComponent) {
    return class HOCErrorFormItem extends React.Component {
        render(){
            return (<div>
                <NormalComponent {...this.props} />
                <ErrorComponent 
                    touched={this.props.touched}
                    errors={this.props.errors}
                 />
            </div>)
        }
    }
}

const MyInputItem = props =>(
    <InputItem
        type={props.type}
        name={props.name}
        onChange={value => props.onChange(value)}
        onBlur={props.onBlur}
        value={props.value}
    >
        {props.label}
    </InputItem>
)

const InputItemWithErrorTip = HOCErrorInItem(MyInputItem,MyErrorItem)

這樣咱們須要就封裝好了一個附帶錯誤提示的輸入框組件。

到這一步,咱們的InnerForm組件應該以下:

const InnerForm = ({
    values,
    errors,
    touched,
    handleBlur,
    setFieldTouched,
    setFieldValue,
    handleSubmit,
    isSubmitting
}) => (<form onSubmit={handleSubmit}>
    <WingBlank>
        <List>
            <InputItemWithErrorTip 
                type="email"
                name="Email"
                label="Email"
                onChange={value => setFieldValue("email", value)}
                onBlur={()=>{setFieldTouched('email',true)}}
                value={value.email}
                touched={touched.email}
                errors={errors.email}
            />
        </List>
    </WingBlank>
</form>)

該文上篇到此結束,此次主要是工做時候做爲記錄而言描寫如下本身的思路,畢竟好記性不如爛筆頭,也算是簡單梳理一下思路。
具體補充將在下篇完成,內容應該包括各種型表單組件封裝,工做完成後對封裝組件的反思,下篇完成時間儘可能在9月中完成,目前代碼仍然處於封裝各個組件的階段。
動筆時間也與上次相比相隔甚久了,感受本身仍是摸索路上,趁着工做壓力不大,多看多讀多想多寫吧。

=_=天天動工一點點,優先知足工做須要。
圖片描述

歡迎你們的一切快樂討論。

目前施工現場:
CodeSandBox
Github

相關文章
相關標籤/搜索