教學

開始之前

歡迎來到 Formik 教學。本教學將引導您學習在 React 中構建簡單和複雜表單所需的一切知識。

如果您迫不及待想在本地電腦上開始操作,請查看60 秒快速入門

我們要構建什麼?

在本教學中,我們將使用 React 和 Formik 構建一個複雜的電子報註冊表單。

您可以在此處查看我們將構建的內容:最終結果。 如果您看不懂程式碼,別擔心!本教學的目標是幫助您理解 Formik。

先決條件

您需要熟悉 HTML、CSS、現代 JavaScriptReact(以及 React Hooks),才能完全理解 Formik 及其運作方式。在本教學中,我們將使用箭頭函式letconst展開語法解構計算屬性名稱async/await。您可以使用 Babel REPL 來檢查 ES6 程式碼編譯後的結果。

教學設定

完成本教學有兩種方法:您可以在瀏覽器中編寫程式碼,也可以在電腦上設定本地開發環境。

設定選項 1:在瀏覽器中編寫程式碼

這是最快開始使用的方法!

首先,在新分頁中開啟此起始程式碼。新分頁應顯示一個電子郵件地址輸入欄位、一個提交按鈕和一些 React 程式碼。我們將在本教學中編輯 React 程式碼。

跳過第二個設定選項,直接前往概觀章節以了解 Formik 的概觀。

設定選項 2:本地開發環境

此步驟完全是可選的,本教學並不需要!

可選:使用您慣用的文字編輯器在本機進行操作的說明

此設定需要更多工作,但允許您使用您選擇的編輯器來完成教學。以下是步驟

  1. 確保您已安裝最新版本的 Node.js
  2. 按照 Create React App 的安裝說明 建立一個新專案。
npx create-react-app my-app
  1. 安裝 Formik
npm i formik

yarn add formik
  1. 刪除新專案 src/ 資料夾中的所有檔案

注意

**不要刪除整個 src 資料夾,只需刪除其中的原始程式碼檔案。**我們將在下一個步驟中用本專案的範例取代預設的程式碼檔案。

cd my-app
cd src
# If you’re using a Mac or Linux:
rm -f *
# Or, if you’re on Windows:
del *
# Then, switch back to the project folder
cd ..
  1. src/ 資料夾中新增一個名為 styles.css 的檔案,其中包含此 CSS 程式碼

  2. src/ 資料夾中新增一個名為 index.js 的檔案,其中包含此 JS 程式碼

現在在專案資料夾中執行 npm start,並在瀏覽器中開啟 https://#:3000。您應該會看到一個電子郵件輸入欄位和一個提交按鈕。

我們建議您按照這些說明為您的編輯器設定語法突顯。

救命,我卡住了!

如果您卡住了,請查看 Formik 的 GitHub 討論區。此外,Formium 社群 Discord 伺服器也是一個快速獲得幫助的好方法。如果您沒有收到答案,或者您仍然卡住,請提交 issue,我們會幫助您。

概觀:什麼是 Formik?

Formik 是一小组 React 組件和 hooks,用於在 React 和 React Native 中構建表單。它有助於處理三個最惱人的部分

  1. 獲取表單狀態的值
  2. 驗證和錯誤訊息
  3. 處理表單提交

Formik 將以上所有功能整合在一起,讓表單的測試、重構和邏輯推理變得輕而易舉,井然有序。

基礎知識

我們將從使用 Formik 最 *繁瑣* 的方式開始。雖然這看起來有點冗長,但了解 Formik 的建構方式非常重要,這樣您才能 volledig 掌握其功能,並建立完整的心智模型來理解其運作原理。

一個簡單的電子報註冊表單

假設我們想為部落格新增一個電子報註冊表單。首先,我們的表單只有一個名為 email 的欄位。使用 Formik,只需幾行程式碼即可完成。

import React from 'react';
import { useFormik } from 'formik';
const SignupForm = () => {
// Pass the useFormik() hook initial form values and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
email: '',
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};

我們將表單的 initialValues(初始值)和一個提交函式 (onSubmit) 傳遞給 useFormik() hook。然後,這個 hook 會將表單狀態和輔助方法的集合回傳給我們,我們將其儲存在名為 formik 的變數中。目前,我們只關心以下輔助方法:

  • handleSubmit:提交處理函式
  • handleChange:要傳遞給每個 <input><select><textarea> 的變更處理函式
  • values:我們表單的目前值

如上所示,我們將這些方法分別傳遞給它們各自的 props...就這樣!我們現在有了一個由 Formik 支援的功能表單。我們不再需要自行管理表單的值,也不需要為每個輸入框撰寫自定義事件處理函式,只需使用 useFormik() 即可。

這很簡潔,但只有一個輸入框時,使用 useFormik() 的好處不明顯。所以讓我們再新增兩個輸入框:一個用於使用者的名字和姓氏,我們將它們分別儲存為表單中的 firstNamelastName

import React from 'react';
import { useFormik } from 'formik';
const SignupForm = () => {
// Note that we have to initialize ALL of fields with values. These
// could come from props, but since we don’t want to prefill this form,
// we just use an empty string. If we don’t do this, React will yell
// at us.
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
value={formik.values.firstName}
/>
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};

如果您仔細觀察我們的新程式碼,您會注意到一些模式和對稱性正在 *形成*。

  1. 我們對每個 HTML 輸入框重複使用相同的變更處理函式 handleChange
  2. 我們傳遞一個與我們在 initialValues 中定義的屬性 *相符* 的 idname HTML 屬性
  3. 我們使用相同的名稱來存取欄位的值 (email -> formik.values.email)

如果您熟悉使用原生 React 建立表單,您可以將 Formik 的 handleChange 理解為以下這樣的工作方式:

const [values, setValues] = React.useState({});
const handleChange = event => {
setValues(prevValues => ({
...prevValues,
// we use the name to tell Formik which key of `values` to update
[event.target.name]: event.target.value
});
}

驗證

雖然我們的聯絡表單可以運作,但功能還不夠完整;使用者可以提交表單,但表單不會告知他們哪些欄位是必填的(如果有的話)。

如果我們可以使用瀏覽器內建的 HTML 輸入驗證,我們可以為每個輸入框新增一個 required 屬性,指定最小/最大長度(maxlengthminlength),和/或為每個輸入框新增一個 pattern 屬性以進行正規表達式驗證。如果可以的話,這些方法都很棒。然而,HTML 驗證有其局限性。首先,它只能在瀏覽器中運作!所以這顯然不適用於 React Native。其次,很難/不可能向使用者顯示自定義錯誤訊息。第三,它很不穩定。

如前所述,Formik 不僅會追蹤表單的 values(值),還會追蹤其驗證和錯誤訊息。要使用 JS 新增驗證,讓我們指定一個自定義驗證函式,並將其作為 validate 傳遞給 useFormik() hook。如果存在錯誤,這個自定義驗證函式應產生一個與我們的 values/initialValues 形狀相符的 error 物件。再次...*對稱性*...是的...

import React from 'react';
import { useFormik } from 'formik';
// A custom validation function. This must return an object
// which keys are symmetrical to our values/initialValues
const validate = values => {
const errors = {};
if (!values.firstName) {
errors.firstName = 'Required';
} else if (values.firstName.length > 15) {
errors.firstName = 'Must be 15 characters or less';
}
if (!values.lastName) {
errors.lastName = 'Required';
} else if (values.lastName.length > 20) {
errors.lastName = 'Must be 20 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';
}
return errors;
};
const SignupForm = () => {
// Pass the useFormik() hook initial form values, a validate function that will be called when
// form values change or fields are blurred, and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validate,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
value={formik.values.firstName}
/>
{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null}
<button type="submit">Submit</button>
</form>
);
};

formik.errors 是透過自定義驗證函式填入的。預設情況下,Formik 會在每次按鍵(變更事件)、每個輸入框的 blur 事件 以及提交之前進行驗證。我們傳遞給 useFormik()onSubmit 函式只有在沒有錯誤的情況下才會執行(即,如果我們的 validate 函式回傳 {})。

已訪問的欄位

雖然我們的表單可以運作,而且我們的使用者可以看到每個錯誤,但这對他們來說並不是一個很好的使用者體驗。由於我們的驗證函式會在每次按鍵時針對 *整個* 表單的 values 執行,因此我們的 errors 物件在任何時刻都包含 *所有* 驗證錯誤。在我們的元件中,我們只是檢查是否存在錯誤,然後立即將其顯示給使用者。這很尷尬,因為我們會顯示使用者甚至尚未訪問過的欄位的錯誤訊息。大多數情況下,我們只想在使用者 *完成* 在該欄位中輸入內容 *後* 才顯示該欄位的錯誤訊息。

errorsvalues 類似,Formik 會追蹤哪些欄位已被訪問。它將此資訊儲存在一個名為 touched 的物件中,該物件也反映了 values/initialValues 的形狀。touched 的鍵是欄位名稱,而 touched 的值是布林值 true/false

為了利用 touched,我們將 formik.handleBlur 傳遞給每個輸入框的 onBlur 屬性。這個函式的運作方式與 formik.handleChange 類似,它使用 name 屬性來判斷要更新哪個欄位。

import React from 'react';
import { useFormik } from 'formik';
const validate = values => {
const errors = {};
if (!values.firstName) {
errors.firstName = 'Required';
} else if (values.firstName.length > 15) {
errors.firstName = 'Must be 15 characters or less';
}
if (!values.lastName) {
errors.lastName = 'Required';
} else if (values.lastName.length > 20) {
errors.lastName = 'Must be 20 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';
}
return errors;
};
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validate,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null}
<button type="submit">Submit</button>
</form>
);
};

快完成了!現在我們正在追蹤 touched,我們可以將錯誤訊息渲染邏輯更改為 *僅* 在錯誤存在 *且* 使用者已訪問該欄位時才顯示該欄位的錯誤訊息。

import React from 'react';
import { useFormik } from 'formik';
const validate = values => {
const errors = {};
if (!values.firstName) {
errors.firstName = 'Required';
} else if (values.firstName.length > 15) {
errors.firstName = 'Must be 15 characters or less';
}
if (!values.lastName) {
errors.lastName = 'Required';
} else if (values.lastName.length > 20) {
errors.lastName = 'Must be 20 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';
}
return errors;
};
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validate,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};

使用 Yup 進行 Schema 驗證

如上所示,驗證由您決定。您可以隨意撰寫自己的驗證器或使用第三方輔助程式庫。Formik 的作者/其大部分使用者使用 Jason Quense 的程式庫 Yup 進行物件 schema 驗證。Yup 有一個類似於 JoiReact PropTypes 的 API,但也足夠小,可以在瀏覽器中使用,並且速度足夠快,可以在執行時使用。您可以使用此 REPL 在這裡試用它。

由於 Formik 作者/使用者非常 *喜歡* Yup,因此 Formik 有一個針對 Yup 的特殊設定屬性,稱為 validationSchema,它會自動將 Yup 的驗證錯誤訊息轉換為一個漂亮的物件,其鍵與 values/initialValues/touched 相符(就像任何自定義驗證函式一樣)。無論如何,您可以從 NPM/yarn 安裝 Yup,如下所示...

npm install yup --save
# or via yarn
yarn add yup

要了解 Yup 的運作方式,讓我們擺脫自定義驗證函式 validate,並使用 Yup 和 validationSchema 重新撰寫我們的驗證

import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
}),
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};

同樣,Yup 是 100% 可選的。但是,我們建議您嘗試一下。如上所示,我們只用 10 行程式碼就表達了完全相同的驗證函式,而不是 30 行。Formik 的核心設計原則之一是幫助您保持條理性。Yup 無疑在這方面提供了很大的幫助——schema 表達能力強,直觀(因為它們反映了您的值),並且可重複使用。無論您是否使用 Yup,我們都強烈建議您在應用程式中分享常用的驗證方法。這將確保通用欄位(例如電子郵件、街道地址、使用者名稱、電話號碼等)的驗證方式一致,並帶來更好的使用者體驗。

減少樣板程式碼

getFieldProps()

以上程式碼非常明確地展示了 Formik 的運作方式。onChange -> handleChangeonBlur -> handleBlur,依此類推。然而,為了節省您的時間,useFormik() 返回一個名為 formik.getFieldProps() 的輔助方法,以便更快地連接輸入欄位。給定一些欄位級別的資訊,它會返回給您一組針對特定欄位的 onChangeonBlurvaluechecked。然後,您可以將其展開應用於 inputselecttextarea

import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
}),
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
type="text"
{...formik.getFieldProps('firstName')}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input id="lastName" type="text" {...formik.getFieldProps('lastName')} />
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input id="email" type="email" {...formik.getFieldProps('email')} />
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};

利用 React Context

我們的程式碼再次非常明確地說明了 Formik 的運作方式。onChange -> handleChangeonBlur -> handleBlur,依此類推。然而,我們仍然必須手動將這個「屬性獲取器」getFieldProps() 傳遞給每個輸入欄位。為了節省您更多的時間,Formik 附帶了基於 React Context 的 API/元件,讓您的生活更輕鬆,程式碼更簡潔:<Formik /><Form /><Field /><ErrorMessage />。更明確地說,它們隱式地使用 React Context 來連接到父級 <Formik /> 的狀態/方法。

由於這些元件使用 React Context,我們需要在我們的樹狀結構中渲染一個包含表單狀態和輔助方法的 React Context Provider。如果您自己這樣做,它看起來會像這樣

import React from 'react';
import { useFormik } from 'formik';
// Create empty context
const FormikContext = React.createContext({});
// Place all of what’s returned by useFormik into context
export const Formik = ({ children, ...props }) => {
const formikStateAndHelpers = useFormik(props);
return (
<FormikContext.Provider value={formikStateAndHelpers}>
{typeof children === 'function'
? children(formikStateAndHelpers)
: children}
</FormikContext.Provider>
);
};

幸運的是,我們已經在 <Formik> 元件中為您完成了這項工作,它的工作原理與此相同。

現在讓我們將 useFormik() hook 換成 Formik 的 <Formik> 元件/渲染屬性。由於它是一個元件,我們會將傳遞給 useFormik() 的物件轉換為 JSX,每個鍵都將成為一個屬性。

import React from 'react';
import { Formik } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
return (
<Formik
initialValues={{ firstName: '', lastName: '', email: '' }}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
type="text"
{...formik.getFieldProps('firstName')}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
type="text"
{...formik.getFieldProps('lastName')}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input id="email" type="email" {...formik.getFieldProps('email')} />
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
)}
</Formik>
);
};

如您在上面看到的,我們將 useFormik() hook 換成了 <Formik> 元件。<Formik> 元件接受一個函式作為其子項(也就是 渲染屬性)。它的參數與 useFormik() 返回的物件完全相同(實際上,<Formik> 在內部調用了 useFormik()!)。因此,我們的表單與以前一樣工作,只是現在我們可以使用新的元件以更簡潔的方式表達自己。

import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
return (
<Formik
initialValues={{ firstName: '', lastName: '', email: '' }}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" />
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" />
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
};

預設情況下,<Field> 元件會渲染一個 <input> 元件,給定一個 name 屬性,它會隱式地抓取相應的 onChangeonBlurvalue 屬性並將它們傳遞給元素,以及您傳遞給它的任何屬性。然而,由於並非所有東西都是輸入欄位,<Field> 也接受其他一些屬性,讓您可以渲染任何您想要的東西。以下是一些例子。

// <input className="form-input" placeHolder="Jane" />
<Field name="firstName" className="form-input" placeholder="Jane" />
// <textarea className="form-textarea"/></textarea>
<Field name="message" as="textarea" className="form-textarea" />
// <select className="my-select"/>
<Field name="colors" as="select" className="my-select">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</Field>

React 的核心是組合,雖然我們已經減少了很多 屬性鑽取,但我們仍然在每個輸入欄位中重複使用 label<Field><ErrorMessage>。我們可以用抽象化做得更好!使用 Formik,您可以而且應該構建可重複使用的輸入欄位基本元件,並在您的應用程式中共享它們。事實證明,我們的 <Field> 渲染屬性元件有一個姐妹,她名叫 useField,她將會做同樣的事情,但透過 React Hooks!看看這個...

import React from 'react';
import ReactDOM from 'react-dom';
import { Formik, Form, useField } from 'formik';
import * as Yup from 'yup';
const MyTextInput = ({ label, ...props }) => {
// useField() returns [formik.getFieldProps(), formik.getFieldMeta()]
// which we can spread on <input>. We can use field meta to show an error
// message if the field is invalid and it has been touched (i.e. visited)
const [field, meta] = useField(props);
return (
<>
<label htmlFor={props.id || props.name}>{label}</label>
<input className="text-input" {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
const MyCheckbox = ({ children, ...props }) => {
// React treats radios and checkbox inputs differently from other input types: select and textarea.
// Formik does this too! When you specify `type` to useField(), it will
// return the correct bag of props for you -- a `checked` prop will be included
// in `field` alongside `name`, `value`, `onChange`, and `onBlur`
const [field, meta] = useField({ ...props, type: 'checkbox' });
return (
<div>
<label className="checkbox-input">
<input type="checkbox" {...field} {...props} />
{children}
</label>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
const MySelect = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<select {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
// And now we can use these
const SignupForm = () => {
return (
<>
<h1>Subscribe!</h1>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
acceptedTerms: false, // added for our checkbox
jobType: '', // added for our select
}}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string()
.email('Invalid email address')
.required('Required'),
acceptedTerms: Yup.boolean()
.required('Required')
.oneOf([true], 'You must accept the terms and conditions.'),
jobType: Yup.string()
.oneOf(
['designer', 'development', 'product', 'other'],
'Invalid Job Type'
)
.required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<MyTextInput
label="First Name"
name="firstName"
type="text"
placeholder="Jane"
/>
<MyTextInput
label="Last Name"
name="lastName"
type="text"
placeholder="Doe"
/>
<MyTextInput
label="Email Address"
name="email"
type="email"
placeholder="jane@formik.com"
/>
<MySelect label="Job Type" name="jobType">
<option value="">Select a job type</option>
<option value="designer">Designer</option>
<option value="development">Developer</option>
<option value="product">Product Manager</option>
<option value="other">Other</option>
</MySelect>
<MyCheckbox name="acceptedTerms">
I accept the terms and conditions
</MyCheckbox>
<button type="submit">Submit</button>
</Form>
</Formik>
</>
);
};

如您在上面看到的,useField() 使我們能夠將任何類型的 React 元件輸入連接到 Formik,就像它是 <Field> + <ErrorMessage> 一樣。我們可以用它來構建一組滿足我們需求的可重複使用的輸入欄位。

總結

恭喜!您已經使用 Formik 建立了一個註冊表單,它

  • 具有複雜的驗證邏輯和豐富的錯誤訊息
  • 在正確的時間(在使用者離開欄位焦點後)向使用者正確顯示錯誤訊息
  • 利用您自己的自定義輸入元件,您可以在應用程式的其他表單上使用

做得好!我們希望您現在對 Formik 的工作原理有了一定的了解。

在此處查看最終結果:最終結果

如果您有額外的時間或想練習您新的 Formik 技能,以下是一些您可以對註冊表單進行改進的想法,這些想法按難度遞增順序列出

  • 在使用者嘗試提交時禁用提交按鈕(提示:formik.isSubmitting
  • 使用 formik.handleReset<button type="reset"> 新增重置按鈕。
  • 根據 URL 查詢字串或傳遞給 <SignupForm> 的屬性預先填入 initialValues
  • 當欄位有錯誤且未獲得焦點時,將輸入框邊框顏色更改為紅色
  • 當欄位顯示錯誤且已被訪問時,為每個欄位新增抖動動畫
  • 將表單狀態保存到瀏覽器的 sessionStorage 中,以便在頁面重新整理之間保留表單進度

在本教學課程中,我們介紹了 Formik 的概念,包括表單狀態、欄位、驗證、hook、渲染屬性和 React context。有關每個主題的更詳細說明,請查看其餘的 文件。要了解有關定義教學課程中的元件和 hook 的更多資訊,請查看 API 參考

這個頁面有幫助嗎?

訂閱我們的電子報

最新的 Formik 新聞、文章和資源,將發送到您的收件箱。

版權所有 © 2020 Formium, Inc. 保留所有權利。