您好,登錄后才能下訂單哦!
前文中我特別提起Redux Form以及redux-form的問題,我覺得學習Formik你不得不提它們,當然還有它們的「老祖宗」React;既然選擇了,那么你必須按照這個方向走下去。有一句叫作“沒有最好,只有更好”。這句話應用于開源技術的學習上也很貼切,基于React技術的表單開發,到底哪一種方案最好,相信國內外很多高手都在探討這個問題。較早的redux-form這個自不必說了,如果你選擇而且熱戀著redux,那么很不難不注意redux-form,但是redux-form盡管在一定程度上簡化了基于redux的表單的開發但是也的確讓人覺得很辛苦,這一點從我自己追蹤學習redux-form來說就有深刻體會。而現在的Formik開發者也正是體會到了這種痛苦,于是基于前輩們的作者又開發了Formik,并提供口號“Build forms in React, without the tears”——能不能浪漫一些翻譯為“React表單不相信眼淚”?(受名片《莫斯科不相信眼淚》誘發)。總之一句話,只有先了解了開發復雜React表單的痛苦你才會深刻體會學習的Formik的必要性。當然,Formik本身也很年輕,只有1歲,版本目前是1.0.2。但是,我相信這個庫會很快地發展下去,除非短時間內出現了比Formik更為優秀的React Form解決方案。
<Field />會自動把表單中的輸入字段「加入」到Formik系統中。它使用name屬性匹配Formik中的狀態( state)。 <Field />會默認對應一個HTML的 <input />元素。復雜情形下,你可以改變這個底層元素——這可以通過指定此API的component屬性的方式實現(這些思路與redux-form都是一致的!)。在此,component屬性值可以是一個簡單的字符串,如“ select”,也可能是另一個復雜的React組件。當然, <Field /> 還擁有一個很重要的render屬性。
下面的代碼片斷給出了<Field />及其重要屬性(component屬性和render屬性)的典型應用展示。
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>
你可以通過把一個具有校驗功能的函數傳遞給validate屬性來執行獨立的字段層面的校驗。此功能的觸發將會相應于在 <Field>的父級組件 <Formik>中指定的validateOnBlur 和validateOnChange配置選項,或者在withFormik方法調用中通過props指定的validateOnBlur 和validateOnChange這兩個選項。當然,校驗還會對應下面兩種情形:
// 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;
};
請參考下面的代碼:
// 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庫(實現界面的多語言版本所需要)的使用方面,TypeScript檢驗類型比較寬松——允許你返回一個函數(例如i18n('invalid'))。
當你沒有使用定制組件而且你想訪問由<Field/>創建的底層的DOM結點(如調用focus)時,你可以通過把回調函數傳遞給innerRef屬性來實現。
<FieldArray />這個API本質上是一個有助于實現字段數組或者列表操作的組件。你可以傳遞給它一個name屬性——使其指向包含對應數組的values中的鍵所在路徑。于是,<FieldArray />可以讓你通過render這個prop訪問數組幫助方法(觀察下面代碼中的arrayHelpers)。 為了方便起見,調用這些方法就可以觸發校驗并能管理表單字段的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>
);
這個屬性指向values中相關聯的鍵的名字或者路徑。
默認值為true,用來決定在運行任何數組操作后執行還是不執行表單校驗。
你還可以遍歷一個對象數組——通過使用一種格式為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>時,進行校驗有些值得注意的地方。
第一,如果你使用validationSchema,并且你的表單正好有數組校驗需求 (例如一個最小長度值),以及在嵌套數組字段需求情況下,顯示錯誤信息時也需要小心一些——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和你的定制校驗函數總會輸出字符串形式的錯誤信息,那么你需要設法確定在顯示時是否你的嵌套錯誤信息是一個數組或者是一個字符串。
于是,為了顯示“Must have friends”和“Minimum of 3 friends”(這是我們示例中的數組校驗約束)......
// 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;
對于嵌套的字段錯誤信息而言,你應當假定并沒有預告定義對象的哪一部分——除非你事先檢查了它。這樣一來,你可以創建一個定制的<ErrorMessage />組件來幫助實現你的校驗,此組件的代碼類似如下:
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這樣的相關的元數據信息,這可以使你免于不得不使用Formik或者lodash的getIn方法來檢查是否你自己定義了路徑部分。
下面的幫助函數可以經由render這個屬性用來輔助操作字段數組:
有三種方式可以渲染<FieldArray />中包含的內容。請參考下面的代碼:
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>
);
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>
);
// 除去數組幫助函數,Formik中的狀態和它本身的幫助函數
// (例如values, touched, setXXX, etc)都是經由表單的prop形式提供的
//
export const MyDynamicForm = ({
move, swap, push, insert, unshift, pop, form
}) => (
<Form>
{/** whatever you need to do */}
</Form>
);
類似于<Field />, <Form />其實也是一個幫助性質的組件(helper component,用于簡化表單編寫并提高開發效率)。實際上,它是一個圍繞<form onSubmit={context.formik.handleSubmit} />實現的包裝器。這意味著,你不需要顯式地書寫<form onSubmit={props.handleSubmit} />——如果你不想干的話。
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>
);
此方法用于構建一個高階React組件類,該類把props和form handlers (即"FormikBag")傳遞進你的根據提供的選項生成的組件中。
在你的內部表單組件是一個無狀態函數組件情況下,你可以使用displayName這個選項來給組件一個合適的名字——從而從React DevTools(調試工具,在我以前的博客中專門討論過)中可以更容易地觀察到它。如果指定了這個屬性,你的包裝表單中將顯示成這樣——Formik(displayName)。如果忽略這個屬性,顯示樣式為Formik(Component)。不過,這個選項對于類組件(例如class XXXXX extends React.Component {..})并不必要。
默認為false。這個屬性用來控制當包裝組件屬性變化(使用深度相等比較,using deep equality)時Formik是否應當復位表單。
這是表單提交處理器。其中的參照是描述你的表單的values對象,還有“FormikBag”。其中,FormikBag:(a)包含一個對象,此對象擁有被注入的屬性和方法的子集(如所有類似于set<Thing>格式的方法,還有方法resetForm);(b)包含任何屬性——這些屬性都將被傳遞給包裝的組件。
默認為 false. 此選擇用于控制表單加載前isValid屬性的初始值。你還可以傳遞一個函數。 Useful for situations when you want to enable/disable a submit and reset buttons on initial mount.
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.
【注意】Formik作者極力推薦使用validationSchema與Yup進行表單校驗。但是,就校驗這個任務而言,你可以任意選擇自己喜歡的直觀高效的校驗方案。
使用函數校驗表單的values對象。這個函數可能是下面兩種情形之一:
(A)同步函數,并且返回一個對象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)異步函數,它返回一個包含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;
}
});
};
默認為true。在blur事件觸發時(更具體一些說,是當調用handleBlur,setFieldTouched或者是setTouched時)使用這個選項進行校驗。
默認為true。 通過此選項告訴Formik在change事件或者相關方法(更具體一些說,是當調用handleChange,setFieldValue或者setValues)觸發時進行校驗。
這個屬性極為重要,它定義了一個Yup模式( schema)或者是返回Yup模式的一個函數。在校驗是使用這個屬性是非常有用的。錯誤信息被映射到內部組件的errors對象。它的鍵應當匹配values中對應的鍵。
這些與<Formik render={props => ...} />是一致的。
connect()是一個高階組件,它用于把原始的Formik上下文以屬性方式(命名為formik)注入到內部組件中。另外一個有趣的事實是:Formik在底層上也利用了connect()來封裝<Field/>,<FastField>和<Form>。因此,在開發定制組件時很有經驗的程序員可能會發現connect()還是很有用的。請參考下面的代碼了解這個函數的基本用法:
import { connect } from 'formik';
const SubmitCount = ({ formik }) => <div>{formik.submitCount}</div>;
export default connect(SubmitCount);
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。