Den Form
為什么叫 Den Form ? 可能是因為
丹鳳眼
非常迷人吧...
一個非常輕巧的 Form 實現(xiàn), gzip 體積只有 3kb, 可以很方便跨越組件層級獲取表單對象, 或者管理聯(lián)動更新
安裝
$ yarn add react-den-form
基礎(chǔ)使用
Form 組件會在有 field 屬性的子組件上注入 onChange 事件, 并獲取其中的值
field 屬性是 Form 組件用于標(biāo)記哪一類子組件需要被監(jiān)管的字段, 并且也是用于校驗更新的 key
field 可以重復(fù), 但是如果兩個子組件擁有相同的 field 值, 那么當(dāng)此類 field 需要更新時, 兩個子組件都會進行更新
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>
);
};
當(dāng)我們輸入數(shù)據(jù)后, 按回車鍵, onSubmit 方法打印如下:
{userName: "333"}
主動傳入的 onChange 事件不會受到影響
Form 組件會在有 field 屬性的子組件上注入 onChange 事件, 并獲取其中的值
import React from "react";
import Form from "react-den-form";
export default () => {
return (
<div>
<Form onChange={({ data }) => console.log(data)} onError>
<input onChange={() => console.log("self-onChange")} field="userName" />
</Form>
</div>
);
};
當(dāng)我們輸入數(shù)據(jù)時, onChange 方法打印如下:
self-onChange
{userName: "333"}
多層級獲取數(shù)據(jù)不需要進行額外處理
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>
);
};
當(dāng)我們輸入數(shù)據(jù)時, onChange 方法打印如下:
{userName: "333", password: "555"}
子函數(shù)組件也不需額外處理
import React from "react";
import Form from "react-den-form";
function SubInput() {
return (
<div>
<div>
<input field="subPassword" />
</div>
</div>
);
}
export default () => {
return (
<div>
<Form onChange={({ data }) => console.log(data)}>
<div>
<div>
<div>
<input field="userName" />
</div>
<input field="password" />
<SubInput />
</div>
</div>
</Form>
</div>
);
};
當(dāng)我們輸入數(shù)據(jù)時, onChange 方法打印如下:
{userName: "333", password: "555", subPassword: "666"}
Form 組件嵌套不需要處理
由于 Form 內(nèi)部有一個 form 標(biāo)簽, 外層 onSubmit 會捕獲所有子組件的 onSubmit 事件, 但是 data 數(shù)據(jù)只會捕獲 當(dāng)前層級內(nèi)的 field 對象
export default () => {
return (
<div>
{/* 此 Form 只會捕獲 userName及password, age及vipLevel被子Form攔截了 */}
<Form onSubmit={({ data }) => console.log("1", data)}>
<input field="userName" />
<input field="password" />
{/* 此 Form 只會捕獲 age及vipLevel */}
<Form onSubmit={({ data }) => console.log("2", data)}>
<input field="age" />
<button type="submit">此Submit會被最近的父級捕獲</button>
</Form>
</Form>
</div>
);
};
自定義 Input 組件
如果我們自己定義的特殊組件, 需要滿足兩個條件:
- 組件外部的 props 需要設(shè)置 field 屬性
- 組件內(nèi)部需要使用 this.props.onChange 返回數(shù)據(jù)
import React from "react";
import Form from "react-den-form";
class SubInput extends React.Component {
handleOnChange = e => {
// 需要使用 this.props.onChange 返回數(shù)據(jù)
this.props.onChange(e.target.value);
};
render() {
return <input onChange={this.handleOnChange} />;
}
}
export default () => {
return (
<div>
<Form onChange={({ data }) => console.log(data)}>
{/* 需要設(shè)置 field 屬性 */}
<SubInput field="userName" />
</Form>
</div>
);
};
類組件的嵌套需要使用 toForm 進行處理
被 Form 組件包裹過的類組件的 props 都會有一個 toForm 的函數(shù)
類組件不同于函數(shù)組件, 需要使用 toForm 對其內(nèi)部的 render 返回值進行處理, 才可以識別其內(nèi)部有 field 屬性的對象, 將其劃分在 Form 組件的監(jiān)管范圍內(nèi):
import React from "react";
import Form from "react-den-form";
// 例子, 這是一個有表單的頁面, 使用類組件進行編寫
class HomePage extends React.Component {
render() {
// 需要使用 toForm 處理返回值
return this.props.toForm(
<div>
<nav>導(dǎo)航欄</nav>
<div>
<div>輸入?yún)^(qū)域</div>
<input field="userName" />
</div>
</div>
);
}
}
export default () => {
return (
<div>
<Form onChange={({ data }) => console.log(data)}>
<HomePage />
</Form>
</div>
);
};
使用 onChangeGetter 獲取自定義組件的值
以下標(biāo)簽, From 會自動識別 onChange 的返回值, 進行解析獲取
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>
);
};
我們自己定義的特殊組件, 如果它們的 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>
);
};
onChangeGetter
的默認(rèn)值相當(dāng)于onChangeGetter={e => e}
表單提交
以下三個情形為都會觸發(fā) Form 的 onSubmit 函數(shù):
- 包含 field 屬性的對象中, 使用鍵盤的回車鍵
- 包含 submit 屬性, 點擊(onClick)
- 包含 type="submit" 屬性的對象中, 點擊(onClick)
import React from "react";
import Form from "react-den-form";
export default () => {
return (
<div>
<Form onSubmit={({ data }) => console.log(data)}>
<input field="userName" />
<input submit field="password" />
<button type="submit" />
</Form>
</div>
);
};
fetch 提交
Form 表單內(nèi)部并無封裝請求行為, 請在 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>
);
};
上下文獲取數(shù)據(jù)
我們?yōu)?Form 顯式注入一個 data, 當(dāng)數(shù)據(jù)變化時, 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>
);
}
}
// 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>
);
};
輸入數(shù)據(jù), 點擊 button, data 數(shù)據(jù)打印如下:
{ userName: "dog" }
表單校驗
表單校驗是無痛的, 并且是高效的
我們給 input 組件添加 errorcheck 屬性, 該屬性可以是一個正則對象, 也可以是一個 函數(shù), 如果 errorcheck 校驗的結(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>
<input
field="userName"
errorcheck={/123/}
errorstyle={{ color: "#f00" }}
/>
</Form>
</div>
);
};
表單校驗相關(guān)的 api:
prop | 類型 | 用途 |
---|---|---|
errorcheck | 正則或函數(shù) | 若返回值為 false, 將其他 error Api 應(yīng)用至組件中 |
errorstyle | style 對象 | 若校驗為錯誤, 將 errorstyle 合并至 style 中 |
errorclass | className 字符串 | 若校驗為錯誤, 將 errorstyle 合并至 className 中 |
errorprops | props 對象 | 若校驗為錯誤, 將 errorprops 合并至整個 props 中 |
為什么是 errorcheck 而不是 errorCheck ? 這是因為 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.
表單校驗時進行特殊處理
如果我們有一個需求, 當(dāng)表單校驗錯誤時, 顯示一個提示信息, 當(dāng)表單校驗通過時, 取消提示信息, 我們就需要對每次校驗有差異時, 進行處理
使用 onChange 方法每次都會被執(zhí)行, 可是我們只希望在表單校驗結(jié)果有變化時進行提示
Form 提供了一個 onErrorCheck 的屬性, 滿足以上需求
import React from "react";
import Form from "react-den-form";
export default () => {
return (
<div>
{/* 只有當(dāng) input內(nèi)容校驗結(jié)果發(fā)生變化時, onErrorCheck 才會執(zhí)行 */}
<Form onErrorCheck={({ isError, data }) => console.log(isError, data)}>
<input
field="userName"
errorcheck={/123/}
errorstyle={{ color: "#f00" }}
/>
</Form>
</div>
);
};
聯(lián)動
當(dāng)我們修改一個對象時, 根據(jù)某些條件, 希望修改另一個對象的行為我們稱之為聯(lián)動
我們可以在任何 Form 的回調(diào)函數(shù)中使用 update 進行更新某個被 field 捆綁的組件的 value 或者 props
下面這個例子: 1. 當(dāng)在 password 輸入時, 會將 userName 的 input 框內(nèi)容改為 'new value'; 2. 當(dāng) userName 的 input 的內(nèi)容包含 'aa' 時, 會將 password 的 value 和 style 進行修改;
import React from "react";
import Form from "react-den-form";
export default () => {
return (
<div>
<Form
onChange={({ 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>
);
};
性能開銷
Form 存在的意義在于簡化開發(fā), 用計算機的時間換取開發(fā)者的時間, 所以會有一些性能開銷.
但是 Form 的開銷絕對不大, 因為 Form 內(nèi)部更新時只會針對指定的子組件進行更新.
- 每個包含 field 屬性的子組件都相當(dāng)于一個受控組件, 當(dāng)子組件 onChange 時, 此子組件會進行更新
- Form 組件聲明或被外部更新時會去查詢當(dāng)前 JSX 對象中的所有子組件是否包含 field 或者 submit 屬性, 如果包含, 則注入 onChange 或 onClick; 如果不希望 Form 被外部更新, 請聲明
<Form shouldUpdate={false} >{...}</Form>
如果因為使用 Form 遇到了性能問題, 請檢查以下情況:
- 請減少 Form 內(nèi)部子組件的個數(shù), 最好不要超過 100 個
- 在一個無限長的滾動列表外包裹 Form 時, 請盡量使用 react-virtualized 或 react-window 類型的虛擬 List 組件, 以減少 Form 包裹的內(nèi)容個數(shù)
- 如果 Form 子組件的個數(shù)過多時, 請確保 Form 組件不會由外部頻繁更新, 或者添加 shouldUpdate={false} 至 Form 中:
<Form shouldUpdate={false} >{...}</Form>
我們有理由相信, 在一個設(shè)計合理的應(yīng)用中, 每個 Form 包裹的組件個數(shù)應(yīng)該是有限的
支持哪些 React 渲染層 ?
此庫支持所有 React 的渲染層, 如 ReactDOM, ReactNative, ReactVR, 但是非 ReactDOM 中, 需要初始化事件類型
如 ReactNative 中, 在項目之初設(shè)定:
import { immitProps } from "react-den-form";
// 設(shè)定 ReactNative 中的讀取值和更新值的監(jiān)聽屬性:
immitProps.value = "value";
immitProps.change = "onChange";
immitProps.click = "onPress";
API
Form API
屬性 | 描述 | 類型 | 默認(rèn)值 | 必填 |
---|---|---|---|---|
data | 用于捆綁上下文的 data 對象, 也會當(dāng)成默認(rèn)值設(shè)置到相應(yīng)的 field 監(jiān)管的組件中 | Object: {<field>:<value>} | {} | -- |
onChange | 當(dāng)有 field 監(jiān)管的子組件執(zhí)行 onChange 時, 進行回調(diào) | (IEvents) => void | undefined | -- |
onSubmit | 當(dāng)有表單進行提交時, 進行回調(diào) | (IEvents) => void | undefined | -- |
onErrorCheck | 當(dāng)有 field 監(jiān)管的子組件的錯誤校驗結(jié)果發(fā)生變化時, 進行回調(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 描述如下:
屬性 | 描述 | 類型 | 默認(rèn)值 | 必傳 |
---|---|---|---|---|
isError | 最后輸入的對象校驗是否正確 | Boolean | false | true |
event | onChange 的原始返回對象 | Boolean | undefined | false |
data | form.data 對象, 包含所有 field 監(jiān)管的值 | Object: {<field>:<value>} | {} | true |
field | 當(dāng)前修改的組件的 field 屬性 | String | undefined | true |
value | 當(dāng)前修改的值 | Boolean | '' | String or Number |
element | 當(dāng)前修改的 ReactElement | Boolean | React.Element | true |
update | 更新某個 field 監(jiān)管的對象 | (IEvents)=> {<field>:<value>} | (IEvents)=> {<field>:<value>} | true |
子組件關(guān)聯(lián)參數(shù)
一個子組件可以被識別關(guān)聯(lián)的參數(shù)如下
<input
field="userName"
submit
type="submit"
errorcheck={/123/}
errorstyle={{ color: "#f00" }}
errorclass="input-error-style"
errorprops={{ disable: true }}
/>
具體的 API 描述如下:
屬性 | 描述 | 類型 | 默認(rèn)值 | 必傳 | |
---|---|---|---|---|---|
field | 用于標(biāo)記組件是否被 Form 組件監(jiān)聽, 并且也是 data 用于存放值的 key | String | undefined | -- | |
submit | 用于確定當(dāng)前對象是否可以響應(yīng) onClick 事件進行提交 | Boolean | undefined | -- | |
type | 當(dāng) type = "submit" 時, 可以響應(yīng) onClick 事件進行提交 | Object: {<field>:<value>} | undefined | -- | |
errorcheck | 用于校驗當(dāng)前對象 value 是否錯誤 | 正則對象或函數(shù) | undefined | -- | |
errorstyle | 當(dāng)前對象 value 錯誤時, 合并 errorstyle 至 style | Object | undefined | -- | |
errorclass | 當(dāng)前對象 value 錯誤時, 合并 errorclass 至 className | String | undefined | -- | |
errorprops | 當(dāng)前對象 value 錯誤時, 合并 errorprops 至 props | Object | undefined | -- |