利用 HTML5 Constraint Validation API 驗證 React App 表單

Amdis Liu
Frochu
Published in
11 min readMay 5, 2019

文章中的範例將會以 React v16.8 的語法撰寫,所以 code snippet 中的 method / state 可能不會有 this 關鍵字在前,因為是使用 function component + hook。

測試環境 : Chrome 73 / FireFox 66 / Safari 12 on OSX 。

Demo app source code : https://github.com/amliu/html5-form-validation-demo

前言

自己專職寫 web 是從大 client side rendering + 前端框架時代開始,此時HTML5 已經很成熟,在查閱 W3C School 與 MDN 時可以看到 input elements 有許多的 constraint 可以使用,如: required / min / max / pattern / maxlength 等等,但卻一直沒有好好利用 constraint validation API 來驗證輸入,有點慚愧,於是做了點研究寫下筆記,試試是否可以讓程式變得更乾淨、更好讀。

既然各個主流瀏覽器都盡量朝遵循 web 標準實作的方向前進,不是更應該好好地利用便利的“共同標準” ,看看那些跨瀏覽器要處理的 CSS(眼神死)。從開發者角度來看,若連簡單的商業邏輯限制都必須去 trace code 才能得知,而無法從 HTML template 上就可一眼看出約束條件與 semantic 上屬於的類型 (eg. text / password / email / url 等),除了浪費掉 web standard 與 API 設計的功用此外,都將耗費開發者更多時間維護,若能將驗證的方法盡量使用 validation API:

1. 將提高程式可讀性
2. 亦可節省掉後面 JS 的運算,所謂的少算就少錯

舉例來說,下方 HTML code 中, password 欄位有個簡單的限制:長度最少要8個字元,沒超過 8 個字元不允許送到 server,在 <form> 按下 submit 後,我的 validate() 裡就不用再寫 if(password.length < 8) 這種 statement,我可以改用 HTMLFormElementreportValidity() 方法檢查密碼是否合法並讓瀏覽器用原生樣式回報訊息。

const handleSubmit = e => {
e.preventDefault();
if (validate()) {
console.log('form submitted');
}
};

const validate = () => {
const form = formRef.current;

return form.reportValidity();
};
<form onSubmit={handleSubmit} ref={formRef}>
<input type="password" minLength="8" value={password} onChange={handleChange} />
<button type="submit"> Submit </button>
</form>

此時密碼輸入過短 submit 時,出現瀏覽器內建訊息, console.log(‘form submitted’) 不會執行:

從左至右為 Chrome / Firefox / Safari 的原生訊息

Constraint Validation API

Property

  1. validity
    read-only 屬性,將回傳一個 ValidityState 物件, 物件中的屬性如下圖,描述著錯誤的類型,每個子屬性也都是 read-only
    舉例來說,若設定 required,但沒有輸入時,element.validity.valueMissing 將為 true
    ValidityState 有 11 個 屬性如下表,其中比較需要注意的是 tooLong ,MDN 提到在 Gecko-based 瀏覽器中輸入長度達到 constraint maxlength 的設定值時會直接無法繼續輸入,所以 validity.tooLong 永遠都不會是 true ,在意使用者體驗的要考慮這個 constraint 的使用;而我在 OSX Mojave 上測試的結果,不只 Firefox (Gecko),Safari & Chrome 的使用者輸入長度是達到 maxlength 長度時直接無法繼續輸入。
ValidityState object 屬性列表

2. validationMessage
read-only 屬性。當 input 為 valid 時,將得到空字串;input 為 invalid 時,得到 localized 的錯誤訊息。如果懶得自己客製化錯誤訊息的可以從這裡拿到訊息並置入自己的 error label 中。

3. willValidate
read-only 屬性: Boolean。說明這個 element 是否會進行 constraint validation,會的話則為 true。

Method

  1. checkValidity()
    檢查 element 的 children form elements 是否合法。 若合法,回傳 true;反之,回傳 false。如果是在 <form> node 上呼叫,會檢查所有 children form elements (e.g. <input>, <select> , <textarea>等) 是否全 valid,任何一個 invalid 則回傳 false。在 <input>, <select> node 這種單一輸入欄位呼叫,則只檢查這個欄位的值。
  2. reportValidity()
    若所有 children form elements 合法則回傳 true;若不合法,回傳 false,同時每一個 invalid 的 child control 也會收到 cancellable invalid event 給瀏覽器處理接下來的訊息顯示。
    checkValidity() 一樣都會檢查合法性差別是, reportValidity() 會讓瀏覽器顯示原生訊息,checkValidity() 不會。
  3. setCustomValidity(message)
    可為指定的元素設定自定義檢核訊息取代原生的瀏覽器錯誤訊息。當 message 為空字串表示元素沒有發生違反 constraint 的錯誤,瀏覽器不會顯示訊息。
上圖取自 html5rocks 文章 (https://www.html5rocks.com/en/tutorials/forms/constraintvalidation/)

支援的 Elements

以下清單來自 MDN ,共 7 種 form associated elements ,支援上述的 properties and methods:

HTMLFormElement 則是支援 checkValidity()reportValidity() 而已。

瀏覽器支援度

Modern browsers 都完全支援。
Opera Mini 與 IE ≤ 9 不支援。
IE11 (desktop & mobile) 則是部分支援,不支援 reportValidity()validity.tooShortvalidity.badInput

實際程式範例

範例中將用 React.createRef() 產生的 formRef 取得表單後,去 iterate 表單中的 children controls,然後一個一個檢驗是否 valid,若 invalid,將錯誤訊息放入自訂的 error label。 (line #43 ~ #53)

範例:使用 validation API 驗證表單 children control 輸入合法性
Gist code 實際執行結果 on Chrome

我將客製化的訊息放在 element 上的 title attribute,是因為在 html5rocks 文章Title Attribute 段落中看到寫在 title 的字串將可以取代原生瀏覽器訊息 (但不確定跨瀏覽器支援度),所以乾脆一致性地將自己的客製化訊息寫在 title attribute,最後再拿出來放入自有的 error label,這樣追 error message 比較簡單,JS 程式碼中也不用再寫一大堆自訂字串。

From html5rocks from constraint validation (https://www.html5rocks.com/en/tutorials/forms/constraintvalidation/)

總結

就算整天追著各式各樣的新技術 (話才說完就看到 GraphQL 推出 v3.3 …),還是要不忘讀讀 web standard,希望這篇文章對閱讀到這裡的你有所幫助,也許是多了解了一點 HTML5 constraint validation 用法,也許是讓程式碼更精簡好讀了一些,當然商業邏輯上還是會有些 constraint 無法定義到的東西需要額外判斷,就繼續找出方法希望讓程式碼長得漂亮點,讓每次的壞味道降到最低,讓每次都進步一點。

因為不常看到有人談這個 API ,我目前的使用經驗也不夠廣泛,若有人曾經踩雷或願意分享使用經驗、技巧等等,請不吝留言告訴我。

感謝你讀完這篇文章,如果喜歡我的文章,用力按下旁邊的「拍手」鈕給我些鼓勵吧。鼓勵這種東西,1下不嫌少,50下不嫌多,
讓我明白花時間寫出來的文章是否有幫助到你。
也歡迎在下面留言,有任何建議和指正,請務必讓我知道。

--

--

Amdis Liu
Frochu
Editor for

Web frontend / mobile developer. Editor of publication Frochu: https://medium.com/frochu .