إنشاء مكتبة التحقق من صحة التفاعل الخاصة بك: الميزات (الجزء 2)

نشرت: 2022-03-10
ملخص سريع ↬ في مقالة كريستوفر السابقة ، شرح كيف يمكن تنفيذ الأجزاء الأساسية لمكتبة التحقق من الصحة. بينما سيركز الجزء التالي على تحسين تجربة المطور ، ستركز مقالة اليوم على إضافة المزيد من الميزات إلى ما تم إنشاؤه في الجزء الأول.

ليس تنفيذ مكتبة التحقق من الصحة بهذه الصعوبة. لا يتم إضافة كل تلك الميزات الإضافية التي تجعل مكتبة التحقق الخاصة بك أفضل بكثير من الباقي.

ستستمر هذه المقالة في تنفيذ مكتبة التحقق التي بدأنا تنفيذها في الجزء السابق من سلسلة المقالات هذه. هذه هي الميزات التي ستأخذنا من دليل بسيط على المفهوم إلى مكتبة فعلية قابلة للاستخدام!

  • الجزء الأول: الأساسيات
  • الجزء 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 مع اسم الحقل كمفتاح ، مما يشير إلى أن هذا الحقل قد تم تعتيمه.

تتمثل الخطوة التالية في إضافة خاصية onBlur إلى وظيفة getFieldProps الخاصة بنا ، والتي ترسل هذا الإجراء عند الاقتضاء:

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

أخيرًا ، نحتاج إلى توفير blurredErrors من خطاف useValidation بنا حتى نتمكن من إظهار الأخطاء عند الحاجة فقط.

 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 أو التحقق الفوري ، فيمكننا تحديد showError في كائن تكوين useValidation بنا.

 const config = { // as before showErrors: 'blur', }; const { getFormProps, getFieldProps, errors } = useValidation(config); // errors would now only include the ones that have been blurred
  • انظر عرض CodeSandbox

ملاحظة حول الافتراضات

"لاحظ أنني أفترض الآن أن كل نموذج سيرغب في إظهار الأخطاء بنفس الطريقة (دائمًا عند الإرسال ، دائمًا عند التعتيم ، إلخ). قد يكون هذا صحيحًا بالنسبة لمعظم التطبيقات ، ولكن ربما لا يكون صحيحًا للجميع. يعد إدراك افتراضاتك جزءًا كبيرًا من إنشاء واجهة برمجة التطبيقات الخاصة بك ".

السماح بالتحقق المتبادل

من الميزات القوية حقًا لمكتبة التحقق من الصحة السماح بالتحقق المتبادل - أي ، تأسيس التحقق من صحة حقل واحد على قيمة حقل آخر.

للسماح بذلك ، نحتاج إلى جعل الخطاف المخصص الخاص بنا يقبل وظيفة بدلاً من كائن. سيتم استدعاء هذه الوظيفة بقيم الحقل الحالية. تنفيذه هو في الواقع ثلاثة أسطر فقط من التعليمات البرمجية!

 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 الذي لا يجعل اسم المستخدم وكلمة المرور بنفس القيمة.

أضف بعض الانتصارات المتعلقة بإمكانية الوصول

من الأشياء الرائعة التي يجب عليك القيام بها عندما تكون مسؤولاً عن دعائم حقل ما هو إضافة علامات aria الصحيحة افتراضيًا. سيساعد هذا برامج قراءة الشاشة في شرح النموذج الخاص بك.

هناك تحسين بسيط للغاية وهو إضافة 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") ، لذلك نحن بحاجة إلى إجبار ذلك المنطقي على مكافئ السلسلة.

لا يزال هناك عدد قليل من التعديلات التي يمكننا القيام بها لتحسين إمكانية الوصول ، ولكن يبدو أن هذا بداية عادلة.

بناء جملة رسالة التحقق من الاختزال

معظم المدققين في حزمة 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 ، والتي ستنشئ لنا الحالة الأولية لمخفض السرعة.

 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 بنا.

نظرًا لأننا نقدم خاصية non-validator في تهيئة الحقول ، فنحن بحاجة إلى تخطيها عندما نتحقق من صحة حقولنا. للقيام بذلك ، نقوم بتغيير وظيفة validateField بنا:

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

مع استمرارنا في إضافة المزيد من الميزات مثل هذه ، يمكننا إضافتها إلى مجموعة specialProps بنا.

  • انظر عرض CodeSandbox

تلخيص لما سبق

نحن في طريقنا لإنشاء مكتبة تحقق مذهلة. لقد أضفنا الكثير من الميزات ، ونحن الآن قادة مفكرون إلى حد كبير.

في الجزء التالي من هذه السلسلة ، سنضيف كل تلك الإضافات التي تجعل مكتبة التحقق الخاصة بنا رائجة على LinkedIn.