在本教程中,我們將看看如何使用React和HTML5的現代組合來處理表單提交和驗證。
當我們在Web應用程式中討論用戶輸入時,我們經常首先想到HTML表單。Web表單從HTML的第一個版本開始就已經可用。顯然,該功能已於1991年推出,並在1995年以RFC 1866標準進行了標準化。我們在任何地方都使用它們,幾乎每個庫和框架。但是React呢?Facebook就如何處理表單提供了有限的意見。主要是為交互事件訂閱表單和控制項,並通過「value」屬性傳遞狀態。因此表單驗證和提交邏輯取決於您。體面的用戶界面意味著你可以覆蓋「on submit」/「on input」欄位驗證,內聯錯誤消息,根據有效性切換元素,「原始」,「提交」狀態等邏輯。我們能不能抽象出這種邏輯並簡單地將它插入我們的表格中?我們當然可以。唯一的問題是採取什麼方法和解決方案。
DevKit中的表單
如果你喜歡去的devkit ReactBootstrap或AntDesign你很可能已經高興的形式。兩者都提供組件來構建滿足不同要求的表單。例如,在AntDesign中,我們使用元素和帶有的表單欄位來定義表單,該 是集合中任何輸入控制項的封裝。您可以在上設置驗證規則,如:
<FormItem>{getFieldDecorator('select', {rules: [{ required: true, message: 'Please select your country!' },],})(<Select placeholder="Please select a country"><Option value="china">China</Option><Option value="use">U.S.A</Option></Select>)}</FormItem>
然後,例如,在表單提交處理程序中,您可以運行this.props.form.validateFields()以應用驗證。它看起來像一切照顧。然而,解決方案是特定於框架的。如果您不使用DevKit,則無法從其功能中受益。
基於模式的建築形式
或者,我們可以使用獨立的組件,根據提供的JSON規範為我們構建表單。例如,我們可以導入 Winterfell組件並構建一個如此簡單的表單:
<Winterfellschema={loginSchema}></Winterfell>
但是,模式可能相當複雜。此外,我們將自己綁定到自定義語法。另一個解決方案 react-jsonschema-form看起來很相似,但依賴於 JSON模式。JSON模式是一個專門用於注釋和驗證JSON文檔的項目無關詞彙表。然而,它將我們與構建器中實現並在架構中定義的唯一功能綁定在一起。
Formsy
我寧願使用我的任意HTML表單的包裝來處理驗證邏輯。在這裡,最受歡迎的解決方案之一是 Formsy。它是什麼樣子的?我們為表單域創建自己的組件,並用HOC包裝它 withFormsy:
import { withFormsy } from "formsy-react";import React from "react";class MyInput extends React.Component {changeValue = ( event ) => {this.props.setValue( event.currentTarget.value );}render() {return (<div><inputonChange={ this.changeValue }type="text"value={ this.props.getValue() || "" }/><span>{ this.props.getErrorMessage() }</span></div>);}}export default withFormsy( MyInput );
正如你所看到的,組件接收getErrorMessage() 道具中的 函數,我們可以使用它來進行內聯錯誤消息。
所以我們做了一個場域組件。讓我們把它放在一個表單中:
import Formsy from "formsy-react";import React from "react";import MyInput from "./MyInput";export default class App extends React.Component {onValid = () => {this.setState({ valid: true });}onInvalid = () => {this.setState({ valid: false });}submit( model ) {//...}render() {return (<Formsy onValidSubmit={this.submit} onValid={this.onValid} onInvalid={this.onInvalid}><MyInputname="email"validations="isEmail"validationError="This is not a valid email"required></MyInput><button type="submit" disabled={ !this.state.valid }>Submit</button></Formsy>);}}
我們用validations 屬性指定所有必需的欄位驗證器 (請參閱 可用驗證器列表)。用 validationError, 我們設置所需的驗證消息,並從形式有效性狀態中接收 onValid 和 onInvalid處理程序。
這看起來簡單,乾淨,靈活。但我想知道為什麼我們不依賴HTML5內置表單驗證,而不是使用無數的自定義實現。
HTML5表單驗證
該技術出現在不久前。第一個實現在2008年與Opera 9.5一起實現。如今,它在所有現代瀏覽器中都可用。表單(數據)驗證引入了額外的HTML屬性和輸入類型,可用於設置表單驗證規則。驗證也可以通過使用專用API從JavaScript進行控制和定製 。
我們來看下面的代碼:
<form><label for="answer">What do you know, Jon Snow?</label><input id="answer" name="answer" required><button>Ask</button></form>
這是一個簡單的形式,除了一件事 - 輸入元素有一個 required 屬性。所以如果我們立即按下提交按鈕,表單將不會被發送到伺服器。相反,我們會在輸入旁邊看到一個工具提示,說明該值不符合給定的約束(即該欄位不應為空)。
現在我們將輸入設置為一個附加約束:
<form><label for="answer">What do you know, Jon Snow?</label><input id="answer" name="answer" required pattern="nothing|nix"><button>Ask</button></form>
所以這個值不僅僅是必需的,而且必須遵從給定的正則表達式pattern。
錯誤信息雖然不是那種信息,是嗎?我們可以對其進行定製(例如,解釋我們對用戶的期望)或者只是翻譯:
<form><label for="answer">What do you know, Jon Snow?</label><input id="answer" name="answer" required pattern="nothing|nix"><button>Ask</button></form>const answer = document.querySelector( "[name=answer]" );answer.addEventListener( "input", ( event ) => {if ( answer.validity.patternMismatch ) {answer.setCustomValidity("Oh, it's not a right answer!");} else {answer.setCustomValidity( "" );}});
所以基本上,在輸入事件中,它檢查patternMismatch 輸入有效性狀態的 屬性狀態。任何時候實際值與模式不匹配,我們定義錯誤消息。如果我們 對控制項有任何其他限制,我們也可以在事件處理程序中覆蓋它們。
對工具提示不滿意?是的,他們在不同的瀏覽器中看起來不一樣。我們添加下面的代碼:
<form novalidate><label for="answer">What do you know, Jon Snow?</label><input id="answer" name="answer" required pattern="nothing|nix"><div data-bind="message"></div><button>Ask</button></form>const answer = document.querySelector( "[name=answer]" ),answerError = document.querySelector( "[name=answer] + [data-bind=message]" );answer.addEventListener( "input", ( event ) => {answerError.innerHTML = answer.validationMessage;});
即使只是這個超級簡短的介紹,你可以看到技術的力量和靈活性。本機表單驗證非常好。那麼,為什麼我們要依靠無數的定製庫?為什麼不使用內置驗證?
React符合表單驗證API
react-html5-form將React(以及可選的Redux)連接到HTML5 Form Validation API。它公開組件Form 和 InputGroup (類似於Formsy的自定義輸入或 FormItem 在AntDesign中)。因此, Form 定義了表單及其範圍,並 定義了可以包含一個或多個輸入的欄位的範圍。我們只是用這些組件包裝任意形式的內容(只是簡單的HTML或React組件)。在用戶事件中,我們可以請求表單驗證,並獲得更新後的狀態 和組件,因此,對底層輸入有效性。 InputGroupFormInputGroup
那麼,讓我們在實踐中看到它。首先,我們定義表單範圍:
import React from "react";import { render } from "react-dom";import { Form, InputGroup } from "Form";const MyForm = props => (<Form>{({ error, valid, pristine, submitting, form }) => (<>Form content<button disabled={ ( pristine || submitting ) } type="submit">Submit</button></>)}</Form>);render( <MyForm ></MyForm>, document.getElementById( "app" ) );
該作用域接收具有屬性的狀態對象:
錯誤 - 表單錯誤消息(通常是伺服器驗證消息)。我們可以設置它 form.setError()。valid - 布爾值,指示所有基礎輸入是否符合指定約束。pristine - 布爾值,指示用戶是否尚未與表單交互。submitting - 表示正在處理表單的布爾值(按下提交按鈕時切換為true,並在用戶定義的異步onSubmit 處理程序解析後立即返回false )。form - 用於訪問API的Form組件的實例。
這裡我們使用just pristine 和submitting properties來將提交按鈕切換到禁用狀態。
為了在提供表單內容時註冊輸入進行驗證,我們將它們包裝在一起 InputGroup
<InputGroup validate={[ "email" ]} }}>{({ error, valid }) => (<div><label htmlFor="emailInput">Email address</label><inputtype="email"requiredname="email"id="emailInput" />{ error && (<div className="invalid-feedback">{error}</div>) }</div>)}</InputGroup>
通過validate 道具,我們指定了團隊的哪些輸入應該被註冊。[ "email" ] 意味著我們有唯一的輸入,名稱為「電子郵件」。
在範圍中,我們收到具有以下屬性的狀態對象:
錯誤 - 所有已註冊輸入的錯誤消息數組。錯誤 - 最後發出的錯誤消息。valid - 布爾值,指示所有基礎輸入是否符合指定約束。inputGroup - 訪問API的組件實例。
渲染之後,我們得到一個帶有電子郵件欄位的表單。如果該值為空或在提交時包含無效的電子郵件地址,則會在輸入旁邊顯示相應的驗證消息。
請記住,我們在使用本機Form Validation API時正在努力處理自定義錯誤消息?在以下情況下更容易 InputGroup:
<InputGroupvalidate={[ "email" ]}translate={{email: {valueMissing: "C'mon! We need some value",typeMismatch: "Hey! We expect an email address here"}}}>...
我們可以為每個輸入指定一個映射,其中鍵是有效性屬性,值是自定義消息。
那麼,消息定製很容易。自定義驗證怎麼樣?我們可以通過validate 道具做到這一點 :
<InputGroup validate={{"email": ( input ) => {if ( !EMAIL_WHITELIST.includes( input.current.value ) ) {input.setCustomValidity( "Only whitelisted email allowed" );return false;}return true;}}}>...
在這種情況下,我們提供了一個映射,而不是一組輸入名稱,其中鍵是輸入名稱,值是驗證處理程序。處理程序檢查輸入值(可以異步完成)並將有效性狀態作為布爾值返回。使用 input.setCustomValidity, 我們分配一個特定於案例的驗證消息。
提交時的驗證並不總是我們想要的。我們來實現一個「即時」驗證。首先,我們為輸入事件定義一個事件處理程序:
const onInput = ( e, inputGroup ) => {inputGroup.checkValidityAndUpdate();};
實際上,我們只是在用戶輸入輸入時重新驗證輸入組。我們認為控制如下:
<input type="email" required name="email" onInput={( e ) => onInput( e, inputGroup, form ) } id="emailInput" />
從現在開始,只要我們改變輸入值就會被驗證,如果它無效,我們會立即收到錯誤信息。
你可以從上面的例子中找到演示的原始碼。
順便說一句,你喜歡將組件派生的表單狀態樹連接到Redux存儲嗎?我們也可以做到這一點。
該軟體包公開了html5form 包含所有註冊表單的狀態樹的reducer 。我們可以像這樣將它連接到商店:
import React from "react";import { render } from "react-dom";import { createStore, combineReducers } from "redux";import { Provider } from "react-redux";import { App } from "./Containers/App.jsx";import { html5form } from "react-html5-form";const appReducer = combineReducers({html5form});// Store creationconst store = createStore( appReducer );render( <Provider store={store}><App ></App></Provider>, document.getElementById( "app" ) );
現在,當我們運行應用程式時,我們可以在商店中找到所有與表單相關的狀態。
這是一個專用演示的原始碼。
概括
React沒有內置的表單驗證邏輯。但我們可以使用第三方解決方案。所以它可以是一個DevKit,它可以是一個表單生成器,它可以是一個HOC或包裝器組件,將表單驗證邏輯混合到任意形式的內容中。我個人的方法是一個包裝組件,它依賴於HTML內置的表單驗證API並在表單和表單域的範圍內公開有效性狀態。