优雅地提高 React 的表单页面的开发效率
Den Form
為什么叫 Den Form ? 可能是因?yàn)?丹鳳眼 非常迷人吧...
一個(gè)非常輕巧的 Form 實(shí)現(xiàn), gzip 體積只有 3kb, 可以很方便跨越組件層級獲取表單對象, 或者管理聯(lián)動更新
安裝
$ yarn add react-den-form 復(fù)制代碼基礎(chǔ)使用
Form 組件會在有 field 屬性的子組件上注入 onChange 事件, 并獲取其中的值
field 屬性是 Form 組件用于標(biāo)記哪一類子組件需要被監(jiān)管的字段, 并且也是用于校驗(yàn)更新的 key
field 可以重復(fù), 但是如果兩個(gè)子組件擁有相同的 field 值, 那么當(dāng)此類 field 需要更新時(shí), 兩個(gè)子組件都會進(jìn)行更新
import React from "react"; import Form from "react-den-form";export default () => {return (<div><Form onSubmit={({ data }) => console.log(data)}><input field="userName" /></Form></div>); }; 復(fù)制代碼當(dāng)我們輸入數(shù)據(jù)后, 按回車鍵, onSubmit 方法打印如下:
{userName: "333"} 復(fù)制代碼層級獲取數(shù)據(jù)不需要進(jìn)行額外處理
import React from "react"; import Form from "react-den-form";export default () => {return (<div><Form onChange={({ data }) => console.log(data)}><div><div><div><input field="userName" /></div><input field="password" /></div></div></Form></div>); }; 復(fù)制代碼當(dāng)我們輸入數(shù)據(jù)時(shí), onChange 方法打印如下:
{userName: "333", password: "555"} 復(fù)制代碼Form 表單嵌套不需要處理
有時(shí)候, 我們會有一些頁面結(jié)構(gòu)讓兩個(gè)不同的表單進(jìn)行嵌套, 如登錄時(shí), 驗(yàn)證碼的輸入框在用戶名和密碼中間, 而驗(yàn)證碼有單獨(dú)的請求. 當(dāng)然, 我們可以更換實(shí)現(xiàn)方式, 但是對于這類場景, DenForm 默認(rèn)處理了表單嵌套.
由于 Form 內(nèi)部有一個(gè) form 標(biāo)簽, 外層 onSubmit 會捕獲所有子組件的 onSubmit 事件, 但是 data 數(shù)據(jù)只會捕獲 當(dāng)前層級內(nèi)的 field 對象
export default () => {return (<div>{/* 此 Form 只會捕獲 userName及password, code被子Form攔截了 */}<Form onSubmit={({ data }) => console.log("1", data)}><input field="userName" />{/* 此 Form 只會捕獲 age */}<Form onSubmit={({ data }) => console.log("2", data)}><input field="code" /><button type="submit">此Submit會被最近的父級捕獲</button></Form><input field="password" /></Form></div>); }; 復(fù)制代碼跨組件的值獲取
當(dāng)我們輸入數(shù)據(jù)時(shí), onChange 方法打印如下:
{userName: "333", password: "555", subPassword: "666"} 復(fù)制代碼自定義 Field 組件
如果我們自己定義的特殊組件, 需要滿足兩個(gè)條件:
:art: 使用 onChangeGetter 獲取自定義組件的值
以下標(biāo)簽, From 會自動識別 onChange 的返回值, 進(jìn)行解析獲取
import React from "react"; import Form from "react-den-form";export default () => {return (<div><Form onChange={(...args) => console.log(args)}><input field="userName" /><textarea field="password" /><select field="loginType"><option value="signUp">Sign up</option><option value="signIn">Sign in</option></select></Form></div>); }; 復(fù)制代碼我們自己定義的特殊組件, 如果它們的 onChange 的返回值結(jié)構(gòu)不確定, 我們可以編寫 onChangeGetter 屬性:
import React from "react"; import Form from "react-den-form";class SubInput extends React.Component {// 假定數(shù)據(jù)有一定的層級inputData = {value: ""};handleOnChange = e => {this.inputData.value = e.target.value;this.props.onChange(this.inputData);};render() {return <input onChange={this.handleOnChange} />;} }export default () => {return (<div><Form onChange={({ data }) => console.log(data)}><SubInput field="userName" onChangeGetter={e => e.value} /></Form></div>); }; 復(fù)制代碼onChangeGetter 的默認(rèn)值相當(dāng)于 onChangeGetter={e => e}
表單提交
以下三個(gè)情形為都會觸發(fā) Form 的 onSubmit 函數(shù):
- 包含 field 屬性的對象中, 使用鍵盤的回車鍵
- 包含 submit 屬性, 點(diǎn)擊(onClick)
- 包含 type="submit" 屬性的對象中, 點(diǎn)擊(onClick)
異步請求提交
Form 表單內(nèi)部并無封裝請求行為, 請?jiān)?onSubmit 事件中自行處理, 如:
import React from "react"; import Form from "react-den-form";function fetchLogin({ data }) {fetch("/api/login", { method: "post", body: JSON.stringify(data) }).then(res => {return res.json();}).then(data => {console.log(data);}); }export default () => {return (<div><Form onSubmit={fetchLogin}><input field="userName" /><input field="password" /></Form></div>); }; 復(fù)制代碼上下文獲取數(shù)據(jù)
我們?yōu)?Form 顯式注入一個(gè) data, 當(dāng)數(shù)據(jù)變化時(shí), data 的值也會變化, 這樣可以在上下文獲取 Form 的數(shù)據(jù)
// React.Component 版本 import React from "react"; import Form from "react-den-form";export default class extends React.Component {data = {};render() {return (<div><Form data={this.data}><input field="userName" /><button onClick={() => console.log(this.data)}>show-data</button></Form></div>);} } 復(fù)制代碼// useHooks 版本 import React, { useState } from "react"; import Form from "react-den-form";export default () => {const [data] = useState({});return (<div><Form data={data}><input field="userName" /><button onClick={() => console.log(data)}>show-data</button></Form></div>); }; 復(fù)制代碼輸入數(shù)據(jù), 點(diǎn)擊 button, data 數(shù)據(jù)打印如下:
{ userName: "dog" } 復(fù)制代碼表單校驗(yàn)
表單校驗(yàn)是無痛的, 并且是高效的
我們給 input 組件添加 errorcheck 屬性, 該屬性可以是一個(gè)正則對象, 也可以是一個(gè) 函數(shù), 如果 errorcheck 校驗(yàn)的結(jié)果為 false, 就會將其他 error 相關(guān)的屬性賦予至組件中
如下代碼, 如果 input 內(nèi)容不包含 123, 字體顏色為紅色:
import "./App.css"; import React from "react"; import Form from "react-den-form";export default () => {return (<div><Form><inputfield="userName"errorcheck={/123/}errorstyle={{ color: "#f00" }}/></Form></div>); }; 復(fù)制代碼表單校驗(yàn)相關(guān)的 api:
| errorcheck | 正則或函數(shù) | 若返回值為 false, 將其他 error Api 應(yīng)用至組件中 |
| errorstyle | style 對象 | 若校驗(yàn)為錯(cuò)誤, 將 errorstyle 合并至 style 中 |
| errorclass | className 字符串 | 若校驗(yàn)為錯(cuò)誤, 將 errorstyle 合并至 className 中 |
| errorprops | props 對象 | 若校驗(yàn)為錯(cuò)誤, 將 errorprops 合并至整個(gè) props 中 |
為什么是 errorcheck 而不是 errorCheck ? 這是因?yàn)?React 對 DOM 元素屬性的定義為 lowercass:
Warning: React does not recognize the `errorCheck` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `errorcheck` instead. If you accidentally passed it from a parent component, remove it from the DOM element. 復(fù)制代碼表單校驗(yàn)時(shí)進(jìn)行特殊處理
如果我們有一個(gè)需求, 當(dāng)表單校驗(yàn)錯(cuò)誤時(shí), 顯示一個(gè)提示信息, 當(dāng)表單校驗(yàn)通過時(shí), 取消提示信息, 我們就需要對每次校驗(yàn)有差異時(shí), 進(jìn)行處理
使用 onChange 方法每次都會被執(zhí)行, 可是我們只希望在表單校驗(yàn)結(jié)果有變化時(shí)進(jìn)行提示
Form 提供了一個(gè) onErrorCheck 的屬性, 滿足以上需求
import React from "react"; import Form from "react-den-form";export default () => {return (<div>{/* 只有當(dāng) input內(nèi)容校驗(yàn)結(jié)果發(fā)生變化時(shí), onErrorCheck 才會執(zhí)行 */}<Form onErrorCheck={({ isError, data }) => console.log(isError, data)}><inputfield="userName"errorcheck={/123/}errorstyle={{ color: "#f00" }}/></Form></div>); }; 復(fù)制代碼聯(lián)動
當(dāng)我們修改一個(gè)對象時(shí), 根據(jù)某些條件, 希望修改另一個(gè)對象的行為我們稱之為聯(lián)動
DenForm 的聯(lián)動是高性能的, 僅更新需要更新的對象
我們可以在任何 Form 的回調(diào)函數(shù)中使用 update 進(jìn)行更新某個(gè)被 field 捆綁的組件的 value 或者 props
下面這個(gè)例子: 1. 當(dāng)在 password 輸入時(shí), 會將 userName 的 input 框內(nèi)容改為 'new value'; 2. 當(dāng) userName 的 input 的內(nèi)容包含 'aa' 時(shí), 會將 password 的 value 和 style 進(jìn)行修改;
import React from "react"; import Form from "react-den-form";export default () => {return (<div><FormonChange={({ data, field, update }) => {if (field === "password") {update({ userName: "new value" });}if (/aa/.test(data.userName)) {update({password: {value: "new value and style",style: { fontSize: 30 }}});}}}><input field="userName" /><input field="password" /></Form></div>); }; 復(fù)制代碼性能開銷
Form 存在的意義在于簡化開發(fā), 用計(jì)算機(jī)的時(shí)間換取開發(fā)者的時(shí)間, 所以會有一些性能開銷.
但是 Form 的開銷絕對不大, 因?yàn)?Form 內(nèi)部更新時(shí)只會針對指定的子組件進(jìn)行更新.
如果因?yàn)槭褂?Form 遇到了性能問題, 請檢查以下情況:
- 請減少 Form 內(nèi)部子組件的個(gè)數(shù), 最好不要超過 100 個(gè)
- 在一個(gè)無限長的滾動列表外包裹 Form 時(shí), 請盡量使用 react-virtualized 或 react-window 類型的虛擬 List 組件, 以減少 Form 包裹的內(nèi)容個(gè)數(shù)
- 如果 Form 子組件的個(gè)數(shù)過多時(shí), 請確保 Form 組件不會由外部頻繁更新, 或者添加 shouldUpdate={false} 至 Form 中: <Form shouldUpdate={false} >{...}</Form>
我們有理由相信, 在一個(gè)設(shè)計(jì)合理的應(yīng)用中, 每個(gè) Form 包裹的組件個(gè)數(shù)應(yīng)該是有限的
支持哪些 React 渲染層 ?
此庫支持所有 React 的渲染層, 如 ReactDOM, ReactNative, ReactVR, 但是非 ReactDOM 中, 需要初始化事件類型
如 ReactNative 中, 在項(xiàng)目之初設(shè)定:
import { immitProps } from "react-den-form";// 設(shè)定 ReactNative 中的讀取值和更新值的監(jiān)聽屬性: immitProps.value = "value"; immitProps.change = "onChange"; immitProps.click = "onPress"; 復(fù)制代碼API
Form API
| data | 用于捆綁上下文的 data 對象, 也會當(dāng)成默認(rèn)值設(shè)置到相應(yīng)的 field 監(jiān)管的組件中 | Object: {:} | {} | -- |
| onChange | 當(dāng)有 field 監(jiān)管的子組件執(zhí)行 onChange 時(shí), 進(jìn)行回調(diào) | (IEvents) => void | undefined | -- |
| onSubmit | 當(dāng)有表單進(jìn)行提交時(shí), 進(jìn)行回調(diào) | (IEvents) => void | undefined | -- |
| onErrorCheck | 當(dāng)有 field 監(jiān)管的子組件的錯(cuò)誤校驗(yàn)結(jié)果發(fā)生變化時(shí), 進(jìn)行回調(diào) | (IEvents) => void | undefined | -- |
IEvents API (回調(diào)函數(shù)的參數(shù))
Form 組件回調(diào)函數(shù)的參數(shù)如下: ({isError, event, data, field, value, element, update }) => void
具體的 API 描述如下:
| isError | 最后輸入的對象校驗(yàn)是否正確 | Boolean | false | true |
| event | onChange 的原始返回對象 | Boolean | undefined | false |
| data | form.data 對象, 包含所有 field 監(jiān)管的值 | Object: {:} | {} | true |
| field | 當(dāng)前修改的組件的 field 屬性 | String | undefined | true |
| value | 當(dāng)前修改的值 | Boolean | '' | String or Number |
| element | 當(dāng)前修改的 ReactElement | Boolean | React.Element | true |
| update | 更新某個(gè) field 監(jiān)管的對象 | (IEvents)=> {:} | (IEvents)=> {:} | true |
子組件關(guān)聯(lián)參數(shù)
一個(gè)子組件可以被識別關(guān)聯(lián)的參數(shù)如下
<inputfield="userName"submittype="submit"errorcheck={/123/}errorstyle={{ color: "#f00" }}errorclass="input-error-style"errorprops={{ disable: true }} /> 復(fù)制代碼具體的 API 描述如下:
| field | 用于標(biāo)記組件是否被 Form 組件監(jiān)聽, 并且也是 data 用于存放值的 key | String | undefined | |
| submit | 用于確定當(dāng)前對象是否可以響應(yīng) onClick 事件進(jìn)行提交 | Boolean | undefined | -- |
| toform | 注入ToForm 組件, 讓子組件對象也可以概括子表單 | Boolean | undefined | |
| type | 當(dāng) type = "submit" 時(shí), 可以響應(yīng) onClick 事件進(jìn)行提交 | Object: {:} | undefined | -- |
| errorcheck | 用于校驗(yàn)當(dāng)前對象 value 是否錯(cuò)誤 | 正則對象或函數(shù) | undefined | -- |
| errorstyle | 當(dāng)前對象 value 錯(cuò)誤時(shí), 合并 errorstyle 至 style | Object | undefined | -- |
| errorclass | 當(dāng)前對象 value 錯(cuò)誤時(shí), 合并 errorclass 至 className | String | undefined | -- |
| errorprops | 當(dāng)前對象 value 錯(cuò)誤時(shí), 合并 errorprops 至 props | Object | undefined | -- |
以上就是全部, 希望 Den Form 能夠幫到你解決 React 表單相關(guān)的痛點(diǎn) :)
轉(zhuǎn)載于:https://juejin.im/post/5c977e075188252d785f20b5
總結(jié)
以上是生活随笔為你收集整理的优雅地提高 React 的表单页面的开发效率的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 说说 Spring 事务管理的实现类
- 下一篇: ^m 问题处理