溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

使用Formik輕松開發(fā)更高質(zhì)量的React表單(四)其他幾個API解析

發(fā)布時間:2020-06-09 05:02:54 來源:網(wǎng)絡(luò) 閱讀:8553 作者:googlingman 欄目:web開發(fā)

舊話重提



前文中我特別提起Redux Form以及redux-form的問題,我覺得學(xué)習(xí)Formik你不得不提它們,當(dāng)然還有它們的「老祖宗」React;既然選擇了,那么你必須按照這個方向走下去。有一句叫作“沒有最好,只有更好”。這句話應(yīng)用于開源技術(shù)的學(xué)習(xí)上也很貼切,基于React技術(shù)的表單開發(fā),到底哪一種方案最好,相信國內(nèi)外很多高手都在探討這個問題。較早的redux-form這個自不必說了,如果你選擇而且熱戀著redux,那么很不難不注意redux-form,但是redux-form盡管在一定程度上簡化了基于redux的表單的開發(fā)但是也的確讓人覺得很辛苦,這一點從我自己追蹤學(xué)習(xí)redux-form來說就有深刻體會。而現(xiàn)在的Formik開發(fā)者也正是體會到了這種痛苦,于是基于前輩們的作者又開發(fā)了Formik,并提供口號“Build forms in React, without the tears”——能不能浪漫一些翻譯為“React表單不相信眼淚”?(受名片《莫斯科不相信眼淚》誘發(fā))。總之一句話,只有先了解了開發(fā)復(fù)雜React表單的痛苦你才會深刻體會學(xué)習(xí)的Formik的必要性。當(dāng)然,F(xiàn)ormik本身也很年輕,只有1歲,版本目前是1.0.2。但是,我相信這個庫會很快地發(fā)展下去,除非短時間內(nèi)出現(xiàn)了比Formik更為優(yōu)秀的React Form解決方案。

<Field />



<Field />會自動把表單中的輸入字段「加入」到Formik系統(tǒng)中。它使用name屬性匹配Formik中的狀態(tài)( state)。 <Field />會默認(rèn)對應(yīng)一個HTML的 <input />元素。復(fù)雜情形下,你可以改變這個底層元素——這可以通過指定此API的component屬性的方式實現(xiàn)(這些思路與redux-form都是一致的?。?。在此,component屬性值可以是一個簡單的字符串,如“ select”,也可能是另一個復(fù)雜的React組件。當(dāng)然, <Field /> 還擁有一個很重要的render屬性。
下面的代碼片斷給出了<Field />及其重要屬性(component屬性和render屬性)的典型應(yīng)用展示。

import React from 'react';
import { Formik, Field } from 'formik';

const Example = () => (
  <div>
    <h2>My Form</h2>
    <Formik
      initialValues={{ email: '', color: 'red', firstName: '' }}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          actions.setSubmitting(false);
        }, 1000);
      }}
      render={(props: FormikProps<Values>) => (
        <form onSubmit={props.handleSubmit}>
          <Field type="email" name="email" placeholder="Email" />
          <Field component="select" name="color">
            <option value="red">Red</option>
            <option value="green">Green</option>
            <option value="blue">Blue</option>
          </Field>
          <Field name="firstName" component={CustomInputComponent} />
          <Field
            name="lastName"
            render={({ field /* _form */ }) => (
              <input {...field} placeholder="firstName" />
            )}
          />
          <button type="submit">Submit</button>
        </form>
      )}
    />
  </div>
);

const CustomInputComponent: React.SFC<
  FieldProps<Values> & CustomInputProps
> = ({
  field, // { name, value, onChange, onBlur }
  form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
  ...props
}) => (
  <div>
    <input type="text" {...field} {...props} />
    {touched[field.name] &&
      errors[field.name] && <div className="error">{errors[field.name]}</div>}
  </div>
);

字段級校驗

思路是使用:validate?: (value: any) => undefined | string | Promise<any>

你可以通過把一個具有校驗功能的函數(shù)傳遞給validate屬性來執(zhí)行獨立的字段層面的校驗。此功能的觸發(fā)將會相應(yīng)于在 <Field>的父級組件 <Formik>中指定的validateOnBlur 和validateOnChange配置選項,或者在withFormik方法調(diào)用中通過props指定的validateOnBlur 和validateOnChange這兩個選項。當(dāng)然,校驗還會對應(yīng)下面兩種情形:

(1)同步情形下,如果校驗無效將返回一個包含錯誤信息的字符串或者干脆返回undefined。典型代碼如下:

// Synchronous validation for Field
const validate = value => {
  let errorMessage;
  if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
    errorMessage = 'Invalid email address';
  }
  return errorMessage;
};

(2)異步情形下,會返回一個能夠拋出包含錯誤信息的字符串異步的Promise對象。這種工作方式類似于Formik提供的validate函數(shù),但不是返回一個errors對象,而僅返回一個字符串。

請參考下面的代碼:

// Async validation for Field
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const validate = value => {
  return sleep(2000).then(() => {
    if (['admin', 'null', 'god'].includes(value)) {
      throw 'Nice try';
    }
  });
};

【注意】考慮到i18n庫(實現(xiàn)界面的多語言版本所需要)的使用方面,TypeScript檢驗類型比較寬松——允許你返回一個函數(shù)(例如i18n('invalid'))。

特別提示

當(dāng)你沒有使用定制組件而且你想訪問由<Field/>創(chuàng)建的底層的DOM結(jié)點(如調(diào)用focus)時,你可以通過把回調(diào)函數(shù)傳遞給innerRef屬性來實現(xiàn)。

<FieldArray />



<FieldArray />這個API本質(zhì)上是一個有助于實現(xiàn)字段數(shù)組或者列表操作的組件。你可以傳遞給它一個name屬性——使其指向包含對應(yīng)數(shù)組的values中的鍵所在路徑。于是,<FieldArray />可以讓你通過render這個prop訪問數(shù)組幫助方法(觀察下面代碼中的arrayHelpers)。 為了方便起見,調(diào)用這些方法就可以觸發(fā)校驗并能管理表單字段的touched信息。

import React from 'react';
import { Formik, Form, Field, FieldArray } from 'formik';

//下面提供的表單示例中有一個可編輯的列表。緊鄰每一個輸入字段是控制插入與刪除的按鈕。
//若列表為空,那么會顯示一個添加項目的按鈕。

export const FriendList = () => (
  <div>
    <h2>Friend List</h2>
    <Formik
      initialValues={{ friends: ['jared', 'ian', 'brent'] }}
      onSubmit={values =>
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
        }, 500)
      }
      render={({ values }) => (
        <Form>
          <FieldArray
            name="friends"
            render={arrayHelpers => (
              <div>
                {values.friends && values.friends.length > 0 ? (
                  values.friends.map((friend, index) => (
                    <div key={index}>
                      <Field name={`friends.${index}`} />
                      <button
                        type="button"
                        onClick={() => arrayHelpers.remove(index)} // remove a friend from the list
                      >
                        -
                      </button>
                      <button
                        type="button"
                        onClick={() => arrayHelpers.insert(index, '')} // insert an empty string at a position
                      >
                        +
                      </button>
                    </div>
                  ))
                ) : (
                  <button type="button" onClick={() => arrayHelpers.push('')}>
                    {/* show this when user has removed all friends from the list */}
                    Add a friend
                  </button>
                )}
                <div>
                  <button type="submit">Submit</button>
                </div>
              </div>
            )}
          />
        </Form>
      )}
    />
  </div>
);

name: string

這個屬性指向values中相關(guān)聯(lián)的鍵的名字或者路徑。

validateOnChange?: boolean

默認(rèn)值為true,用來決定在運行任何數(shù)組操作后執(zhí)行還是不執(zhí)行表單校驗。

FieldArray——針對對象數(shù)組的使用情形

你還可以遍歷一個對象數(shù)組——通過使用一種格式為object[index]property或者是object.index.property的方法,它們分別作為<FieldArray />中的 <Field /> 或者<input />元素的name屬性的值。請參考下面代碼:

<Form>
  <FieldArray
    name="friends"
    render={arrayHelpers => (
      <div>
        {values.friends.map((friend, index) => (
          <div key={index}>
            <Field name={`friends[${index}]name`} />
            <Field name={`friends.${index}.age`} /> // both these conventions do
            the same
            <button type="button" onClick={() => arrayHelpers.remove(index)}>
              -
            </button>
          </div>
        ))}
        <button
          type="button"
          onClick={() => arrayHelpers.push({ name: '', age: '' })}
        >
          +
        </button>
      </div>
    )}
  />
</Form>

FieldArray校驗陷阱

當(dāng)使用<FieldArray>時,進(jìn)行校驗有些值得注意的地方。

第一,如果你使用validationSchema,并且你的表單正好有數(shù)組校驗需求 (例如一個最小長度值),以及在嵌套數(shù)組字段需求情況下,顯示錯誤信息時也需要小心一些——Formik/Yup會在外部顯示校驗錯誤信息。例如:

const schema = Yup.object().shape({
  friends: Yup.array()
    .of(
      Yup.object().shape({
        name: Yup.string()
          .min(4, 'too short')
          .required('Required'), // these constraints take precedence
        salary: Yup.string()
          .min(3, 'cmon')
          .required('Required'), // these constraints take precedence
      })
    )
    .required('Must have friends') // these constraints are shown if and only if inner constraints are satisfied
    .min(3, 'Minimum of 3 friends'),
});

既然Yup和你的定制校驗函數(shù)總會輸出字符串形式的錯誤信息,那么你需要設(shè)法確定在顯示時是否你的嵌套錯誤信息是一個數(shù)組或者是一個字符串。
于是,為了顯示“Must have friends”和“Minimum of 3 friends”(這是我們示例中的數(shù)組校驗約束)......

不利之處

// within a `FieldArray`'s render
const FriendArrayErrors = errors =>
  errors.friends ? <div>{errors.friends}</div> : null; // app will crash

有利之處

// within a FieldArray's render

const FriendArrayErrors = errors =>
  typeof errors.friends === 'string' ? <div>{errors.friends}</div> : null;

對于嵌套的字段錯誤信息而言,你應(yīng)當(dāng)假定并沒有預(yù)告定義對象的哪一部分——除非你事先檢查了它。這樣一來,你可以創(chuàng)建一個定制的<ErrorMessage />組件來幫助實現(xiàn)你的校驗,此組件的代碼類似如下:

import { Field, getIn } from 'formik';

const ErrorMessage = ({ name }) => (
  <Field
    name={name}
    render={({ form }) => {
      const error = getIn(form.errors, name);
      const touch = getIn(form.touched, name);
      return touch && error ? error : null;
    }}
  />
);
//使用上面定制組件的情形:
<ErrorMessage name="friends[0].name" />; // => null, 'too short', or 'required'

【注意】在Formik v0.12 / 1.0中,支持把一個新的meta屬性添加給Field和FieldArray,此屬性用于為你提供類似于error和touch這樣的相關(guān)的元數(shù)據(jù)信息,這可以使你免于不得不使用Formik或者lodash的getIn方法來檢查是否你自己定義了路徑部分。

FieldArray幫助函數(shù)

下面的幫助函數(shù)可以經(jīng)由render這個屬性用來輔助操作字段數(shù)組:


  • push: (obj: any) => void: 把一個值添加到數(shù)組最后**

  • swap: (indexA: number, indexB: number) => void: 交換數(shù)組中的兩個值

  • move: (from: number, to: number) => void: 把數(shù)組中的一個元素從一個索引位置移動到另一個索引位置

  • insert: (index: number, value: any) => void: 在一個給定索引位置插入一個元素

  • unshift: (value: any) => number: 把一個元素添加到數(shù)組開始并返回新的數(shù)組長度

  • remove<T>(index: number): T | undefined: 刪除指定索引位置的一個元素并返回這個元素

  • pop<T>(): T | undefined: 刪除并返回數(shù)組尾端的元素**


FieldArray的render方法

有三種方式可以渲染<FieldArray />中包含的內(nèi)容。請參考下面的代碼:

  • <FieldArray name="..." component>

  • <FieldArray name="..." render>

情形一:render: (arrayHelpers: ArrayHelpers) => React.ReactNode

import React from 'react';
import { Formik, Form, Field, FieldArray } from 'formik'

export const FriendList = () => (
  <div>
    <h2>Friend List</h2>
    <Formik
      initialValues={{ friends: ['jared', 'ian', 'brent'] }}
      onSubmit={...}
      render={formikProps => (
        <FieldArray
          name="friends"
          render={({ move, swap, push, insert, unshift, pop }) => (
            <Form>
              {/*... use these however you want */}
            </Form>
          )}
        />
    />
  </div>
);

情況二:當(dāng)component屬性為React組件時

import React from 'react';
import { Formik, Form, Field, FieldArray } from 'formik'

export const FriendList = () => (
  <div>
    <h2>Friend List</h2>
    <Formik
      initialValues={{ friends: ['jared', 'ian', 'brent'] }}
      onSubmit={...}
      render={formikProps => (
        <FieldArray
          name="friends"
          component={MyDynamicForm}
        />
    />
  </div>
);

// 除去數(shù)組幫助函數(shù),F(xiàn)ormik中的狀態(tài)和它本身的幫助函數(shù)
// (例如values, touched, setXXX, etc)都是經(jīng)由表單的prop形式提供的
//
export const MyDynamicForm = ({
  move, swap, push, insert, unshift, pop, form
}) => (
 <Form>
  {/**  whatever you need to do */}
 </Form>
);

<Form />



類似于<Field />, <Form />其實也是一個幫助性質(zhì)的組件(helper component,用于簡化表單編寫并提高開發(fā)效率)。實際上,它是一個圍繞<form onSubmit={context.formik.handleSubmit} />實現(xiàn)的包裝器。這意味著,你不需要顯式地書寫<form onSubmit={props.handleSubmit} />——如果你不想干的話。

只圍繞ReactDOM的使用情形

import React from 'react';
import { Formik, Field, Form } from 'formik';

const Example = () => (
  <div>
    <h2>My Form</h2>
    <Formik
      initialValues={{ email: '', color: 'red' }}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          actions.setSubmitting(false);
        }, 1000);
      }}
      component={MyForm}
    />
  </div>
);

const MyForm = () => (
  <Form>
    <Field type="email" name="email" placeholder="Email" />
    <Field component="select" name="color">
      <option value="red">Red</option>
      <option value="green">Green</option>
      <option value="blue">Blue</option>
    </Field>
    <button type="submit">Submit</button>
  </Form>
);

withFormik(options)



此方法用于構(gòu)建一個高階React組件類,該類把props和form handlers (即"FormikBag")傳遞進(jìn)你的根據(jù)提供的選項生成的組件中。

重要options成員解析


(1)displayName?: string

在你的內(nèi)部表單組件是一個無狀態(tài)函數(shù)組件情況下,你可以使用displayName這個選項來給組件一個合適的名字——從而從React DevTools(調(diào)試工具,在我以前的博客中專門討論過)中可以更容易地觀察到它。如果指定了這個屬性,你的包裝表單中將顯示成這樣——Formik(displayName)。如果忽略這個屬性,顯示樣式為Formik(Component)。不過,這個選項對于類組件(例如class XXXXX extends React.Component {..})并不必要。

(2)enableReinitialize?: boolean

默認(rèn)為false。這個屬性用來控制當(dāng)包裝組件屬性變化(使用深度相等比較,using deep equality)時Formik是否應(yīng)當(dāng)復(fù)位表單。

(3)handleSubmit: (values: Values, formikBag: FormikBag) => void

這是表單提交處理器。其中的參照是描述你的表單的values對象,還有“FormikBag”。其中,F(xiàn)ormikBag:(a)包含一個對象,此對象擁有被注入的屬性和方法的子集(如所有類似于set<Thing>格式的方法,還有方法resetForm);(b)包含任何屬性——這些屬性都將被傳遞給包裝的組件。

(4)The "FormikBag":

  • props (傳遞給包裝組件的props)
  • resetForm
  • setErrors
  • setFieldError
  • setFieldTouched
  • setFieldValue
  • setStatus
  • setSubmitting
  • setTouched
  • setValues
    【注意】errors,touched,status及所有的事件處理器都沒有包含在FormikBag中。

(5)isInitialValid?: boolean | (props: Props) => boolean

默認(rèn)為 false. 此選擇用于控制表單加載前isValid屬性的初始值。你還可以傳遞一個函數(shù)。 Useful for situations when you want to enable/disable a submit and reset buttons on initial mount.

(6)mapPropsToValues?: (props: Props) => Values

If this option is specified, then Formik will transfer its results into updatable form state and make these values available to the new component as props.values. If mapPropsToValues is not specified, then Formik will map all props that are not functions to the inner component's props.values. That is, if you omit it, Formik will only pass props where typeof props[k] !== 'function', where k is some key.

Even if your form is not receiving any props from its parent, use mapPropsToValues to initialize your forms empty state.

(7)validate?: (values: Values, props: Props) => FormikErrors<Values> | Promise<any>

【注意】Formik作者極力推薦使用validationSchema與Yup進(jìn)行表單校驗。但是,就校驗這個任務(wù)而言,你可以任意選擇自己喜歡的直觀高效的校驗方案。

使用函數(shù)校驗表單的values對象。這個函數(shù)可能是下面兩種情形之一:

(A)同步函數(shù),并且返回一個對象errors。
// Synchronous validation
const validate = (values, props) => {
  let 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;
};

(B)異步函數(shù),它返回一個包含errors對象的Promise。

// Async Validation
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const validate = (values, props) => {
  return sleep(2000).then(() => {
    let errors = {};
    if (['admin', 'null', 'god'].includes(values.username)) {
      errors.username = 'Nice try';
    }
    // ...
    if (Object.keys(errors).length) {
      throw errors;
    }
  });
};

(8)validateOnBlur?: boolean

默認(rèn)為true。在blur事件觸發(fā)時(更具體一些說,是當(dāng)調(diào)用handleBlur,setFieldTouched或者是setTouched時)使用這個選項進(jìn)行校驗。

(9)validateOnChange?: boolean

默認(rèn)為true。 通過此選項告訴Formik在change事件或者相關(guān)方法(更具體一些說,是當(dāng)調(diào)用handleChange,setFieldValue或者setValues)觸發(fā)時進(jìn)行校驗。

(10)validationSchema?: Schema | ((props: Props) => Schema)

這個屬性極為重要,它定義了一個Yup模式( schema)或者是返回Yup模式的一個函數(shù)。在校驗是使用這個屬性是非常有用的。錯誤信息被映射到內(nèi)部組件的errors對象。它的鍵應(yīng)當(dāng)匹配values中對應(yīng)的鍵。

注入屬性與方法

這些與<Formik render={props => ...} />是一致的。

在Formik中使用connect()



connect()是一個高階組件,它用于把原始的Formik上下文以屬性方式(命名為formik)注入到內(nèi)部組件中。另外一個有趣的事實是:Formik在底層上也利用了connect()來封裝<Field/>,<FastField>和<Form>。因此,在開發(fā)定制組件時很有經(jīng)驗的程序員可能會發(fā)現(xiàn)connect()還是很有用的。請參考下面的代碼了解這個函數(shù)的基本用法:

import { connect } from 'formik';

const SubmitCount = ({ formik }) => <div>{formik.submitCount}</div>;

export default connect(SubmitCount);
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI