前端的Form 表單主要用於解決數據獲取、數據校驗、數據賦值 這三大類問題。這篇文章裏面的提供的解決方案可以比較完美的用在 React 框架上,可是解決問題的思路相信應該是可使用於任何框架語言。html
中後臺的表單組件已經不只僅有 input 和 select,可能還擴展到 範圍選擇器、日期選擇器 等,這些組件每每爲了實現更優雅的UI和更使用的交互會在原生的組件上面作多層封裝,而通過多層疊加後可能已經看不到原生表單元素的影子了。好比通過封裝下面這段 DOM 結構通過樣式修改也可能成爲一個輸入組件,雖然徹底看不到 input 的影子。前端
<span>
<span contentEditable></span>
</span>複製代碼
因此爲了便於你們理解我這裏從傳統的原生 form 提及,好讓你們有一個遞進的過程。react
最初始的一份代碼以下,代碼很簡單,看着也很舒服。git
<form action="/api/post" method="post">
username: <input name="username" />
passowrd: <input name="password" />
<button type="submit">submit</button>
</form>複製代碼
可是你開始作數據校驗相關,表單就馬上變得複雜多了。以下:代碼增多了一倍。github
<script>
function checkname(target) {
const value = target.value;
if (value.length < 10) {
document.getElementById('username_msg').innerHTML = '長度必須>10'
} else {
document.getElementById('username_msg').innerHTML = ''
}
}
function checkpassword(target) {
const value = target.value;
if (!value.match(/^[\w]{6,16}$/)) {
document.getElementById('password_msg').innerHTML = '密碼必須 6-16 位字母數字'
} else {
document.getElementById('password_msg').innerHTML = ''
}
}
function getInitData() {
ajax({
url:'/api/data',
success:function(data) {
document.getElementById('username') = data.username;
});
}
getInitData();
</script>
<form action="/api/post" method="post">
username: <input name="username" onchange="checkname(this)"/>
<span id="username_msg"></span>
passowrd: <input name="password" onchange="checkpassword(this)"/>
<span id="password_msg"></span>
<button type="submit">submit</button>
</form>複製代碼
若是把DOM的部分也用JS來實現,基本能夠作到只修改JS不須要再動DOM結構,可是也讓JS的複雜度增高很多。ajax
React 裏面全部的DOM結構都是本身經過JS 生成的,JSX也能夠方便的實現DOM結構。但這裏我拿原生表單舉例,只是想說用 React 寫出來的原生表單,並不比用原生 js 的優雅多少!!!正則表達式
一樣一段最簡單的功能,套在 react 框架下面是這個樣子。後端
class Demo extends React.Component {
render() {
return <form action="/api/post" method="post">
username: <input name="username" />
passowrd: <input name="password" />
<button type="submit">submit</button>
</form>
}
}複製代碼
好比一樣想要實現校驗輸入自動校驗 和 賦值,看下面一段代碼,想一想就是一大堆事情要作。api
class Demo extends React.Component {
state = {
username: '',
password: '',
usernameMsg: '',
passwordMsg: '',
};
checkname = e => {
// 獲取數據
const value = e.target.value;
// 受控模式賦值
this.setState({
username: value,
});
// 校驗數據
if (value.length < 10) {
this.setState({
usernameMsg: '長度必須>10',
});
} else {
this.setState({
usernameMsg: '',
});
}
};
checkpassword = e => {
// 獲取數據
const value = e.target.value;
// 受控模式賦值
this.setState({
password: value,
});
// 校驗數據
if (!value.match(/^[\w]{6,16}$/)) {
this.setState({
passwordMsg: '密碼必須 6-16 位字母數字',
});
} else {
this.setState({
passwordMsg: '',
});
}
};
handleSubmit = () => {
ajax({
url: '/api/post',
data: {
username: this.state.username,
password: this.state.password,
},
success: () => {
// success
},
});
};
render() {
// 獲取數據和錯誤信息
const { username, password, usernameMsg, passwordMsg } = this.state;
return (
<form action="/api/post" method="post">
username: <input value={username} onChange={this.checkname} />
<span>{usernameMsg}</span>
passowrd: <input value={password} onChange={this.checkpassword} />
<span>{passwordMsg}</span>
<button type="submit" onClick={this.handleSubmit}>
submit
</button>
</form>
);
}
}複製代碼
代碼有點長,可是基本能夠總結出一個現象,要想實現表單數據獲取、校驗,基本離不開 onChange 這個方法,並且是有幾個表單控件,就要寫幾個 onChange 。(以上代碼可直接運行,能夠在 codepen.io/frankqian/p… 調試)數組
其實這裏和框架並無什麼關係,由於無論用什麼框架要想作到 賦值和校驗 這兩個功能,基本必定要在 input 上面綁定 onChange。 因此若是有個通用的工具能夠自動幫你把這些onChange的綁定都作了,再把校驗規則固定下,是否是全部的表單問題均可以解決了呢?是的通用表單解決方案就是按照這種思路設計出來的!
全部的用 React 寫成的組件均可以使用該方案。甚至 非 React 體系也可使用改思路來解決問題。
基於全部表單控件都須要綁定 onChange 作數據獲取和校驗的原則,因此我設計了一個 Field 工具。這個工具原理很簡單,就是能夠自動幫你綁定 value + onChange 解決上面一長串代碼的問題。
const field = new Field(this);
field.init('username');複製代碼
field.init 會自動返回 value + onChange ,內容以下:
{
value: "",
onChange: ƒ ()
}複製代碼
下面這張圖簡單表面 Field 和 React 體系之間的關係。
import {Field} from '@alifd/next';
class Demo extends React.Component {
field = new Field(this);
handleSubmit = () => {
console.log(this.field.getValues()); // 獲取數據
}
render() {
const {init} = this.field;
return <form>
username: <input {...init('username')} />
passowrd: <input {...init('password')} />
<button onClick={this.handleSubmit} >submit</button>
</form>
}
}複製代碼
這樣一個表單的數據獲取問題就解決了,代碼簡潔了不少。 Demo 在這裏 codepen.io/frankqian/p… 能夠本身調試
既然可以獲取到數據了,那邊表單校驗是順手的事情,由於校驗只依賴數據。咱們只須要對集中固定的交互性形式和校驗規則作抽象就行了。
規則名稱 |
描述 |
類型 |
觸發條件/數據類型 |
required | 不能爲空 | Boolean | undefined/null/「」/[] |
pattern | 校驗正則表達式 | 正則 | |
minLength | 字符串最小長度 / 數組最小個數 | Number | String/Number/Array |
maxLength | 字符串最大長度 / 數組最大個數 | Number | String/Number/Array |
length | 字符串精確長度 / 數組精確個數 | Number | String/Number/Array |
min | 最小值 | Number | String/Number |
max | 最大值 | Number | String/Number |
format | 對經常使用 pattern 的總結 url/email/tel/number |
String | String |
validator | 自定義校驗 | Function |
這裏說明下表單是弱類型的數據。好比 input 框裏面你但願用戶輸入的是整數,返回的 value 類型可能有兩種
這個時候要求用戶必定要返回 Number 類型才能校驗很是不友好,因此在 Field 校驗邏輯裏面就把類型的問題處理掉了,而不是交給用戶去判斷。
上面是小插曲,咱們繼續看以下 Field + 表單的代碼,解決了數據獲取、表單校驗的全部功能
import { Field } from '@alifd/next';
class Demo extends React.Component {
field = new Field(this);
handleSubmit = (e) => {
e.preventDefault();
this.field.validate(); // 自定義校驗
console.log(this.field.getValues()); // 獲取數據
}
render() {
const {init, getError} = this.field;
return <form>
username: <input {...init('username', {rules: { required: true, minLength: 10}})} />
<span style={{color: 'red'}}>{getError('username')}</span> {/**錯誤信息**/}
passowrd: <input {...init('password', {rules: {
pattern: /^[\w]{6,16}$/,
message: '密碼必須 6-16 位字母數字'
}})} />
<span style={{color: 'red'}}>{getError('password')}</span> {/**錯誤信息**/}
<button onClick={this.handleSubmit} >validate</button>
</form>
}
}複製代碼
這樣以前可能須要 70 行的代碼 24 行就能夠解決了,可讓代碼清晰很多。調試demo見: codepen.io/frankqian/p…
如今不少React 組件是在原生組件之上又作了封裝,還有不少組件可能並無包裹表單元素(好比 Fusion Select裏面並無 select 元素,下拉框是本身作的 )。可是隻要你本身寫的組件也遵循表單的規則就可使用該方案。
這個規則其實來自原生 html 的組件,咱們本身寫的組件只要按照標準來均可以使用 Field。
本身寫的組件比起原生的表單組件會更加美觀,交互更友好。只要遵循規範都能在 field 裏面使用,詳細demo 見 codepen.io/frankqian/p…
還有一些其餘更加細粒度的規則,是爲了讓你的組件更加好的適配高級功能,好比:
componentWillReceiveProps(nextProps) {
if ('value' in nextProps ) {
this.setState({
value: nextProps.value === undefined? []: nextProps.value // 設置組件的被清空後的數值
})
}
}複製代碼
Fusion Next 的表單組件基本都已是按照這套規範標準實現了,詳細能夠查看這裏的文檔 fusion.design/component/f… 拉到最下面
上面知道了 Field 能夠解決校驗、獲取、賦值等數據方面的問題,可是並不能解決 UI 和 交互的問題,在佈局和錯誤展現的時候須要本身來控制。
場景的佈局有水平 inline 佈局、垂直的分欄佈局,經過 FormItem 的 api 能夠很是輕鬆的作到
<Form>
<FormItem label="Username:">
<Input name="first" placeholder="first"/>
<Input name="second" placeholder="second"/>
</FormItem>
<FormItem label="Password:" required>
<Input htmlType="password" name="pass" placeholder="Please enter your password!"/>
</FormItem>
<FormItem label=" ">
<Form.Submit>Submit</Form.Submit>
</FormItem>
</Form>複製代碼
<Form inline>...</Form>複製代碼
<Form labelAligin="inset">...</Form>複製代碼
出錯的時候自動展現錯誤信息,不須要本身 getError 判斷。 每種狀態怎麼展示由各自的組件本身實現。減小和Form的耦合
每一個組件的加載中、成功、失敗,都由組件本身實現,Form 只是在校驗的時候傳遞 state 給各個組件,這樣不須要 Form 去關心每一個組件應該展示爲何樣!
<Input state="error" /> // 錯誤狀態
<Input state="loading" /> // 加載中
<Input state="success" /> // 成功
<DatePicker state="error" /> // 錯誤狀態複製代碼
以上咱們仍是 Field + Form 配合來使用的,代碼基本是這個樣子。
import { Form, Input, Field, Button } from '@alifd/next';
const FormItem = Form.Item;
class Demo extends React.Component {
field = new Field(this);
handleSubmit = () => {
this.field.validate();
}
render() {
const {init} = this.field;
return <Form field={this.field}>
<FormItem label="Username:">
<Input {...init('username', {
rules: {required}
})} />
</FormItem>
<FormItem label="Password:">
<Input {...init('password', {
rules: {pattern:/[\w]{6,16}/}
})} htmlType="password" />
</FormItem>
<FormItem label=" ">
<Button onClick={this.handleSubmit} >Submit</Button>
</FormItem>
</Form>
}
}複製代碼
可能寫多了以後就會想,每一個組件都要使用 init 、都須要寫 rules 規則,並且在 jsx 中寫一大串的 JSON 數據。
是否有方法讓數據獲取和校驗變得更簡單,讓代碼再進一步的簡化呢?
針對以上問題對 Form 進一步優化,把 Field 的能力整合進了 Form,而把 Field 的用法進一步弱化,讓你們不須要再關心 init/取數據 等問題。代碼以下:
import { Form, Input, Button } from '@alifd/next';
const FormItem = Form.Item;
class Demo extends React.Component {
handleSubmit = (values, errors) => {
if (errors) {
// 校驗出錯
return;
}
console.log(values) // 獲取數據
}
render() {
return <Form>
<FormItem label="Username:" required>
<Input name"username" />
</FormItem>
<FormItem label="Password:" pattern={/[\w]{6,16}/}>
<Input name="password" htmlType="password" />
</FormItem>
<FormItem label=" ">
<Form.Submit validate onClick={this.handleSubmit} >Submit</Form.Submit>
</FormItem>
</Form>
}
}複製代碼
上面代碼中能夠看出幾個優化點:
Form 的優化必定不會僅僅止於此,由於在實際業務中會遇到更加複雜的功能。
不少業務爲了更加方便快捷,會抽象經常使用的組件佈局,經過後端接口吐出JSON schema的方式直接在前端動態展現表單,雖然比較業務化當時確實方便快捷,可以極大的解決效率問題;
又或者把經常使用的表單類場景作成業務組件、模塊模板,在使用的時候直接下載使用。好比:Fusion的表單類模塊:fusion.design/module?cate…
方案不少,總有適合本身的一套。