由於昨天討論 React Hook Form 的過程中提到,若要做檢核(validation)動作,可以透過 Yup 這個 library 來處理,那可能就會有疑問了:
於是今天算一個小章節,一起來討論 Yup 這項工具!
即便我們手上有一些像 Redux Form、React Hook Form 之類的工具,可以有效管理表單欄位,並且在按下「Submit」同時,將所有欄位都蒐集到手上,準備送到後端。
但。。。中間是不是漏了什麼?
如果使用者輸入的帳號或密碼少於六位數?
如果使用者硬是在「金額」的欄位輸入中文?
甚至,如果駭客想要透過輸入欄位 inject 一些壞壞的程式碼?
是的,在「Submit」與「實際送出」之間,還需要加入一個「檢核」的流程。
在還沒有像 Yup 這樣用來檢核的工具之前,檢核是相當土法煉鋼的,比如像 Redux Form 的 submit validation 就會出現類似這樣的 code:
// values 代表 Redux Form 幫你把所有欄位的數值集中到一個 object
const validate = values => {
// errors 放入 key-value pair(錯誤的欄位 key,以及對應的錯誤訊息 value)
const errors = {};
if (!values.username) {
errors.username = '必填欄位'
} else if (values.username.length > 15) {
errors.username = '至多 15 位字元'
}
if (!values.email) {
errors.email = '必填欄位'
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = '未符合 email 格式'
}
if (!values.age) {
errors.age = '必填欄位'
} else if (isNaN(Number(values.age))) {
errors.age = '必須為數字'
} else if (Number(values.age) > 0) {
errors.age = '數字需大於 0'
}
return errors
}
可以看到邏輯是複雜且難以閱讀的,不僅充斥著 if-else,也會出現許多類似的判斷。
Yup is a schema builder for runtime value parsing and validation.
schema 可以說是對於「資料格式」與「數值」的一種描述架構,透過事先定義 schema,就像先訂好規則,然後要求 form 表單的資料要能夠匹配 schema,而 Yup 就是幫助我們整個這兩者的工具之一。
同樣是上面那個 username
、email
、age
的範例,schema 的版本會像是這樣:
const userSchema = object({
// String 格式,最多 15 個字元,必填
username: string().max(15).required(),
// String 格式,符合 email 格式,必填
email: string().email().required(),
// Number 格式,大於 0,整數,必填
age: number().positive().integer().required(),
});
比起上面的範例,是否簡潔好讀了許多?甚至不需要註解也能懂!
欄位的資料類型,基本上該有的都有了:
yup.string()
yup.number()
yup.boolean()
yup.date()
yup.object()
yup.array()
而最方便的則是,一些很基本的檢核,比如是否為 null,數字大小區間,陣列至少包含一項等,通通都用淺顯易懂,且可以串接(chainable)的方式提供:
Schema.nullable()
Schema.required()
Schema.typeError()
Schema.oneOf()
number.min()
number.max()
當然,即便面對一些更複雜的檢核條件,Yup 也都提供相對應的 API 來處理:
.when()
.test()
.ref()
.validate()
.test()
「沉默地出錯」是 yup 較令人詬病的一點,因為本身的錯誤處理已經被用來當作 schema validation 的錯誤訊息,反而導致 yup 內的程式碼出錯時,不會有任何反應,尤其容易發生在 .when()
或者 .test()
這種客製化邏輯的地方,建議都需要用 try-catch 包起來比較保險。
市面上支援 JavaScript 的 validation libraries 百百種,起碼光是 React Hook Form 提到的就有以下四種:joi、superstruct、yup、zod
觀察到很有趣的是,為了吸引其他套件的使用者,有些 library 還會條列出其他套件的缺點(主觀地),讓其他使用者趕快來投奔,足見競爭之激烈XD
validation libraries 做為前端的第一層把關,能夠提早一步檢查要送往後端的資料,也往往是 UX 的一個重點項目。
因此這類 library 只要表單內的欄位數量多,就很適合用來提升可讀性,且在多人協作的專案,內建的檢核 API 也能確保每個人實作風格的一致。
檢核算是相對單純的需求,基本上就是「資料進來 -> 判斷 -> 給出錯誤訊息」,也因此才會百家爭鳴,今天我才知道有這麼多種 libraries,不過也正因為很單純,如何將 API 開得好讀、好用,可能才是這個戰場上最有競爭力的價值了!
你好~
關於缺點那個小段落,是否有文章或程式碼可以再更深入了解實際的狀況呢?
因為自己也有在使用 yup 這個工具,所以想確切了解這個工具的潛在問題,避免自己實際遇到卻不知道怎麼解決,謝謝你~
「沉默地出錯」是 yup 較令人詬病的一點,因為本身的錯誤處理已經被用來當作 schema validation 的錯誤訊息,反而導致 yup 內的程式碼出錯時,不會有任何反應,尤其容易發生在 .when() 或者 .test() 這種客製化邏輯的地方,建議都需要用 try-catch 包起來比較保險。
問得很好!的確舉個例會更好理解,比如像這樣
const validation = yup.object({
field1: yup.string().required(),
field2: yup.string().when('field1', (field1) => {
if (field1.something.not.exist) {
return yup.string().required();
}
return yup.string();
}),
});
因為 field1.something.not.exist
這行取不存在的 key 會死掉,但因為是 runtime error,所以會等到「按下 submit 的那一刻」才死掉,而且畫面上不會有任何反應,乍看之下還會以為是不是 submit 的 function 出了什麼事。
要捕捉到這種錯誤就比較麻煩,需要用到 try-catch:
const validation = yup.object({
field1: yup.string().required(),
field2: yup.string().when('field1', (field1) => {
try {
if (field1.somethingNotExist) {
return yup.string().required();
}
return yup.string();
} catch (error) {
console.log('error', error);
}
}),
});
但相對來說除了一些比較複雜的 case,其實是比較不會遇到啦XD“,只是下次如果發現,按下 submit 之後不明原因停留在原地,可以考慮看一下是不是檢核的問題囉!
非常謝謝你提供程式碼解釋~原來還有這樣的寫法,我之前都只知道以下的寫法(文件範例)
以後遇到問題就知道朝哪個方向解決了~
let schema = object({
isBig: boolean(),
count: number().when('isBig', {
is: true,
then: (schema) => schema.min(5),
otherwise: (schema) => schema.min(0),
}),
});