則表達式以及正則在JS表單校驗中的應用
1. 正則表達式的引入
表單驗證兩種常用的方法:
<input type="submit" onclick="return validate()"/>
<form action="/aa" onsubmit="return validate()"/>
這里我們使用QQ號碼作為表單驗證來測試,我們知道QQ號碼的規則還是比較復雜,但是這里我們僅僅使用以下三條規則來講解:
1. qq號必須是5-10位
2. 數字0不可以作為qq號碼的開頭
3. QQ號碼必須是純數字組成
代碼示例如下所示:
<input type="submit" onclick="return validate()"/>
<form action="/aa" onsubmit="return validate()"/>
當然除了在form表單中添加onsubmit事件來實現表單驗證,我們還可以使用在submit類型的input標簽中添加onclick事件來實現表單驗證,代碼如下所示:
<form action="server/server.html" method="get" >
QQ賬號: <input id="qq" type="text" placeholder="請輸入QQ賬號...">
<span id="msg"></span> <br>
<input type="submit" value="登錄" onclick="return checkQQ();">
</form>
相信大家對上述代碼都能夠理解,但是大家應該也發現了使用傳統的if-else來校驗qq號碼,代碼非常冗余,而且可讀性很差,何況才短短兩條規則就寫了這么多的判斷,那真實的qq號碼肯定規則更多,如果讓你去驗證郵箱,或者網址呢?所以我們非常有必要學習一門新的技術用于我們JS表單校驗,它就是正則表達式了。
2. 正則表達式概述
正則表達式簡介
為什么需要正則表達式?
1.本質是用來對復雜文本進行處理。
2.當然也可以用于數據校驗
3.文本匹配、搜索、分割、替換等等
正則表達式的優勢和用途?
一種強大而靈活的文本處理工具;
提供了一種緊湊的、動態的方式,能夠以一種完全通用的方式來解決各種字符串處理(例如:驗證、查找、替換等)問題;
大部分語言、數據庫都支持正則表達式。
正則表達式定義:
正如他的名字一樣是描述了一個規則,通過這個規則可以匹配一類字符串。
正則表達式的用處:
驗證給定字符串是否符合指定特征,比如驗證是否是合法的郵件地址。
用來查找字符串,從一個長的文本中查找符合指定特征的字符串。
用來替換,比普通的替換更強大,用來替換滿足某個規則的字符串
用來分割,比普通的分割更強大,用來分割滿足某個規則的字符串
3. 工具軟件的使用
3.1. 工具軟件RegexBuddy的使用
為了提高開發效率,一般都先在工具軟件中測試正則表達式,通過測試后,才在程序中使用。
3.2 正則表達式圖形化顯示網站 Regexper
由于正則表達式非常靈活,正則表達式本身的可讀性非常差,所以比較復雜的正則表達式閱讀起來相對比較困難,所以我們可以借助一個圖形化正則表達式網站來幫助我們分析一個相對復雜的正則表達式。
正則圖形化顯示網站:
//regexper.com
//regexr-cn.com
圖形化顯示結果如下所示:
4. 正則表達式規則
4.1 普通字符
字母、數字、漢字、下劃線、以及沒有特殊定義的標點符號,都是“普通字符”。表達式中的普通字符,在匹配一個字符串的時候,匹配與之相同的一個字符。例如字符a, b, c ,1,2,中等等。
4.2 轉義字符
\n | 代表換行符 |
\t | 制表符 |
\ | 代表\本身 |
\^ ,$,\.,\(, \) , \{, \} , \? , \+ , \* , \| ,\[, \] | 匹配這些字符本身 |
4.3 字符集合
[ ]方括號匹配方式,能夠匹配方括號中任意一個字符
[ab5@] | 匹配 "a" 或 "b" 或 "5" 或 "@" |
[^abc] | 匹配 "a","b","c" 之外的任意一個字符 |
[f-k] | 匹配 "f"~"k" 之間的任意一個字母 |
[^A-F0-3] | 匹配 "A"~"F","0"~"3" 之外的任意一個字符 |
顯示效果如下所示:
注意: 正則表達式中的特殊符號,如果被包含于中括號中,則失去特殊意義,但 \ [ ] : ^ - 除外。
4.4 預定義字符
還有一些字符是正則表達式語法事先定義好的,有特殊的含義,能夠與 ‘多種字符’ 匹配的表達式
\d | 任意一個數字,0~9 中的任意一個 |
\w | 任意一個字母或數字或下劃線,也就是 A~Z,a~z,0~9,_ 中任意一個 |
\s | 包括空格、制表符、換行符等空白字符的其中任意一個 |
. | 小數點可以匹配除了換行符(\n)以外的任意一個字符 |
注意: 如果是\D,\W,\S表示相反的意思。
4.5 量詞
{n} | 表達式重復n次 |
{m,n} | 表達式至少重復m次,最多重復n次 |
{m,} | 表達式至少重復m次 |
? | 匹配表達式0次或者1次,相當于 {0,1} |
+ | 表達式至少出現1次,相當于 {1,} |
* | 表達式不出現或出現任意次,相當于 {0,} |
顯示效果如下所示:
表達式至少重復2次,最多重復4次
至少3次:
匹配表達式0次或者1次,相當于 {0,1}.
+至少一次
表達式不出現或出現任意次,相當于 {0,}
4.6 貪婪模式和非貪婪模式
匹配次數中的貪婪模式(匹配字符越多越好)
“{m,n}”, “{m,}”, “?”, “*”, “+”,具體匹配的次數隨被匹配的字符串而定。這種重復匹配不定次數的表達式在匹配過程中,總是盡可能多的匹配。
匹配次數中的非貪婪模式(匹配字符越少越好)
在修飾匹配次數的特殊符號后再加上一個 "?" 號,則可以使匹配次數不定的表達式盡可能少的匹配,使可匹配可不匹配的表達式,盡可能地 "不匹配"。
4.7 邊界字符
(本組標記匹配的不是字符而是位置,符合某種條件的位置)
^ | 與字符串開始的地方匹配 |
$ | 與字符串結束的地方匹配 |
\b | 匹配一個單詞邊界 |
顯示效果如下所示:
4.8 選擇符和分組
表達式 | 作用 |
| | 左右兩邊表達式之間 "或" 關系,匹配左邊或者右邊 |
( ) | (1). 在被修飾匹配次數的時候,括號中的表達式可以作為整體被修飾 (2). 取匹配結果的時候,括號中的表達式匹配到的內容可以被單獨得到 (3). 每一對括號會分配一個編號,使用 () 的捕獲根據左括號的順序從 1 開始自動編號。捕獲元素編號為零的第一個捕獲是由整個正則表達式模式匹配的文本 |
選擇符操作顯示效果如下所示:
分組:
4.9 反向引用(\nnn)
1. 每一對()會分配一個編號,使用 () 的捕獲根據左括號的順序從 1 開始自動編號。
2. 通過反向引用,可以對分組已捕獲的字符串進行引用。
5. 正則表達式的匹配模式
5.1 IGNORECASE 忽略大小寫模式
1. 匹配時忽略大小寫。
2. 默認情況下,正則表達式是要區分大小寫的。
5.2 SINGLELINE 單行模式
1. 整個文本看作一個字符串,只有一個開頭,一個結尾。
2.使小數點 "." 可以匹配包含換行符(\n)在內的任意字符。
5.3 MULTILINE 多行模式
1.每行都是一個字符串,都有開頭和結尾。
2.在指定了 MULTILINE 之后,如果需要僅匹配字符串開始和結束位置,可以使用 \A 和 \Z
6.開發中使用正則表達式的流程
1. 分析所要匹配的數據,寫出測試用的典型數據
2. 在工具軟件中進行匹配測試
3. 在程序中調用通過測試的正則表達式
7. 課堂練習
電話號碼驗證
(1)電話號碼由數字和"-"構成
(2)電話號碼為7到8位(不加區號)
(3)如果電話號碼中包含有區號,那么區號為三位或四位, 首位是0.
(4)區號用"-"和其他部分隔開
(5)移動電話號碼為11位
(6)11位移動電話號碼的第一位和第二位為"13“,”15”,”18”
0(\d{2,3})-\d{7,8}
(13)|(15)|(18)\d{9}
8. Java中使用正則表達式
相關類位于:java.util.regex包下面
類 Pattern:
正則表達式的編譯表示形式。
Pattern p = Pattern.compile(r); //建立正則表達式,并啟用相應模式
類 Matcher:
通過解釋 Pattern 對 character sequence 執行匹配操作的引擎
Matcher m = p.matcher(str); //匹配str字符串
編程中使用正則表達式常見情況:
驗證表達式是否匹配整個字符串
驗證表達式是否可以匹配字符串的子字符串
返回給定字符串中匹配給定正則表達式所有子字符串
替換給定字符串中匹配正則表達式的子字符串
根據正則表達式定義規則截取字符串
9. JAVASCRIPT中使用正則表達式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- QQ賬號的驗證:
1.qq號必須是5-10位
2.數字0不可以作為qq號碼的開頭
--> <form action="server/server.html" method="get" onsubmit="return checkQQ()">
QQ賬號: <input id="qq" type="text" placeholder="請輸入QQ賬號..."> <span id="msg"></span> <br>
<input type="submit" value="登錄">
</form>
<script>
function checkQQ() { var qq = document.getElementById("qq").value;
var msg = document.getElementById("msg");
// 默認qq號碼是合格的
var flag = true;
// 保證5-10位
if(qq.length >= 5 && qq.length <= 10) {
// 保證不是0開頭
if(qq.charAt(0) != 0) {
// 保證是數字
for(var i = 0; i < qq.length; i++) {
var ch = qq.substr(i,1);
if(!(ch >= '0' && ch <= '9')) {
flag = false;
msg.innerText = "XXX QQ號碼必須是數字!!!"
msg.style.color = "red";
break;
}
}
} else {
flag = false;
msg.innerText = "XXX QQ號碼不能以數字0開頭!!!"
msg.style.color = "red";
}
} else {
flag = false;
msg.innerText = "XXX QQ號碼的位數必須在5~10之間!!!"
msg.style.color = "red";
}
return flag;
}
</script>
</body>
</html>
Flags可選項常見值:
g (全文查找出現的所有pattern)(沒有g只會匹配一次)
i (忽略大小寫)
<form action="server/server.html" method="get" >
QQ賬號: <input id="qq" type="text" placeholder="請輸入QQ賬號...">
<span id="msg"></span> <br>
<input type="submit" value="登錄" onclick="return checkQQ();">
</form>
RegExp對象常用方法:
exec(): 返回的是一個數組。該數組包含了匹配該模式的第一個子字符串以及該子字符串中匹配相關分組的字符串。比如:
var re = new RegExp("(\\d+)([a-z]+)","ig");
var result = re.exec("33ff55tt77uu88yy");
返回的數組為:[33ff,33,ff]
test(): 返回一個 Boolean 值,它指出在被查找的字符串中是否存在模式匹配的子字符串
//test 查找是否 存在的字符串
var str1 = "abc123abc123";
//判斷字符串是否都是數字
var reg3 = /^\d+$/;
var flag = reg3.test(str1);
console.log(flag);
字符串中常用的使用正則表達式的方式:
match():使用正則表達式模式對字符串執行查找,并將符合該模式的所有子字符串包含到數組中返回。
var re = new RegExp("(\\d+)([a-z]+)","ig");
var reg = /(\d+)([a-z]+)/gi;
var t = "33ff55tt77uu88yy".match(re);
結果為數組:[33ff,55tt,77uu,88yy]
search(): 返回與正則表達式查找內容匹配的第一個子字符串的位置
var reg = /\d+/g; // 數組正則
var index = "33ff55tt77uu88yy".search(reg);
split(regex):按照指定的模式切割字符串,返回一個數組。
var t = "asdfd33ff55tt77uu88yy".split(/\d+/);
replace(): 替換
var t = "asdfd3355tt77uu88yy".replace(/\d+/g,"#");
代碼示例如下所示:
// 替換
// 指定為global模式,否則只替換第一個子字符串
var reg4= /\d+/g;
var str2 = str1.replace(reg4,"#");
console.log(str2);
10. 針對表單域的驗證,封裝一個通用的函數
我們可以編寫一個簡單的用戶名驗證的方法,代碼如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Js表單正則驗證</title>
</head>
<body>
<form action="server/server.html" method="get">
用戶名:<input type="text" id="nameId" name="name" onblur="checkName();"><span id="msg"></span><br>
密碼: <input type="password" name="pwd"><br>
<input type="submit">
</form>
<script>
/*
表單驗證:
實現思路:
1.給input標簽添加onblur事件
2.獲取input標簽輸入的內容
3.判斷是否輸入合法
4.dom操作修改頁面提示信息
*/
function checkName() {
var nameInputValue = document.getElementById("nameId").value;
var msg = document.getElementById("msg");
var reg = /^[\u4e00-\u9fa5]{2,10}$/;
if(reg.test(nameInputValue)) {
msg.innerText = "√ 用戶名合法!!!"
msg.style.color = "red";
} else {
msg.innerText = "X 用戶名必須是2-10個中文字符!!!"
msg.style.color = "red";
}
}
</script>
</body>
</html>
這里我們通過輸入框失去焦點來對輸入的內容進行驗證,驗證的步驟也非常簡單,步驟基本如下所示:
1. 給表單域添加onblur失去焦點事件
2. 獲取表單域輸入框的內容
3. 通過正則表達式判斷輸入是否合法
4. 根據是否合法顯示不同的頁面提示信息
通過觀察我們不難發現,不僅驗證用戶名,驗證郵箱,密碼,身份證等等基本上步驟都是一樣的,但是有一些例如正則表達式或者顯示的正確或者錯誤信息是變化的,所以我們可以對驗證的方法進行封裝,將變化的抽取成為形參,從而用于其他的表單校驗,代碼如下所示:
現代web開發中,表單是用戶與網站互動的重要方式之一。HTML5為表單提交提供了強大的功能和豐富的輸入類型,讓收集和驗證用戶輸入數據變得更加容易和安全。本文將詳細介紹HTML5表單的各個方面,包括基本結構、輸入類型、驗證方法和提交過程。
HTML表單由<form>標簽定義,它可以包含輸入字段、標簽、按鈕等元素。一個基本的表單結構如下所示:
<form action="/submit_form" method="post">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
<label for="email">電子郵箱:</label>
<input type="email" id="email" name="email" required>
<input type="submit" value="提交">
</form>
在這個例子中,表單有兩個輸入字段:姓名和電子郵箱。每個輸入字段都有一個<label>標簽,這不僅有助于用戶理解輸入的內容,也有助于屏幕閱讀器等輔助技術。<form>標簽的action屬性定義了數據提交到服務器的URL,method屬性定義了提交數據的HTTP方法(通常是post或get)。
HTML5提供了多種輸入類型,以支持不同的數據格式和設備。
<!-- 單行文本 -->
<input type="text" name="username" placeholder="請輸入用戶名" required>
<!-- 密碼 -->
<input type="password" name="password" required minlength="8">
<!-- 郵箱 -->
<input type="email" name="email" required placeholder="example@domain.com">
<!-- 搜索框 -->
<input type="search" name="search" placeholder="搜索...">
<!-- 數值 -->
<input type="number" name="age" min="18" max="100" step="1" required>
<!-- 滑動條 -->
<input type="range" name="volume" min="0" max="100" step="1">
<!-- 電話號碼 -->
<input type="tel" name="phone" pattern="^\+?\d{0,13}" placeholder="+8613800000000">
<!-- 日期 -->
<input type="date" name="birthdate" required>
<!-- 時間 -->
<input type="time" name="appointmenttime">
<!-- 日期和時間 -->
<input type="datetime-local" name="appointmentdatetime">
<!-- 復選框 -->
<label><input type="checkbox" name="interest" value="coding"> 編程</label>
<label><input type="checkbox" name="interest" value="music"> 音樂</label>
<!-- 單選按鈕 -->
<label><input type="radio" name="gender" value="male" required> 男性</label>
<label><input type="radio" name="gender" value="female"> 女性</label>
<!-- 下拉選擇 -->
<select name="country" required>
<option value="china">中國</option>
<option value="usa">美國</option>
</select>
<!-- 顏色選擇器 -->
<input type="color" name="favcolor" value="#ff0000">
<!-- 文件上傳 -->
<input type="file" name="resume" accept=".pdf,.docx" multiple>
HTML5表單提供了內置的驗證功能,可以在數據提交到服務器之前進行檢查。
<input type="text" name="username" required>
<input type="text" name="zipcode" pattern="\d{5}(-\d{4})?" title="請輸入5位數的郵政編碼">
<input type="number" name="age" min="18" max="99">
<input type="text" name="username" minlength="4" maxlength="8">
當用戶填寫完表單并點擊提交按鈕時,瀏覽器會自動檢查所有輸入字段的有效性。如果所有字段都滿足要求,表單數據將被發送到服務器。否則,瀏覽器會顯示錯誤信息,并阻止表單提交。
<input type="submit" value="提交">
可以使用JavaScript來自定義驗證或處理提交事件:
document.querySelector('form').addEventListener('submit', function(event) {
// 檢查表單數據
if (!this.checkValidity()) {
event.preventDefault(); // 阻止表單提交
// 自定義錯誤處理
}
// 可以在這里添加額外的邏輯,比如發送數據到服務器的Ajax請求
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表單提交并顯示JSON</title>
</head>
<body>
<!-- 表單定義 -->
<form id="myForm">
<label for="name">姓名:</label>
<input type="text" id="name" name="name">
<br>
<label for="email">電子郵件:</label>
<input type="email" id="email" name="email">
<br>
<input type="button" value="提交" onclick="submitForm()">
</form>
<script>
// JavaScript函數,處理表單提交
function submitForm() {
// 獲取表單元素
var form = document.getElementById('myForm');
// 創建一個FormData對象
var formData = new FormData(form);
// 創建一個空對象來存儲表單數據
var formObject = {};
// 將FormData轉換為普通對象
formData.forEach(function(value, key){
formObject[key] = value;
});
// 將對象轉換為JSON字符串
var jsonString = JSON.stringify(formObject);
// 彈出包含JSON字符串的對話框
alert(jsonString);
// 阻止表單的默認提交行為
return false;
}
</script>
</body>
</html>
在這個例子中:
注意,這個例子中我們使用了type="button"而不是type="submit",因為我們不希望表單有默認的提交行為。我們的JavaScript函數submitForm會處理所有的邏輯,并且通過返回false來阻止默認的表單提交。如果你想要使用type="submit",你需要在<form>標簽上添加一個onsubmit="return submitForm()"屬性來代替按鈕上的onclick事件。
HTML5的表單功能為開發者提供了強大的工具,以便創建功能豐富、用戶友好且安全的網站。通過使用HTML5的輸入類型和驗證方法,可以確保用戶輸入的數據是有效的,同時提高用戶體驗。隨著技術的不斷進步,HTML5表單和相關API將繼續發展,為前端工程師提供更多的可能性。
者:郜克帥
原文:https://dev.to/ajones_codes/a-better-guide-to-forms-in-react-47f0?utm_source=newsletter.reactdigest.net&utm_medium=newsletter&utm_campaign=a-better-guide-to-forms-in-react
React 生態擁有豐富的庫、文章、視頻和幾乎你能想到的所有 Web 領域的資料。然而,隨著時間的推移,這些資料許多都已經過時,無法滿足現代最佳實踐的要求了。
最近,我在開發一個 AI 項目,里面有許多復雜的動態表單。在研究了許多優秀的 React 表單指南之后,我意識到,大多數構建表單的資源都已經過時了,而且往往已經過時很多年。
本文將介紹 React 中構建表單的現代最佳實踐、如何去構建動態表單、 RSC(React Server Components)的表單等等。最后,在理解了這些之后,我將解釋我在其他指南中發現的不足,并根據我使用 React 的經驗提出建議。
理解 React 中表單的關鍵點在于 “受控” 與 “非受控” 的概念,這是 React 中構建表單的兩種不同的方法。
受控表單使用 state 存儲每個 input 的值,然后在每次渲染時通過 value 屬性設置對應 input的值。如果其他函數更新了這些 state,同樣的,對應 input 的值也會立刻改變。
如果你的代碼沒有渲染 Form,但相關的 state 并不會消失,仍然存在于我們的運行時上下文中。
受控表單往往給予了我們更大的選擇,例如比較復雜的、非 HTML 標準的表單校驗,如檢查密碼強度和對用戶手機號進行格式化。
它們看起來往往是這個樣子的:
import React, { useState } from 'react'
function ControlledForm() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = () => {
sendInputValueToApi(value).then(() => /** 業務邏輯... */);
};
return (
<>
<input type="text" value={value} onChange={handleChange} />
<button onClick={handleSubmit}>send</button>
</>
)
}
注意,用 <form> 將 input 包裹起來并且給 input 一個命名從語義上來講更加準確,但是這不是必需的。
因為數據已經保存在 state 中,所以我們并不需要真正的 onSubmit事件,而且在按鈕點擊時,我們也并不需要直接訪問 input 的值。
這種方式有一些不足之處:
import React, { useState } from "react";
function CumbersomeForm() {
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
email: "",
address: "",
// ... 可能會有更多的值
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prevState) => ({ ...prevState, [name]: value }));
};
return (
<>
<label>First Name:</label>
<input
type="text"
name="firstName"
value={formData.firstName}
onChange={handleChange}
/>
<label>Last Name:</label>
<input
type="text"
name="lastName"
value={formData.lastName}
onChange={handleChange}
/>
<label>Email:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
<label>Address:</label>
<input
type="text"
name="address"
value={formData.address}
onChange={handleChange}
/>
{/* ... 可能會有更多的字段 */}
</>
);
}
與受控表單不同的是,非受控表單不在 state 中存儲表單的值。相反,非受控表單使用原生 HTML 內置的 <form> 的功能和 JavaScript 去管理數據。
舉例來說,瀏覽器會幫我們管理狀態,我們無需在每次 input 改變時使用 setState 更新 state 并把 state 設置到 input 的 value 屬性上,我們的組件不再需要或使用這些 state
當組件渲染時,React 會將 onSubmit 監聽器添加到表單上。當提交按鈕被點擊時,我們的 handleSubmit 函數會被執行。與使用 state相比,它更接近于不使用任何 JavaScript 的普通 HTML 表單的工作方式。
function UncontrolledForm() {
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const inputValue = formData.get('inputName');
sendInputValueToApi(inputValue).then(() => /* 業務邏輯... */)
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="inputName" />
<button type="submit">Send</button>
</form>
);
}
使用非受控表單的一個好處就是會減少大量的冗余代碼:
// 受控
const [value, setValue] = useState('')
const handleChange = (event) => {
setValue(event.target.value);
}
...
<input type="text" value={value} onChange={handleChange} />
// 非受控
<input type="text" name="inputName" />
即便只有 1 個 input,區別也是非常顯著的,當有許多 input 時,效果會更加明顯:
function UncontrolledForm() {
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const { name, email, address } = Object.fromEntries(formData);
};
return (
<form onSubmit={handleSubmit}>
<label>First Name:</label>
<input type="text" name="firstName" />
<label>Last Name:</label>
<input type="text" name="lastName" />
<label>Email:</label>
<input type="email" name="email" />
<label>Address:</label>
<input type="text" name="address" />
{/* ... 可能會有更多的字段 */}
<button type="submit">Submit</button>
</form>
);
}
非受控表單與受控表單相比,沒有許多冗余的代碼,并且我們不需要手動管理許許多多的 state 或一個巨大的對象。事實上,這里根本沒有 state。這個表單可以有成百上千個子組件但它們不會導致彼此重新渲染。使用這種方式,會讓表單性能變的更好、減少大量的冗余代碼并且使我們代碼的可讀性更強。
非受控表單的不足之處是你無法直接訪問每個 input 的值。這會使自定義校驗變的棘手。例如你需要在用戶輸入手機號的時候格式化手機號。
許多文章推薦在非受控表單的每個 input 上使用一個 ref 而不是使用 new FormData(),我認為原因是 FormData API 很少人知道。然而,它在大約十年前已經成為了一個標準并且已經被所有主流瀏覽器支持。
我強烈建議你不要為表單使用 useRef,因為它會像 useState 一樣引入許多相同的問題和冗余的代碼。
然而,確實有一些場景,ref 可以幫助我們。
function MyForm() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<form>
<input ref={inputRef} type="text" />
<button type="button" onClick={focusInput}>
Focus Input
</button>
</form>
);
}
const ChildComponent = React.forwardRef((props, ref) => (
<input ref={ref} type="text" />
));
function MyForm() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<form>
<ChildComponent ref={inputRef} />
<button type="button" onClick={focusInput}>
Focus Input
</button>
</form>
);
}
在許多場景中,你可能需要控制一個或更多的 input,當用戶在輸入手機號碼時對其進行格式化是一個非常棒的例子。在這些場景中,即便你正在使用非受控表單,你也可以使用一個受控的 input。在這種情況下,也不要使用 state 去訪問 input 的值,繼續使用 new FormData(...),僅僅使用 state 去管理相關輸入的展示。
function MixedForm() {
const [phoneNumber, setPhoneNumber] = useState("");
const handlePhoneNumberChange = (event) => {
// 格式化手機號
const formattedNumber = formatPhoneNumber(event.target.value);
setPhoneNumber(formattedNumber);
};
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
};
return (
<form onSubmit={handleSubmit}>
<label>Name:</label>
<input type="text" name="name" />
<label>Email:</label>
<input type="email" name="email" />
<label>Phone Number:</label>
<input
type="tel"
name="phoneNumber"
value={phoneNumber}
onChange={handlePhoneNumberChange}
/>
<label>Address:</label>
<input type="text" name="address" />
<button type="submit">Submit</button>
</form>
);
}
function formatPhoneNumber(number) {
return number.replace(/\D/g, "").slice(0, 10);
}
注意:盡量減少 state,在這個例子中,你不會既想要一個保存原始電話號碼的 useState,又想要一個用于格式化電話號碼的 useState,并且因為同步它們還會帶來多余的重新渲染的效果。
談到重新渲染優化,我們可以將受控 input 抽離出來以此來減少重新渲染對表單其余部分的影響。
const PhoneInput = () => {
const [phoneNumber, setPhoneNumber] = useState("");
const handlePhoneNumberChange = (event) => {
const formattedNumber = formatPhoneNumber(event.target.value);
setPhoneNumber(formattedNumber);
};
return (
<input
type="tel"
name="phoneNumber"
value={phoneNumber}
onChange={handlePhoneNumberChange}
/>
);
};
function MixedForm() {
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
};
return (
<form onSubmit={handleSubmit}>
<label>Name:</label>
<input type="text" name="name" />
<label>Email:</label>
<input type="email" name="email" />
<label>Phone Number:</label>
<PhoneInput />
<label>Address:</label>
<input type="text" name="address" />
<button type="submit">Submit</button>
</form>
);
}
function formatPhoneNumber(number) {
return number.replace(/\D/g, "").slice(0, 10);
}
如果你用過受控 input,那么看完上面代碼之后,你可能會想:“沒有傳遞任何 setState 或 ref, 父組件是如何知道子組件的值”。為了理解這個問題,請記住,當 React 代碼被渲染成 HTML 時,瀏覽器只會看到 Form 和它里面的 inputs,包括 <PhoneInput /> 渲染的 input。
我們的組件組合方式對我們渲染的 HTML 沒有功能上的影響。因此,那個 input 的值會像其他字段一樣被包含在 FormData 中。這就是組件組合和封裝的力量。我們可以將重新渲染控制在最小影響范圍內,與此同時,DOM 依然像原生 HTML 一樣呈現。
考慮到這個問題的并非只有你一個!當在提交前需要校驗時,React 開發者往往會傾向于去使用受控組件。
許多開發者并沒有意識到,你并不需要 React 或自定義的 JavaScript 做這些校驗。事實上,有一些原生的屬性已經支持了這些事情。請參閱 MDN 查看更多的細節:https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation
在不使用任何 JavaScript 的前提下,你可以設置 input 必填、設置長度限制和用正則表達式設置格式要求。
在相關的討論中,在我們需要在客戶端展示錯誤信息的時候,開發者通常會選擇受控表單來解決這個問題。然而,我會優先選擇使用非受控組件并在我的 onSubmit 函數里面做校驗和錯誤管理,而不是使用受控組件并在每次 input 改變時更新對應的 state。這種方式可以盡量減少 state 和 setState 的數量。
function UncontrolledForm() {
const [errors, setErrors] = useState({});
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
let validationErrors = {};
// 自定義校驗:確保郵箱的域名是:"example.com"
const email = formData.get("email");
if (email && !email.endsWith("@example.com")) {
validationErrors.email = "Email must be from the domain example.com.";
}
if (formData.get("phoneNumber").length !== 10) {
validationErrors.phoneNumber = "Phone number must be 10 digits.";
}
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
} else {
console.log(Array.from(formData.entries()));
// 清空之前的值
setErrors({});
}
};
return (
<form onSubmit={handleSubmit}>
<label>Name:</label>
<input type="text" name="name" required />
{errors.name && <div className="error">{errors.name}</div>}
<label>Email (must be @example.com):</label>
<input type="email" name="email" required />
{errors.email && <div className="error">{errors.email}</div>}
<label>Phone Number (10 digits):</label>
<input type="tel" name="phoneNumber" required pattern="\d{10}" />
{errors.phoneNumber && <div className="error">{errors.phoneNumber}</div>}
<button type="submit">Submit</button>
</form>
);
}
export default UncontrolledForm;
React Server Components(RSC) 使用服務端框架去渲染部分組件,通過這種辦法可以減少瀏覽器訪問你網站時下載的 JavaScript 的數量。這可以顯著地提升你網站的性能。
RSC 對我們編寫表單的方式有很大的影響。因為,對于首次渲染來說,如果我們沒有使用 state,它們可以在服務端被渲染并不附帶任何 JavaScript 文件給瀏覽器。這意味著,非受控表單即使在沒 JavaScript的情況下也可以交互,意味著它們可以更早的工作而不用等待 JavaScript 去下載然后運行。這可以讓你的網站體驗更加絲滑。
使用 Next.js,你可以在你的表單中使用 Server Actions,因此你不需要去為了你的表單交互寫一個 API。你需要準備的只是一個事件處理函數。你可以在 Next.js 的文檔中找到關于這個主題的更多內容或者觀看是 Lee 的視頻。
如果你要在 RSC 中混合一些受控表單,請確保把它們抽離為單獨的客戶端組件,就像上面的 <PhoneInput /> 一樣。這可以盡可能的減少需要打包的 JavaScript 文件。
// page.jsx
import { PhoneInput } from "./PhoneInput";
export default function Page() {
async function create(formData: FormData) {
"use server";
// ... use the FormData
}
return (
<form action={create}>
<label>Name:</label>
<input type="text" name="name" />
<label>Email:</label>
<input type="email" name="email" />
<label>Phone Number:</label>
<PhoneInput />
<label>Address:</label>
<input type="text" name="address" />
<button type="submit">Submit</button>
</form>
);
}
// PhoneInput.jsx
"use client";
function formatPhoneNumber(number) {
return number.replace(/\D/g, "").slice(0, 10);
}
import { useState } from "react";
export const PhoneInput = () => {
const handlePhoneNumberChange = (event) => {
const formattedNumber = formatPhoneNumber(event.target.value);
setPhoneNumber(formattedNumber);
};
const [phoneNumber, setPhoneNumber] = useState("");
return (
<input
type="tel"
name="phoneNumber"
value={phoneNumber}
onChange={handlePhoneNumberChange}
/>
);
};
在 React 生態中有許多為受控表單設計的優秀的庫。最近我一直在使用 React Hook Form 來處理這些應用,不過我更傾向于使用非受控表單,因為不需要額外的庫來管理表單狀態。(一些流行的庫:React Hook Form、Formik和Informed)
因為 Google 搜索 react forms 時排名靠前的文章令人感到困惑、過時或具有誤導性,因此我寫了本文。
一些總結性的看法:
作者:郜克帥
來源:微信公眾號:Goodme前端團隊
出處:https://mp.weixin.qq.com/s/JikF87PYtxnb9uxEtTtGNA
*請認真填寫需求信息,我們會在24小時內與您取得聯系。