閱讀本文須要對如下幾項有必定了解javascript
文章中大量用到了 ES6 語法,好比解構賦值和函數參數默認值、剩餘參數、展開語法、箭頭函數等。html
React 在 16.8 版本中推出了 Hooks,它容許你在「函數組件」中使用「類組件」的一些特性。java
React 自己提供了一些 Hooks,好比 useState、useReducer 等。經過在一個以「use」做爲命名起始的函數中調用這些 Hooks,就獲得了一個 custom Hook(自定義 Hook)。react
Custom Hooks 容許咱們把任何邏輯封裝到其中,以便於複用足夠小的組件邏輯。正則表達式
當咱們把像 <input>
<textarea>
和 <select>
這樣的 HTML 元素自己的狀態交給 React state 去管理,咱們就獲得了一個「受控組件」。數組
一個與 React 契合良好的 CSS in JS 庫。它容許你使用 JS 編寫樣式,並編譯成純 CSS 文件。函數
下面代碼中全部的樣式都是使用它編寫的。若是對代碼中樣式的實現不是很感興趣的話, 這個能夠跳過。flex
首先咱們須要實現一個 Input 組件,咱們將在該組件的基礎上進行輸入、校驗並提示。ui
Input.js
spa
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const Wrap = styled.div({
display: 'flex',
flexDirection: 'column',
label: { display: 'flex', alignItems: 'center' },
input: {
marginLeft: 8,
},
p: {
color: 'red',
},
});
function Input({ label, type, helperText, error, ...otherProps }) {
return (
<Wrap> <label> {label}: <input {...otherProps} type={type} /> </label> {error && <p>{helperText}</p>} </Wrap> ); } Input.propTypes = { label: PropTypes.string, type: PropTypes.string, helperText: PropTypes.string, error: PropTypes.bool, }; export default Input; 複製代碼
該組件主要接收如下幾個 props:
label
label 標籤的文本type
賦值給原生 input 標籤的 type 屬性error
數據類型爲 Boolean,若是爲 true
則表示當前表單域有錯誤,即驗證不經過helperText
當前表單域驗證不經過時,顯示在表單域下方的提示文字otherProps
props 中除了上述四個之外的其餘屬性,所有賦值給原生 input 標籤有了 UI 組件以後,就能夠開始實現咱們的自定義 Hook 了。
useInput.js
import { useState } from 'react';
export default function useInput({
initValue = '',
helperText = '',
validator = () => true,
validateTriggers = ['onChange'],
} = {}) {
// 保存用戶輸入的值,使用 initValue 做爲初始值
const [value, setValue] = useState(initValue);
// Boolean 類型,表示當前表單項的驗證狀態
const [error, setError] = useState(false);
function onChange(e) {
const { value } = e.target;
setValue(value);
// 根據 validateTriggers 的選項,決定是否要在 onChange 裏進行校驗
if (validateTriggers.includes('onChange')) {
setError(!validator(value));
}
}
/**
* 根據 validateTriggers 生成相應的事件處理器
*/
function createEventHandlers() {
const eventHandlers = {};
validateTriggers.forEach(item => {
// 生成相應的事件處理器,並在其中作輸入校驗。
eventHandlers[item] = e => {
const { value } = e.target;
setError(!validator(value));
};
});
return eventHandlers;
}
const eventHandlers = createEventHandlers();
return {
value,
helperText,
error,
...eventHandlers,
onChange,
};
}
複製代碼
useInput
接收一個 options 對象做爲參數,考慮到擴展性,使用一個配置對象做爲參數比較好。
options 對象擁有如下幾個屬性:
initValue
輸入框的初始值helperText
當表單驗證不經過時顯示的字符串validator
用於進行表單驗證的函數,接收 value 做爲參數,並返回一個 Boolean 值,表示表單驗證是否經過validateTriggers
字符串數組,代表在哪一個或哪幾個事件中調用 validator 進行輸入校驗。在函數體中,咱們調用兩次 useState
來初始化 value
和 error
的值,分別保存用戶輸入的值和當前表單域的校驗結果。
而後,聲明一個 onChange
方法用來綁定 input 元素的 change 事件,在該方法中,咱們把用戶輸入的值賦值給 value
,同時根據 validateTriggers
的值,決定是否要在該方法中進行輸入校驗。該方法隨後會被返回出去,再做爲 props 傳遞給相應的組件,完成受控組件的狀態綁定。
咱們還須要聲明一個 createEventHandlers
方法,該方法經過遍歷 validateTriggers
,生成相應的事件處理器,並在這些事件處理器中進行輸入校驗。
最後咱們調用 createEventHandlers
方法,並把生成的 eventHandlers(事件處理器) 經過擴展運算符,插入到最終返回的對象中。
注意:這裏咱們須要把
onChange
放在最後,以避免帶有狀態綁定的onChange
方法被eventHandlers
中的onChange
覆蓋掉。
如今讓咱們來看看實際該如何使用:
import React from 'react';
import Input from './Input';
import useInput from './useInput';
// 用於驗證郵箱的正則表達式
const EMAIL_REG = /\S+@\S+\.\S+/;
export default function Form() {
const email = useInput({
initValue: '',
helperText: '請輸入有效的郵箱!',
validator: value => EMAIL_REG.test(value),
validateTriggers: ['onBlur'],
});
const password = useInput({
initValue: '',
helperText: '密碼長度須要在 6-20 之間!',
validator: value => value.length >= 6 && value.length <= 20,
validateTriggers: ['onChange', 'onBlur'],
});
/** * 判斷是否禁用按鈕 */
function isButtonDisabled() {
// 當郵箱或密碼未填寫時,或者郵箱或密碼輸入校驗未經過時,禁用按鈕
return !email.value || !password.value || email.error || password.error;
}
/** * 處理表單提交 */
function handleButtonClick() {
console.log('郵箱:', email.value);
console.log('密碼:', password.value);
}
return (
<div>
<Input {...email} label="郵箱" type="email" />
<Input {...password} label="密碼" type="password" />
<button disabled={isButtonDisabled()} onClick={handleButtonClick}>
登陸
</button>
</div>
);
}
複製代碼
這裏調用了兩次 useInput
,初始化 email 和 password 表單域數據。
而後使用擴展運算符,把值所有賦給 Input
組件。只用了幾行代碼就完成了定義初始值和受控組件的綁定,是否是很方便?
當咱們輸入郵箱的時候,並不會出現校驗提示,可是一旦從郵箱輸入框失去焦點之後,輸入的值就會被校驗,並根據校驗結果顯示相應的提示。而密碼輸入框,則會在輸入的過程當中和失焦後都進行校驗。
上面這個例子已經能夠處理基本的表單驗證,至於格式化用戶輸入的數據以及自定義收集表單域的值的時機等其餘需求,我就再也不演示了,你們能夠自行設計。這也是 Hooks 的特殊之處,它讓咱們能夠更容易的複用邏輯代碼,能夠根據須要自行編寫 custom Hooks。
文章中關於 useInput
的 API 設計只是衆多方案中的一種,只是爲你們提供一些參考。你也能夠把整個表單的狀態封裝到一個 useForm
方法中,統一管理全部表單域的狀態。
但願本文能爲你們帶來一些關於如何使用 Hooks 的靈感,即便歷來沒有使用過 Hooks,也強烈建議你們嘗試一下。我已經在項目中大量使用 Hooks 了,而且它也爲我帶來了很好的效果。