創建您自己的 React 驗證庫:功能(第 2 部分)

已發表: 2022-03-10
快速總結 ↬在 Kristofer 的上一篇文章中,他解釋瞭如何實現驗證庫的基本部分。 下一部分將側重於改善開發人員體驗,而今天的文章將側重於為第 1 部分中創建的內容添加更多功能。

實現一個驗證庫並不是那麼難。 也不是添加所有那些使您的驗證庫比其他功能更好的額外功能。

本文將繼續實現我們在本系列上一部分開始實現的驗證庫。 這些功能將把我們從一個簡單的概念證明變成一個實際可用的庫!

  • 第 1 部分:基礎知識
  • 第 2 部分:功能
  • 第 3 部分:體驗

僅在提交時顯示驗證

由於我們正在驗證所有更改事件,因此我們太早地顯示用戶錯誤消息而無法獲得良好的用戶體驗。 有幾種方法可以緩解這種情況。

第一個解決方案只是提供submitted的標誌作為useValidation掛鉤的返回屬性。 這樣,我們可以在顯示錯誤消息之前檢查表單是否已提交。 這裡的缺點是我們的“顯示錯誤代碼”變得有點長:

 <label> Username <br /> <input {...getFieldProps('username')} /> {submitted && errors.username && ( <div className="error">{errors.username}</div> )} </label>

另一種方法是提供第二組錯誤(我們稱它們為submittedErrors ),如果submitted為假,則為空對象,如果為真,則為errors對象。 我們可以這樣實現:

 const useValidation = config => { // as before return { errors: state.errors, submittedErrors: state.submitted ? state.errors : {}, }; }

這樣,我們可以簡單地解構出我們想要顯示的錯誤類型。 當然,我們也可以在調用站點執行此操作——但通過在此處提供它,我們只是實現了一次,而不是在所有消費者內部實現。

  • 請參閱 CodeSandbox 演示,展示如何使用submittedErrors
跳躍後更多! 繼續往下看↓

模糊顯示錯誤消息

很多人希望在離開某個領域後看到錯誤。 我們可以添加對此的支持,通過跟踪哪些字段被“模糊”(導航離開),並返回一個對象blurredErrors ,類似於上面的submittedErrors錯誤。

該實現需要我們處理一個新的動作類型—— blur ,它將更新一個名為blurred的新狀態對象:

 const initialState = { values: {}, errors: {}, blurred: {}, submitted: false, }; function validationReducer(state, action) { switch (action.type) { // as before case 'blur': const blurred = { ...state.blurred, [action.payload]: true }; return { ...state, blurred }; default: throw new Error('Unknown action type'); } }

當我們派發blur動作時,我們在blurred狀態對像中創建一個新屬性,其中字段名稱作為鍵,指示字段已被模糊。

下一步是在我們的getFieldProps函數中添加一個onBlur屬性,它會在適用時調度這個動作:

 getFieldProps: fieldName => ({ // as before onBlur: () => { dispatch({ type: 'blur', payload: fieldName }); }, }),

最後,我們需要從我們的useValidation鉤子中提供blurredErrors以便我們可以僅在需要時顯示錯誤。

 const blurredErrors = useMemo(() => { const returnValue = {}; for (let fieldName in state.errors) { returnValue[fieldName] = state.blurred[fieldName] ? state.errors[fieldName] : null; } return returnValue; }, [state.errors, state.blurred]); return { // as before blurredErrors, };

在這裡,我們創建了一個記憶函數,該函數根據字段是否模糊來確定要顯示哪些錯誤。 每當錯誤或模糊對象發生變化時,我們都會重新計算這組錯誤。 您可以在文檔中閱讀有關useMemo掛鉤的更多信息。

  • 請參閱 CodeSandbox 演示

是時候進行微小的重構了

我們的useValidation組件現在返回三組錯誤——其中大部分在某個時間點看起來都是一樣的。 我們不會沿著這條路線走,而是讓用戶在配置中指定他們希望在他們的表單中顯示錯誤的時間。

我們的新選項showErrors將接受“提交”(默認)、“始終”或“模糊”。 如果需要,我們可以稍後添加更多選項。

 function getErrors(state, config) { if (config.showErrors === 'always') { return state.errors; } if (config.showErrors === 'blur') { return Object.entries(state.blurred) .filter(([, blurred]) => blurred) .reduce((acc, [name]) => ({ ...acc, [name]: state.errors[name] }), {}); } return state.submitted ? state.errors : {}; } const useValidation = config => { // as before const errors = useMemo( () => getErrors(state, config), [state, config] ); return { errors, // as before }; };

由於錯誤處理代碼開始佔用我們的大部分空間,我們將其重構為自己的函數。 如果您不遵循Object.entries.reduce的內容——那很好——這是對上一節中for...in代碼的重寫。

如果我們需要 onBlur 或即時驗證,我們可以在我們的useValidation配置對像中指定showError屬性。

 const config = { // as before showErrors: 'blur', }; const { getFormProps, getFieldProps, errors } = useValidation(config); // errors would now only include the ones that have been blurred
  • 請參閱 CodeSandbox 演示

關於假設的說明

“請注意,我現在假設每個表單都希望以相同的方式顯示錯誤(始終處於提交狀態,始終處於模糊狀態等)。 對於大多數應用程序來說,這可能是正確的,但可能並非對所有應用程序都是如此。 了解您的假設是創建 API 的重要部分。”

允許交叉驗證

驗證庫的一個非常強大的功能是允許交叉驗證——也就是說,將一個字段的驗證基於另一個字段的值。

為此,我們需要讓我們的自定義鉤子接受一個函數而不是一個對象。 將使用當前字段值調用此函數。 實現它其實只需要三行代碼!

 function useValidation(config) { const [state, dispatch] = useReducer(...); if (typeof config === 'function') { config = config(state.values); } }

要使用此功能,我們可以簡單地將一個返回配置對象的函數傳遞給useValidation

 const { getFieldProps } = useValidation(fields => ({ password: { isRequired: { message: 'Please fill out the password' }, }, repeatPassword: { isRequired: { message: 'Please fill out the password one more time' }, isEqual: { value: fields.password, message: 'Your passwords don\'t match' } } }));

在這裡,我們使用fields.password的值來確保兩個密碼字段包含相同的輸入(這是糟糕的用戶體驗,但這是另一篇博客文章的內容)。

  • 請參閱不允許用戶名和密碼為相同值的 CodeSandbox 演示。

添加一些輔助功能勝利

當您負責字段的 props 時,要做的一件巧妙的事情是默認添加正確的 aria-tags。 這將幫助屏幕閱讀器解釋您的表單。

一個非常簡單的改進是如果字段有錯誤,則添加aria-invalid="true" 。 讓我們實現它:

 const useValidation = config => { // as before return { // as before getFieldProps: fieldName => ({ // as before 'aria-invalid': String(!!errors[fieldName]), }), } };

這增加了一行代碼,為屏幕閱讀器用戶提供更好的用戶體驗。

您可能想知道我們為什麼要寫String(!!state.errors[fieldName])state.errors[fieldName]是一個字符串,雙重否定運算符為我們提供了一個布爾值(而不僅僅是一個真值或假值)。 但是, aria-invalid屬性應該是一個字符串(除了“true”或“false”,它還可以讀作“grammar”或“spelling”),因此我們需要將該布爾值強制轉換為等效的字符串。

我們還可以做一些調整來提高可訪問性,但這似乎是一個公平的開始。

速記驗證消息語法

calidators包中的大多數驗證器(以及我認為的大多數其他驗證器)只需要一條錯誤消息。 如果我們可以只傳遞該字符串而不是具有包含該字符串的message屬性的對象,那不是很好嗎?

讓我們在validateField函數中實現它:

 function validateField(fieldValue = '', fieldConfig, allFieldValues) { for (let validatorName in fieldConfig) { let validatorConfig = fieldConfig[validatorName]; if (typeof validatorConfig === 'string') { validatorConfig = { message: validatorConfig }; } const configuredValidator = validators[validatorName](validatorConfig); const errorMessage = configuredValidator(fieldValue); if (errorMessage) { return errorMessage; } } return null; }

這樣,我們可以像這樣重寫我們的驗證配置:

 const config = { username: { isRequired: 'The username is required', isEmail: 'The username should be a valid email address', }, };

乾淨多了!

初始字段值

有時,我們需要驗證已經填寫的表格。 我們的自定義鉤子還不支持它——所以讓我們開始吧!

初始字段值將在每個字段的配置中指定,在屬性initialValue中。 如果未指定,則默認為空字符串。

我們將創建一個函數getInitialState ,它將為我們創建 reducer 的初始狀態。

 function getInitialState(config) { if (typeof config === 'function') { config = config({}); } const initialValues = {}; const initialBlurred = {}; for (let fieldName in config.fields) { initialValues[fieldName] = config.fields[fieldName].initialValue || ''; initialBlurred[fieldName] = false; } const initialErrors = validateFields(initialValues, config.fields); return { values: initialValues, errors: initialErrors, blurred: initialBlurred, submitted: false, }; }

我們遍歷所有字段,檢查它們是否具有initialValue屬性,並相應地設置初始值。 然後我們通過驗證器運行這些初始值併計算初始誤差。 我們返回初始狀態對象,然後可以將其傳遞給我們的useReducer鉤子。

由於我們在字段配置中引入了非驗證器道具,因此在驗證字段時需要跳過它。 為此,我們更改了validateField函數:

 function validateField(fieldValue = '', fieldConfig) { const specialProps = ['initialValue']; for (let validatorName in fieldConfig) { if (specialProps.includes(validatorName)) { continue; } // as before } }

隨著我們不斷添加更多這樣的功能,我們可以將它們添加到我們的specialProps數組中。

  • 請參閱 CodeSandbox 演示

加起來

我們正在努力創建一個令人驚嘆的驗證庫。 我們添加了大量功能,現在我們幾乎是思想領袖。

在本系列的下一部分中,我們將添加所有使我們的驗證庫在 LinkedIn 上流行的附加功能。