溫馨提示×

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

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

使用Formik輕松開發(fā)更高質(zhì)量的React表單(二)使用指南

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

一個(gè)基本的例子

設(shè)想你要開發(fā)一個(gè)可以編輯用戶數(shù)據(jù)的表單。不過,你的用戶API端使用了具有類似下面的嵌套對(duì)象表達(dá):

{
   id: string,
   email: string,
   social: {
     facebook: string,
     twitter: string,
     // ...
   }
}

最后,我們想使開發(fā)的對(duì)話框表單能夠接收下面幾個(gè)屬性(props):user,updateUser和onClose(顯然,user是一個(gè)對(duì)象,updateUser和onClose卻都是兩個(gè)方法)。

// User.js
import React from 'react';
import Dialog from 'MySuperDialog';
import { Formik } from 'formik';

const EditUserDialog = ({ user, updateUser, onClose }) => {
  return (
    <Dialog onClose={onClose}>
      <h2>Edit User</h2>
      <Formik
        initialValues={user /** { email, social } */}
        onSubmit={(values, actions) => {
          CallMyApi(user.id, values).then(
            updatedUser => {
              actions.setSubmitting(false);
              updateUser(updatedUser), onClose();
            },
            error => {
              actions.setSubmitting(false);
              actions.setErrors(transformMyAPIErrorToAnObject(error));
            }
          );
        }}
        render={({
          values,
          errors,
          touched,
          handleBlur,
          handleChange,
          handleSubmit,
          isSubmitting,
        }) => (
          <form onSubmit={handleSubmit}>
            <input
              type="email"
              name="email"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.email}
            />
            {errors.email && touched.email && <div>{errors.email}</div>}
            <input
              type="text"
              name="social.facebook"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.social.facebook}
            />
            {errors.social &&
              errors.social.facebook &&
              touched.facebook && <div>{errors.social.facebook}</div>}
            <input
              type="text"
              name="social.twitter"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.social.twitter}
            />
            {errors.social &&
              errors.social.twitter &&
              touched.twitter && <div>{errors.social.twitter}</div>}
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </form>
        )}
      />
    </Dialog>
  );
};

簡(jiǎn)化編碼

為了簡(jiǎn)化表單組件的編碼,F(xiàn)ormik還提供了兩個(gè)幫助API:


  • <Field>

  • <Form />


于是,下面的代碼與前面一致,只是使用<Form />和<Field />這兩個(gè)API進(jìn)行了改寫:

// EditUserDialog.js
import React from 'react';
import Dialog from 'MySuperDialog';
import { Formik, Field, Form } from 'formik';

const EditUserDialog = ({ user, updateUser, onClose }) => {
  return (
    <Dialog onClose={onClose}>
      <h2>Edit User</h2>
      <Formik
        initialValues={user /** { email, social } */}
        onSubmit={(values, actions) => {
          CallMyApi(user.id, values).then(
            updatedUser => {
              actions.setSubmitting(false);
              updateUser(updatedUser), onClose();
            },
            error => {
              actions.setSubmitting(false);
              actions.setErrors(transformMyAPIErrorToAnObject(error));
            }
          );
        }}
        render={({ errors, touched, isSubmitting }) => (
          <Form>
            <Field type="email" name="email" />
            {errors.email && touched.social.email && <div>{errors.email}</div>}
            <Field type="text" name="social.facebook" />
            {errors.social.facebook &&
              touched.social.facebook && <div>{errors.social.facebook}</div>}
            <Field type="text" name="social.twitter" />
            {errors.social.twitter &&
              touched.social.twitter && <div>{errors.social.twitter}</div>}
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </Form>
        )}
      />
    </Dialog>
  );
};

React Native開發(fā)問題


Formik與React Native 和React Native Web開發(fā)完全兼容。然而,由于ReactDOM和React Native表單處理與文本輸入方式的不同,有兩個(gè)區(qū)別值得注意。本文將介紹這個(gè)問題并推薦更佳使用方式。

在進(jìn)一步討論前,先來最簡(jiǎn)要地概括一下如何在React Native中使用Formik。下面的輪廓代碼展示了兩者的關(guān)鍵區(qū)別:

// Formik +React Native示例
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { withFormik } from 'formik';

const enhancer = withFormik({
  /*...*/
});

const MyReactNativeForm = props => (
  <View>
    <TextInput
      onChangeText={props.handleChange('email')}
      onBlur={props.handleBlur('email')}
      value={props.values.email}
    />
    <Button onPress={props.handleSubmit} title="Submit" />
  </View>
);

export default enhancer(MyReactNativeForm);

從上面代碼中,你會(huì)明顯注意到在React Native 和React DOM開發(fā)中使用Formik存在如下不同:


(1)Formik的props.handleSubmit被傳遞給一個(gè)<Button onPress={...} />,而不是HTML <form onSubmit={...} /> 組件(因?yàn)樵赗eact Native中沒有<form />元素)。

(2)<TextInput />使用Formik的props.handleChange(fieldName)和handleBlur(fieldName),而不是直接把回調(diào)函數(shù)賦值給props,因?yàn)槲覀儽仨殢哪程幍玫絝ieldName,而在ReactNative中我們無法你在Web中一樣自動(dòng)獲取它(使用input的name屬性)。作為可選方案,你還可以使用 setFieldValue(fieldName, value) 和setTouched(fieldName, bool) 這兩個(gè)函數(shù)。


避免在render中創(chuàng)建新函數(shù)

如果因某種原因你想在每一個(gè)render中避免創(chuàng)建新函數(shù),那么我建議你把React Native的 <TextInput /> 當(dāng)作它是一個(gè)第三方提供的定制輸入元素:


  • 編寫你自己的針對(duì)定制輸入元素的類包裝器;
  • 傳遞定制組件的props.setFieldValue,而不是傳遞props.handleChange;
  • 使用一個(gè)定制的change函數(shù)回調(diào),它將調(diào)用你傳遞給setFieldValue的任何內(nèi)容。

請(qǐng)參考下面的代碼:

// FormikReactNativeTextInput.js
import * as React from 'react';
import { TextInput } from 'react-native';

export default class FormikReactNativeTextInput extends React.Component {
  handleChange = (value: string) => {
    // remember that onChangeText will be Formik's setFieldValue
    this.props.onChangeText(this.props.name, value);
  };

  render() {
    // we want to pass through all the props except for onChangeText
    const { onChangeText, ...otherProps } = this.props;
    return (
      <TextInput
        onChangeText={this.handleChange}
        {...otherProps} // IRL, you should be more explicit when using TS
      />
    );
  }
}

然后,你可以像下面這樣使用這個(gè)定制輸入組件:

// MyReactNativeForm.js
import { View, Button } from 'react-native';
import TextInput from './FormikReactNativeTextInput';
import { Formik } from 'formik';

const MyReactNativeForm = props => (
  <View>
    <Formik
      onSubmit={(values, actions) => {
        setTimeout(() => {
          console.log(JSON.stringify(values, null, 2));
          actions.setSubmitting(false);
        }, 1000);
      }}
      render={props => (
        <View>
          <TextInput
            name="email"
            onChangeText={props.setFieldValue}
            value={props.values.email}
          />
          <Button title="submit" onPress={props.handleSubmit} />
        </View>
      )}
    />
  </View>
);

export default MyReactNativeForm;

使用TypeScript開發(fā)Formik表單

(一)TypeScript類型

Formik是使用TypeScript寫的,F(xiàn)ormik中的類型十分類似于React Router 4中的<Route>。

Render props (<Formik /> and <Field />)
import * as React from 'react';
import { Formik, FormikProps, Form, Field, FieldProps } from 'formik';

interface MyFormValues {
  firstName: string;
}

export const MyApp: React.SFC<{} /* whatever */> = () => {
  return (
    <div>
      <h2>My Example</h2>
      <Formik
        initialValues={{ firstName: '' }}
        onSubmit={(values: MyFormValues) => alert(JSON.stringify(values))}
        render={(formikBag: FormikProps<MyFormValues>) => (
          <Form>
            <Field
              name="firstName"
              render={({ field, form }: FieldProps<MyFormValues>) => (
                <div>
                  <input type="text" {...field} placeholder="First Name" />
                  {form.touched.firstName &&
                    form.errors.firstName &&
                    form.errors.firstName}
                </div>
              )}
            />
          </Form>
        )}
      />
    </div>
  );
};

(二)使用withFormik()

import React from 'react';
import * as Yup from 'yup';
import { withFormik, FormikProps, FormikErrors, Form, Field } from 'formik';

// Shape of form values
interface FormValues {
  email: string;
  password: string;
}

interface OtherProps {
  message: string;
}

順便提醒一下,你可以使用InjectedFormikProps<OtherProps, FormValues>來代替下面的實(shí)現(xiàn)方式。本質(zhì)上,它們是相同的,只不過InjectedFormikProps是當(dāng)Formik僅輸出一個(gè)HOC(高階組件)時(shí)的代替而已。而且,這個(gè)方法靈活性差一些,因?yàn)樗枰獙?duì)所有屬性(props)進(jìn)行包裝。

const InnerForm = (props: OtherProps & FormikProps<FormValues>) => {
  const { touched, errors, isSubmitting, message } = props;
  return (
    <Form>
      <h2>{message}</h2>
      <Field type="email" name="email" />
      {touched.email && errors.email && <div>{errors.email}</div>}

      <Field type="password" name="password" />
      {touched.password && errors.password && <div>{errors.password}</div>}

      <button type="submit" disabled={isSubmitting}>
        Submit
      </button>
    </Form>
  );
};

//MyForm接收的props的類型
interface MyFormProps {
  initialEmail?: string;
  message: string; // if this passed all the way through you might do this or make a union type
}

//使用withFormik高階組件包裝你的表單
const MyForm = withFormik<MyFormProps, FormValues>({
  // Transform outer props into form values
  mapPropsToValues: props => {
    return {
      email: props.initialEmail || '',
      password: '',
    };
  },

  //添加定制的校驗(yàn)函數(shù)(也有可能是異步的)
  validate: (values: FormValues) => {
    let errors: FormikErrors = {};
    if (!values.email) {
      errors.email = 'Required';
    } else if (!isValidEmail(values.email)) {
      errors.email = 'Invalid email address';
    }
    return errors;
  },

  handleSubmit: values => {
    // do submitting things
  },
})(InnerForm);

// 你可以在任何地方使用<MyForm />
const Basic = () => (
  <div>
    <h2>My App</h2>
    <p>This can be anywhere in your application</p>
    <MyForm message="Sign up" />
  </div>
);

export default Basic;

Formik表單提交原理


要在Formik中提交表單,你需要以某種方式觸發(fā) handleSubmit(e) 或者submitForm屬性調(diào)用(在Formik中這兩個(gè)方法都是以屬性的方式提供的)。 當(dāng)調(diào)用其中一個(gè)方法時(shí),F(xiàn)ormik每次都會(huì)執(zhí)行下面的偽代碼:

(一)預(yù)提交
(1)修改所有字段
(2)把isSubmitting 設(shè)置為true
(3)submitCount + 1
(二)校驗(yàn)
(1)把isValidating設(shè)置為true
(2)異步運(yùn)行所有字段級(jí)的校驗(yàn)和validationSchema,并深度合并執(zhí)行結(jié)果
(3)判斷是否存在錯(cuò)誤:
如果存在錯(cuò)誤:取消提交,把isValidating設(shè)置為false,設(shè)置錯(cuò)誤信息,并把isSubmitting設(shè)置為false
如果不存在錯(cuò)誤:Set isValidating to false, proceed to "Submission"
(三)提交
最后繼續(xù)運(yùn)行你的提交函數(shù)吧(例如是onSubmit或者h(yuǎn)andleSubmit)。你可以通過在你的處理器函數(shù)中調(diào)用setSubmitting(false) 來結(jié)束生命周期。

FAQ



(1)Q:怎么判定提交處理器(submission handler)正在執(zhí)行中?
A:當(dāng)isValidating為false且isSubmitting為true時(shí)。

(2)Q:為什么在提交前Formik要“潤(rùn)色一下(touch)”表單中所有字段?
A:通常,當(dāng)UI表單中輸入字段被操作過后(Formik中稱為“touched”)只顯示與之相關(guān)的錯(cuò)誤信息。于是,在提交一個(gè)表單前,F(xiàn)ormik會(huì)touch一下所有字段,這樣所有可能隱藏的錯(cuò)誤都會(huì)變得可見。

(3)Q:如何避免兩次重復(fù)提交?
A:辦法是當(dāng)isSubmitting為true時(shí),禁止所有能夠觸發(fā)提交的調(diào)用。

(4)Q:如何得知表單在提交前正在校驗(yàn)中?
A:如果isValidating為true而且isSubmitting也為true的話,......

向AI問一下細(xì)節(jié)

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

AI