讓你的React表單操作更優(yōu)雅(formik+yup)

表單操作在React中一直是一件比較繁瑣的事情,使用formik可以極大的簡化表單操作,formik的slogan就是Build forms in React, without the tears ??

formik

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值!瞒大!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末螃征,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子透敌,更是在濱河造成了極大的恐慌盯滚,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酗电,死亡現(xiàn)場(chǎng)離奇詭異魄藕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)顾瞻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門泼疑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荷荤,你說我怎么就攤上這事退渗。” “怎么了蕴纳?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵会油,是天一觀的道長。 經(jīng)常有香客問我古毛,道長翻翩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任稻薇,我火速辦了婚禮嫂冻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘塞椎。我一直安慰自己桨仿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布案狠。 她就那樣靜靜地躺著服傍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骂铁。 梳的紋絲不亂的頭發(fā)上吹零,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音拉庵,去河邊找鬼灿椅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阱扬。 我是一名探鬼主播泣懊,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼麻惶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起信夫,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤窃蹋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后静稻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體警没,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年振湾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杀迹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡押搪,死狀恐怖树酪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情大州,我是刑警寧澤续语,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站厦画,受9級(jí)特大地震影響疮茄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜根暑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一力试、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧排嫌,春花似錦畸裳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薇芝,卻和暖如春蓬抄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夯到。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工嚷缭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓阅爽,卻偏偏與公主長得像路幸,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子付翁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • React中沒有類似Angular那樣的雙向數(shù)據(jù)綁定百侧,在做一些表單復(fù)雜的后臺(tái)類頁面時(shí)砰识,監(jiān)聽、賦值佣渴、傳遞辫狼、校驗(yàn)時(shí)編碼...
    tedyuen777閱讀 9,856評(píng)論 1 23
  • 作為一個(gè)合格的開發(fā)者,不要只滿足于編寫了可以運(yùn)行的代碼辛润。而要了解代碼背后的工作原理膨处;不要只滿足于自己的程序...
    六個(gè)周閱讀 8,429評(píng)論 1 33
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對(duì)于 Vue 1.0 印象不深的內(nèi)容砂竖。關(guān)于...
    云之外閱讀 5,046評(píng)論 0 29
  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南真椿,這只是我在學(xué)習(xí)過程中的一些閱讀筆記,個(gè)人覺得該教程講解深入淺出晦溪,比目前大...
    leonaxiong閱讀 2,813評(píng)論 1 18
  • 版本:Angular 5.0.0-alpha 表單是商業(yè)應(yīng)用的支柱瀑粥,我們用它來執(zhí)行登錄、求助三圆、下單狞换、預(yù)訂機(jī)票、安排...
    soojade閱讀 1,270評(píng)論 0 1