表單操作在React中一直是一件比較繁瑣的事情,使用formik可以極大的簡化表單操作,formik的slogan就是
Build forms in React, without the tears ??
formik介紹
在介紹formik之前,我們先來說說日常操作form表單中的一些痛點(diǎn)鹊漠。
當(dāng)我們想要訪問表單中的input控件的值時(shí)愧驱,通常有兩種操作派桩,一種是使用ref去得到dom結(jié)構(gòu)泼差,第二種是使用state存儲(chǔ)value贵少。首先,針對(duì)于第一種情況堆缘,雖然使用ref能夠讓我們很方便的獲取到dom節(jié)點(diǎn)春瞬,進(jìn)而獲取input的值,但是需要注意的是當(dāng)我們?cè)谑褂肦eact的時(shí)候套啤,我們需要時(shí)刻告訴自己,不要直接操作dom随常!不要直接操作dom潜沦!不要直接操作dom!官方也是不推薦該做法的绪氛; 對(duì)于第二種做法唆鸡,雖然這是官方推薦的做法,當(dāng)時(shí)由于綁定了state枣察,所以form表單中的控件也變成了受控組件争占,如果我們想真正改動(dòng)其數(shù)據(jù)燃逻,我們往往需要再綁定一個(gè)onChange事件,當(dāng)控件過多時(shí)臂痕,操作也是非常繁瑣的伯襟。
而formik的出現(xiàn),將表單操作化繁為簡握童,使得表單操作變得非常的簡單姆怪。formik不僅提供了針對(duì)的表單的狀態(tài)管理,還能很方便的對(duì)表單做規(guī)則校驗(yàn)澡绩。其實(shí)formik的原理很簡單:它內(nèi)置了表單的state管理操作稽揭,無需我們?cè)趩为?dú)為表單建立state,同時(shí)使用了Context肥卡,能夠讓表單組件多層嵌套溪掀,不再需要我們一層層傳遞。
入門例子
我們以一個(gè)簡單的例子來看看formik到底是如何使用的步鉴,它又怎樣簡化了表單的操作揪胃。
我們?cè)摫韱巫鳛槔樱韱伟诵彰肱眩詣e只嚣,年齡三個(gè)字段,一個(gè)提交按鈕艺沼,給定表單規(guī)則如下:
- 姓名(name): 必填非空
- 性別(gender): 必選非空
- 年齡(age):必填非空册舞,大于0的整數(shù)
formik提供了一個(gè)名為Formik
的組件,組件包含了initialValues障般,validate,onSubmit,render等props(不限于此调鲸,列舉出來的只是當(dāng)前用到的關(guān)鍵的props,完整結(jié)構(gòu)詳解Formik API挽荡,下面解釋一個(gè)各個(gè)prop作用:
- initialValues:初始化表單的數(shù)據(jù)
- validate:表單規(guī)則校驗(yàn)藐石,返回一個(gè)錯(cuò)誤信息的對(duì)象
- onSubmit: 獲得表單各個(gè)字段,自定義自己的業(yè)務(wù)操作
- render: 表單內(nèi)部的children組件
針對(duì)內(nèi)部的表單定拟,render方法會(huì)傳入一個(gè)props于微,props包括了values,errors青自,handleSubmit株依,handleChange,handleBlur等屬性(更詳細(xì)的內(nèi)容參見完整props延窜,其中:
- values:對(duì)應(yīng)的就是表單各項(xiàng)數(shù)據(jù)
- errors:validate所返回的表單校驗(yàn)后的錯(cuò)誤信息
- touched:表示表單內(nèi)的控件是否已經(jīng)被操作過
- handleSubmit:是表單觸發(fā)提交事件后的formik對(duì)提交流程處理事件恋腕,規(guī)則校驗(yàn)就在其中
- handleChane: 更改表單數(shù)據(jù)的事件
- handleBlur: 控件失去焦點(diǎn)后的操作事件,會(huì)進(jìn)行對(duì)表單進(jìn)行規(guī)則校驗(yàn)的流程
示例代碼如下:
<Formik
initialValues={{
name: "",
gender: "",
age: ""
}}
validate={values => {
let errors = {};
if (values.name.length === 0) {
errors.name = "Name can not be empty"
}
if (values.gender.length === 0) {
errors.gender = "You must choose a gender"
}
if (values.age <= 0 || values.age % 1 !== 0) {
errors.age = "Age must be a Positive Integer"
}
return errors;
}}
onSubmit={(values) => {
console.log(values)
}}
render={props =>
<form onSubmit={props.handleSubmit}>
<div className="content">
<div>
<label>姓名: </label><input type="text" id="name" name="name" value={props.values.name}
onChange={props.handleChange} onBlur={props.handleBlur}/>
{props.touched.name && props.errors.name && <div>{props.errors.name}</div>}
</div>
<div>
<label>性別: </label>
<input type="radio" id="male" value="male" name="gender" onChange={props.handleChange}
onBlur={props.handleBlur}/> <label htmlFor="male">男</label>
<input type="radio" id="female" value="female" name="gender" onChange={props.handleChange}
onBlur={props.handleBlur}/> <label htmlFor="female">女</label>
{props.touched.gender && props.errors.gender && <div>{props.errors.gender}</div>}
</div>
<div>
<label>年齡: </label><input type="number" id="age" name="age" value={props.values.age}
onChange={props.handleChange} onBlur={props.handleBlur}/>
{props.touched.age && props.errors.age && <div>{props.errors.age}</div>}
</div>
<div className="submit-area">
<button type="submit">提交</button>
</div>
</div>
</form>
}
/>
注意:在輸出錯(cuò)誤信息的時(shí)候逆瑞,通常有
touched.name&&errors.name
的判斷荠藤,這是因?yàn)閒ormik在做表單校驗(yàn)的時(shí)候伙单,會(huì)將全字段都檢查一遍,不管用戶有沒有操作哈肖,但是對(duì)于用戶來說吻育,通常不希望在還沒有操作的字段顯示一行錯(cuò)誤信息,所以需要touched字段查看該字段是否已經(jīng)操作過牡彻,操作過并且有錯(cuò)誤扫沼,才顯示。
通過例子可以看出庄吼,通過Formik組件傳入初始字段缎除,校驗(yàn)規(guī)則,提交事件总寻;并在表單中綁定上handleSubmit器罐,handleChange,handleBlur等事件后渐行,我們就能夠?qū)崿F(xiàn)一個(gè)簡單表單操作轰坊。
但是我們依舊可以看出在每一個(gè)輸入控件的地方,都重復(fù)的綁定了onChange祟印,onBlur等事件肴沫,并且每個(gè)輸入錯(cuò)誤信息的方式都是很重復(fù)的,這顯得有點(diǎn)累贅蕴忆。但是好在formik提交了Form颤芬,F(xiàn)ield,ErrorMessage等封裝好的組件套鹅,用于減少重復(fù)的工作站蝠,代碼如下:
<Formik
......
//same as before
render={props =>
<Form>
<div className="content">
<div>
<label>姓名: </label><Field name="name"/>
<ErrorMessage name="name"/>
</div>
<div>
<label>性別: </label>
<Field name="gender" render={({field}) =>
<input type="radio" name={field.name} value="male" checked={field.value === "male"}
onChange={field.onChange}
onBlur={field.onBlur}/>
}/>
<label htmlFor="male">男</label>
<Field name="gender" render={({field}) =>
<input type="radio" name={field.name} value="female" checked={field.value === "female"}
onChange={field.onChange}
onBlur={field.onBlur}/>
}/> <label htmlFor="female">女</label>
<ErrorMessage name="gender"/>
</div>
<div>
<label>年齡: </label><Field name="age"/>
<ErrorMessage name="age"/>
</div>
<div className="submit-area">
<button type="submit">提交</button>
</div>
</div>
</Form>
}
/>
可以看出,使用formik提供的組件還能進(jìn)一步簡化代碼卓鹿,通過對(duì)比可以看出菱魔,F(xiàn)orm組件想到與form加上onSubmit;ErrorMessage組件就是將touched,errors等一系列判斷包裹了起來吟孙;Field組件則是封裝了onChange澜倦,onBlur,value等屬性杰妓,需要注意的是對(duì)于radio肥隆,checkbox,select等控件需要單獨(dú)做一些特殊處理稚失,通常使用其render方法來自定義,除此之外恰聘,因?yàn)橛辛藃ender方法句各,使得Field也能很好的和其他的第三方封裝的控件結(jié)合吸占。
此外,formik針對(duì)數(shù)組類型屬性也有很好的支持凿宾,現(xiàn)在讓我們?cè)诒韱卫锩婕右粋€(gè)字段矾屯,表單樣式如下:
我們?yōu)楸韱翁砑恿艘粋€(gè)addresses字段,它是一個(gè)存儲(chǔ)地址的數(shù)據(jù)初厚,它的驗(yàn)證規(guī)則是至少有一個(gè)非空的字段件蚕。
formik提供了一個(gè)FieldArray的組件專門處理數(shù)組類型字段,代碼如下:
<Formik
initialValues={{
name: "",
gender: "",
age: "",
addresses: [""]
}}
validate={values => {
let errors = {};
if (values.name.length === 0) {
errors.name = "Name can not be empty"
}
if (values.gender.length === 0) {
errors.gender = "You must choose a gender"
}
if (values.age <= 0 || values.age % 1 !== 0) {
errors.age = "Age must be a Positive Integer"
}
let addresses = values.addresses.filter(address => address.trim() !== "");
if (addresses.length === 0) {
errors.addresses = "Addresses must have 1 address at least"
}
return errors;
}}
onSubmit={(values) => {
console.log(values)
}}
render={props =>
<Form>
<div className="content">
......
// same as before
<div>
<label>地址: </label>
<FieldArray name="addresses" render={arrayHelper =>
<div>
{props.values.addresses.map((address, index) =>
<div className="address">
<Field name={`addresses.${index}`}/>
<div className="address_add" onClick={() => {
arrayHelper.push("")
}}>+
</div>
</div>
)}
</div>
}/>
<ErrorMessage name="addresses" component="div" className="error"/>
</div>
<div className="submit-area">
<button type="submit">提交</button>
</div>
</div>
</Form>
}
/>
可以看到产禾,使用FieldArray時(shí)排作,它會(huì)提供一個(gè)arrayHelper的參數(shù),我們可以通過arrayHelper很方便去操作數(shù)組的數(shù)組亚情,如push妄痪,pop等等(更詳細(xì)的參見完整arrayHelper API)
到此為止,就是formik的基本功能的介紹了楞件。
注入Yup
雖然formik已經(jīng)極大的方便了表單的操作衫生,但是我們還是可以看到在做表單驗(yàn)證的時(shí)候,寫的代碼還是比較繁瑣土浸,這個(gè)時(shí)候就需要yup出場(chǎng)了罪针,yup的專長就是做規(guī)則校驗(yàn)。并且聰明的formik作者在設(shè)計(jì)formik的時(shí)候就讓其很好的支持yup了黄伊。
yup的官方介紹:
Yup is a JavaScript object schema validator and object parser.
在Formik組件里有一個(gè)名為validationSchema的prop泪酱,這個(gè)就是為yup留出的接口。
話不多說毅舆,先show代碼:
//沒有yup的情況
<Formik
validate={values => {
let errors = {};
if (values.name.length === 0) {
errors.name = "Name can not be empty"
}
if (values.gender.length === 0) {
errors.gender = "You must choose a gender"
}
if (values.age <= 0 || values.age % 1 !== 0) {
errors.age = "Age must be a Positive Integer"
}
let addresses = values.addresses.filter(address => address.trim() !== "");
if (addresses.length === 0) {
errors.addresses = "Addresses must have 1 address at least"
}
return errors;
}}
//......
/>
//使用yup的情況
<Formik
validationSchema={Yup.object().shape({
name: Yup.string().trim().required("Name can not be empty"),
gender: Yup.string().required("You must choose a gender"),
age: Yup.number().moreThan(0, "Age must be greater than 0").integer("Age must be a integer"),
addresses: Yup.array().compact().min(1, "Addresses must have 1 address at least")
})}
//......
/>
可以看到使用Yup后西篓,不管是從代碼量,還是可讀性來看都提交了很多憋活,尤其是在表單非常復(fù)雜時(shí)岂津,這之間的優(yōu)化會(huì)非常的明顯。
可以看到Y(jié)up的操作非常的語義化悦即,所以學(xué)習(xí)成本非常的低吮成,你只需要知道它的api,就能很輕易的使用它辜梳,所以對(duì)Yup的使用不再贅述粱甫,詳細(xì)信息參見Yup文檔。
結(jié)語
本文也只是對(duì)formik+yup做了簡單的入門介紹作瞄,只做拋磚引玉茶宵。其實(shí)他們的厲害之處不僅于此,大家可以盡情的探索宗挥。
如想查看源碼點(diǎn)擊表單代碼鏈接
如果對(duì)本文有什么意見和建議乌庶,歡迎討論和指正V值!瞒大!