إنشاء مكتبة التحقق من صحة التفاعل الخاصة بك: الأساسيات (الجزء الأول)
نشرت: 2022-03-10لطالما اعتقدت أن مكتبات التحقق من صحة النماذج كانت رائعة جدًا. أعلم أنه من مصلحة أن يكون لديك - لكننا نستخدمها كثيرًا! في وظيفتي على الأقل - معظم ما أقوم به هو إنشاء نماذج أكثر أو أقل تعقيدًا مع قواعد التحقق من الصحة التي تعتمد على الخيارات والمسارات السابقة. إن فهم كيفية عمل مكتبة التحقق من صحة النموذج أمر بالغ الأهمية.
في العام الماضي ، كتبت إحدى مكتبات التحقق من صحة النماذج. أطلق عليها اسم "Calidation" ، ويمكنك قراءة منشور المدونة التمهيدي هنا. إنها مكتبة جيدة توفر قدرًا كبيرًا من المرونة وتستخدم نهجًا مختلفًا قليلاً عن تلك الموجودة في السوق. هناك الكثير من المكتبات الرائعة الأخرى الموجودة هناك أيضًا - لقد عملت بشكل جيد لتلبية متطلباتنا .
سأوضح لك اليوم كيفية كتابة مكتبة التحقق الخاصة بك من أجل React. سنمر في العملية خطوة بخطوة ، وستجد أمثلة على CodeSandbox بينما نمضي قدمًا. بنهاية هذه المقالة ، ستعرف كيفية كتابة مكتبة التحقق الخاصة بك ، أو على الأقل أن يكون لديك فهم أعمق لكيفية تنفيذ المكتبات الأخرى لـ "سحر التحقق من الصحة".
- الجزء الأول: الأساسيات
- الجزء 2: الميزات
- الجزء 3: التجربة
الخطوة 1: تصميم API
تتمثل الخطوة الأولى لإنشاء أي مكتبة في تصميم كيفية استخدامها. إنه يضع الأساس لكثير من العمل الذي سيأتي ، وفي رأيي ، إنه القرار الوحيد الأكثر أهمية الذي ستتخذه في مكتبتك.
من المهم إنشاء واجهة برمجة تطبيقات "سهلة الاستخدام" ومرنة بما يكفي للسماح بالتحسينات المستقبلية وحالات الاستخدام المتقدمة. سنحاول تحقيق هذين الهدفين.
سنقوم بإنشاء خطاف مخصص يقبل كائن تكوين واحد. سيسمح هذا بتمرير الخيارات المستقبلية دون إدخال تغييرات جذرية.
ملاحظة على الخطافات
الخطافات هي طريقة جديدة لكتابة React. إذا كنت قد كتبت React في الماضي ، فقد لا تتعرف على بعض هذه المفاهيم. في هذه الحالة ، يرجى إلقاء نظرة على الوثائق الرسمية. إنه مكتوب بشكل جيد للغاية ، ويأخذك عبر الأساسيات التي تحتاج إلى معرفتها.
سنقوم باستدعاء خطافنا المخصص useValidation
في الوقت الحالي. قد يبدو استخدامه كالتالي:
const config = { fields: { username: { isRequired: { message: 'Please fill out a username' }, }, password: { isRequired: { message: 'Please fill out a password' }, isMinLength: { value: 6, message: 'Please make it more secure' } } }, onSubmit: e => { /* handle submit */ } }; const { getFieldProps, getFormProps, errors } = useValidation(config);
يقبل كائن config
خاصية fields
، والتي تقوم بإعداد قواعد التحقق من الصحة لكل حقل. بالإضافة إلى ذلك ، فإنه يقبل رد الاتصال عند إرسال النموذج.
يحتوي كائن fields
على مفتاح لكل حقل نريد التحقق من صحته. كل حقل له تكوين خاص به ، حيث يكون كل مفتاح هو اسم مدقق ، وكل قيمة هي خاصية تكوين لهذا المدقق. هناك طريقة أخرى لكتابة نفس الشيء وهي:
{ fields: { fieldName: { oneValidator: { validatorRule: 'validator value' }, anotherValidator: { errorMessage: 'something is not as it should' } } } }
سيعيد خطاف useValidation
الخاص بنا كائنًا بخصائص قليلة - getFieldProps
و getFormProps
errors
. الوظيفتان الأوليان هما ما يسميه كينت سي دودز "أدوات المساعدة" (انظر هنا للحصول على مقالة رائعة عن هؤلاء) ، ويتم استخدامها للحصول على الدعائم ذات الصلة لحقل نموذج معين أو علامة نموذج معينة. خاصيّة errors
عبارة عن كائن به أي رسائل خطأ ، يتم تحديده في كل حقل.
سيبدو هذا الاستخدام كما يلي:
const config = { ... }; // like above const LoginForm = props => { const { getFieldProps, getFormProps, errors } = useValidation(config); return ( <form {...getFormProps()}> <label> Username<br/> <input {...getFieldProps('username')} /> {errors.username && <div className="error">{errors.username}</div>} </label> <label> Password<br/> <input {...getFieldProps('password')} /> {errors.password && <div className="error">{errors.password}</div>} </label> <button type="submit">Submit my form</button> </form> ); };
حسنًا! لذلك قمنا بتثبيت API.
- انظر عرض CodeSandbox
لاحظ أننا أنشأنا أيضًا تنفيذًا useValidation
لخطاف التحقق من الصحة. في الوقت الحالي ، هو مجرد إعادة كائن مع الكائنات والوظائف التي نطلبها لتكون هناك ، لذلك لا نكسر نموذج التنفيذ الخاص بنا.
تخزين حالة النموذج
أول شيء يتعين علينا القيام به هو تخزين كل حالة النموذج في الخطاف المخصص لدينا. نحتاج إلى تذكر قيم كل حقل وأي رسائل خطأ وما إذا كان النموذج قد تم تقديمه أم لا. سنستخدم الخطاف useReducer
لهذا لأنه يتيح أكبر قدر من المرونة (وأقل معيارًا). إذا كنت قد استخدمت Redux من قبل ، فسترى بعض المفاهيم المألوفة - وإذا لم تكن كذلك ، فسنشرح لك بينما نمضي قدمًا! سنبدأ بكتابة المخفض ، والذي يتم تمريره إلى خطاف useReducer
:
const initialState = { values: {}, errors: {}, submitted: false, }; function validationReducer(state, action) { switch(action.type) { case 'change': const values = { ...state.values, ...action.payload }; return { ...state, values, }; case 'submit': return { ...state, submitted: true }; default: throw new Error('Unknown action type'); } }
ما هو المخفض؟
المخفض هو وظيفة تقبل كائن من القيم و "إجراء" وتعيد إصدارًا معززًا من كائن القيم.
الإجراءات هي كائنات JavaScript عادية بخاصية type
. نحن نستخدم تعليمة switch
للتعامل مع كل نوع من أنواع الإجراءات الممكنة.
غالبًا ما يُشار إلى "كائن القيم" بالحالة ، وفي حالتنا ، إنها حالة منطق التحقق لدينا.
تتكون حالتنا من ثلاثة أجزاء من البيانات - values
(القيم الحالية لحقول النموذج الخاصة بنا) ، errors
(المجموعة الحالية من رسائل الخطأ) ويتم تقديم isSubmitted
تشير إلى ما إذا كان قد تم إرسال النموذج مرة واحدة على الأقل أم لا.
من أجل تخزين حالة النموذج الخاصة بنا ، نحتاج إلى تنفيذ بضعة أجزاء من useValidation
الخاص بنا. عندما نستدعي طريقة getFieldProps
، نحتاج إلى إرجاع كائن بقيمة هذا الحقل ، ومعالج تغيير عندما يتغير ، وخاصية اسم لتتبع أي حقل هو.
function validationReducer(state, action) { // Like above } const initialState = { /* like above */ }; const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); return { errors: state.errors, getFormProps: e => {}, getFieldProps: fieldName => ({ onChange: e => { if (!config.fields[fieldName]) { return; } dispatch({ type: 'change', payload: { [fieldName]: e.target.value } }); }, name: fieldName, value: state.values[fieldName], }), }; };
تُرجع طريقة getFieldProps
الآن الخاصيات المطلوبة لكل حقل. عندما يتم إطلاق حدث تغيير ، فإننا نتأكد من أن الحقل موجود في تكوين التحقق من الصحة الخاص بنا ، ثم نخبر مخفضنا بحدوث إجراء change
. المخفض سوف يتعامل مع التغييرات في حالة التحقق من الصحة.
- انظر عرض CodeSandbox
التحقق من صحة النموذج الخاص بنا
تبدو مكتبة التحقق من صحة النماذج جيدة ، ولكنها لا تفعل الكثير من حيث التحقق من صحة قيم النموذج! دعونا نصلح ذلك.

سنقوم بالتحقق من صحة جميع الحقول في كل حدث تغيير. قد لا يبدو هذا فعالًا للغاية ، ولكن في تطبيقات العالم الواقعي التي صادفتها ، فهي ليست مشكلة حقًا.
ملاحظة ، نحن لا نقول إنه يجب عليك إظهار كل خطأ عند كل تغيير. سنعيد النظر في كيفية إظهار الأخطاء فقط عند الإرسال أو الانتقال بعيدًا عن أحد الحقول ، لاحقًا في هذه المقالة.
كيفية اختيار وظائف المدقق
عندما يتعلق الأمر بالمصدقين ، فهناك الكثير من المكتبات التي تنفذ جميع طرق التحقق التي تحتاجها في أي وقت. يمكنك أيضًا كتابة ما تريد. إنه تمرين ممتع!
بالنسبة لهذا المشروع ، سنستخدم مجموعة من أدوات التحقق التي كتبتها منذ بعض الوقت - calidators
. هؤلاء المدققون لديهم واجهة برمجة التطبيقات التالية:
function isRequired(config) { return function(value) { if (value === '') { return config.message; } else { return null; } }; } // or the same, but terser const isRequired = config => value => value === '' ? config.message : null;
بمعنى آخر ، يقبل كل مدقق كائن تكوين ويعيد مدققًا مكوّنًا بالكامل. عندما يتم استدعاء هذه الوظيفة بقيمة ، فإنها تُرجع خاصية message
إذا كانت القيمة غير صالحة ، أو null
إذا كانت صالحة. يمكنك إلقاء نظرة على كيفية تنفيذ بعض هذه المدققات من خلال النظر إلى الكود المصدري.
للوصول إلى هذه المدققات ، قم بتثبيت حزمة calidators
مع npm install calidators
.
تحقق من صحة حقل واحد
تذكر التكوين الذي نمرره إلى كائن useValidation
بنا؟ تبدو هكذا:
{ fields: { username: { isRequired: { message: 'Please fill out a username' }, }, password: { isRequired: { message: 'Please fill out a password' }, isMinLength: { value: 6, message: 'Please make it more secure' } } }, // more stuff }
لتبسيط التنفيذ ، لنفترض أن لدينا حقلًا واحدًا فقط للتحقق من صحته. سننتقل إلى كل مفتاح في كائن تكوين الحقل ، ونشغل المدققات واحدًا تلو الآخر حتى نكتشف خطأً أو ننتهي من التحقق من الصحة.
import * as validators from 'calidators'; function validateField(fieldValue = '', fieldConfig) { for (let validatorName in fieldConfig) { const validatorConfig = fieldConfig[validatorName]; const validator = validators[validatorName]; const configuredValidator = validator(validatorConfig); const errorMessage = configuredValidator(fieldValue); if (errorMessage) { return errorMessage; } } return null; }
هنا ، قمنا بكتابة دالة validateField
، والتي تقبل القيمة المراد التحقق منها وتهيئة المدقق لهذا الحقل. نقوم بعمل حلقة عبر جميع المدققين ، ونمرر لهم التكوين لهذا المدقق ، ونشغّله. إذا تلقينا رسالة خطأ ، فإننا نتخطى بقية المدققات ونرجع. إذا لم يكن كذلك ، فإننا نجرب المدقق التالي.
ملاحظة: في المدقق APIs
إذا اخترت مدققين مختلفين باستخدام واجهات برمجة تطبيقات مختلفة (مثل validator.js
الشائع جدًا) ، فقد يبدو هذا الجزء من التعليمات البرمجية مختلفًا بعض الشيء. ومع ذلك ، من أجل الإيجاز ، نسمح لهذا الجزء أن يكون تمرينًا متروكًا للقارئ.
ملاحظة: تشغيل لـ ... في الحلقات
لم تستخدم for...in
الحلقات من قبل؟ لا بأس ، هذه كانت المرة الأولى لي أيضًا! في الأساس ، يتكرر على المفاتيح في كائن ما. يمكنك قراءة المزيد عنها في MDN.
تحقق من صحة جميع الحقول
الآن بعد أن تحققنا من حقل واحد ، يجب أن نكون قادرين على التحقق من صحة جميع الحقول دون الكثير من المتاعب.
function validateField(fieldValue = '', fieldConfig) { // as before } function validateFields(fieldValues, fieldConfigs) { const errors = {}; for (let fieldName in fieldConfigs) { const fieldConfig = fieldConfigs[fieldName]; const fieldValue = fieldValues[fieldName]; errors[fieldName] = validateField(fieldValue, fieldConfig); } return errors; }
لقد كتبنا دالة validateFields
تقبل جميع قيم الحقول وتكوين الحقل بالكامل. نقوم بإجراء حلقة من خلال كل اسم حقل في التكوين والتحقق من صحة هذا الحقل بكائن التكوين الخاص به وقيمته.
التالي: أخبر مخفضنا
حسنًا ، لدينا الآن هذه الوظيفة التي تتحقق من صحة جميع عناصرنا. دعنا نسحبه إلى بقية الكود الخاص بنا!
أولاً ، سنقوم بإضافة معالج إجراء validate
إلى validationReducer
الخاص بنا.
function validationReducer(state, action) { switch (action.type) { case 'change': // as before case 'submit': // as before case 'validate': return { ...state, errors: action.payload }; default: throw new Error('Unknown action type'); } }
عندما نشغل إجراء validate
، فإننا نستبدل الأخطاء في حالتنا بكل ما تم تمريره بجانب الإجراء.
بعد ذلك ، سنقوم بتشغيل منطق التحقق الخاص بنا من خطاف useEffect
:
const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); useEffect(() => { const errors = validateFields(state.fields, config.fields); dispatch({ type: 'validate', payload: errors }); }, [state.fields, config.fields]); return { // as before }; };
يتم تشغيل خطاف useEffect
هذا عندما يتغير أي state.fields
أو config.fields
، بالإضافة إلى عند التحميل الأول.
احذر من البق
هناك خطأ فائق الدقة في الكود أعلاه. لقد حددنا أن خطاف useEffect
بنا يجب أن يعاد تشغيله فقط كلما state.fields
أو config.fields
. تبين أن "التغيير" لا يعني بالضرورة تغيير القيمة! يستخدم useEffect
Object.is
لضمان المساواة بين الكائنات ، والتي بدورها تستخدم المساواة المرجعية. أي - إذا مررت كائنًا جديدًا بنفس المحتوى ، فلن يكون هو نفسه (نظرًا لأن الكائن نفسه جديد).
يتم إرجاع state.fields
من useReducer
، مما يضمن لنا هذه المساواة المرجعية ، لكن config
الخاص بنا محدد بشكل مضمّن في مكون الوظيفة الخاص بنا. هذا يعني أن الكائن يُعاد إنشاؤه في كل تصيير ، والذي بدوره سيؤدي إلى تشغيل useEffect
أعلاه!
لحل هذه المشكلة ، نحتاج إلى استخدام مكتبة use-deep-compare-effect
بواسطة Kent C. Dodds. يمكنك تثبيته باستخدام npm install use-deep-compare-effect
، واستبدل استدعاء useEffect
الخاص بك بهذا بدلاً من ذلك. هذا يضمن أننا نجري فحصًا عميقًا للمساواة بدلاً من فحص المساواة المرجعية.
سيبدو الرمز الخاص بك الآن كما يلي:
import useDeepCompareEffect from 'use-deep-compare-effect'; const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); useDeepCompareEffect(() => { const errors = validateFields(state.fields, config.fields); dispatch({ type: 'validate', payload: errors }); }, [state.fields, config.fields]); return { // as before }; };
ملاحظة حول useEffect
تبين أن useEffect
هي وظيفة مثيرة للاهتمام. كتب دان أبراموف مقالًا طويلًا لطيفًا حقًا عن تعقيدات useEffect
إذا كنت مهتمًا بمعرفة كل ما هو موجود حول هذا الخطاف.
الآن بدأت الأشياء تبدو كمكتبة تحقق من الصحة!
- انظر عرض CodeSandbox
معالجة تقديم النموذج
يتعامل الجزء الأخير من مكتبة التحقق من صحة النموذج الأساسية مع ما يحدث عند إرسال النموذج. في الوقت الحالي ، يقوم بإعادة تحميل الصفحة ولا يحدث شيء. هذا ليس هو الأمثل. نريد منع سلوك المتصفح الافتراضي عندما يتعلق الأمر بالنماذج ، والتعامل معه بأنفسنا بدلاً من ذلك. نضع هذا المنطق داخل getFormProps
prop getter:
const useValidation = config => { const [state, dispatch] = useReducer(validationReducer, initialState); // as before return { getFormProps: () => ({ onSubmit: e => { e.preventDefault(); dispatch({ type: 'submit' }); if (config.onSubmit) { config.onSubmit(state); } }, }), // as before }; };
نقوم بتغيير دالة getFormProps
بنا لإرجاع دالة onSubmit
، والتي يتم تشغيلها عندما يتم تشغيل حدث submit
DOM. نحن نمنع سلوك المتصفح الافتراضي ، ونرسل إجراءً لإخبار مخفضنا الذي أرسلناه ، وندعو رد الاتصال onSubmit
المقدم مع الحالة بأكملها - إذا تم توفيرها.
ملخص
كانوا هناك! لقد أنشأنا مكتبة تحقق بسيطة وقابلة للاستخدام ورائعة. لا يزال هناك الكثير من العمل الذي يتعين القيام به قبل أن نتمكن من السيطرة على الشبكات الداخلية.
- الجزء الأول: الأساسيات
- الجزء 2: الميزات
- الجزء 3: التجربة