دورة TanStack Form الكاملة › دورة TanStack Form الكاملة 1 / 1 السابق التالي

التحقّق من الصحة باستخدام Zod (Validating with ZOD)

الدرس 12 من 21 · دورة TanStack Form الكاملة · 11 دقيقة و55 ثانية

الفيديو الأصلي باللغة الإنجليزية. إذا تعذّر تشغيله هنا، شاهده على موقع المؤلف. التفريغ النصي العربي الكامل أدناه.

مستودع شيفرة هذا الدرس

الفرع: zod-1

[00:00] إذًا كما رأينا في TanStack Form، فإنّ لَدَينا مرونةً كبيرة عندما يتعلّق الأمر بالتحقّق من الصحة (validation): فبإمكاننا أن نختار متى نتحقّق بالضبط، وبإمكاننا أيضًا أن نقدّم دوالّنا الخاصّة لتنفيذ منطق التحقّق من الصحة لدينا. غير أنّه، كلّما كبرت نماذجك، أو إذا

[00:14] كنت تمتلك تطبيقًا كبيرًا فيه نماذج كثيرة، فقد يكون تقديم دوالك الخاصّة هنا مُسهبًا بعض الشيء، وقد تجد نفسك تكتب قدرًا كبيرًا من الشيفرة من أجل التحقّق من الصحة وحده. فاستخدام مكتبة تحقّقٍ قائمةٍ على المخطّط (schema) يمكن أن يجعل هذا

[00:27] العمليّة كلّها أسهل بكثير. يدعم TanStack Form مكتباتٍ متعدّدة مثل Zod وValibot وغيرها. غير أنّنا سنستخدم Zod في هذا الفيديو. فما يمكننا فعله بدلًا من كتابة دوالّ التحقّق من الصحة الخاصّة بنا هو أنّنا نكتب

[00:40] مخطّطًا واحدًا ونمرّره إلى نموذجنا بأكمله لنتحقّق من صحة جميع حقولنا. ويمكننا أيضًا، إن أردنا، التحقّق من الحقول منفردةً كذلك. فكما بإمكاننا فعله مع هذه الدوالّ، بإمكاننا التحقّق باستخدام Zod على

[00:54] مستوى الحقل (field) وعلى مستوى النموذج (form) كذلك. فلنرَ كيف يمكننا فعل ذلك. فما سأفعله هو أنّي سأحذف أيّ تحقّق أنجزناه حتى الآن، كي يصبح لَدَينا نموذج من غير أيّ تحقّق من الصحة

[01:06] ويمكن أن نبدأ التحقّق من الصفر باستخدام Zod. ففي هنا لدينا تحقّق في validators في confirmPassword.

[01:13] فلنحذفه تمامًا. وأظنّ أنّه كان لَدَينا في الاسم الأول كذلك.

[01:18] ففي الاسم الأول كان لَدَينا validators.

[01:21] فلنحذفه. وأظنّ أنّه كان هذا هو كلّ شيء. وعلى مستوى النموذج كان — كنّا نمتلك — تحقّقات، لكنّنا حذفنا بالفعل تحقّقات مستوى النموذج. ما بقي عندنا هو منطق التحقّق (validation logic) هنا، وهو الذي سأحافظ عليه لأنّه

[01:34] سنستخدمه لاحقًا كذلك. فالآن لدينا نموذج بلا أيّ تحقّق من الصحة. صحيح؟

[01:42] إذًا فلنثبّت Zod الآن. فسأذهب هنا وسأنفّذ npm install Zod فحسب. وهيا لنستخدمه إذًا. سأستورد Zod هنا هكذا: استيراد كلّ شيء بالاسم z من Zod. ويمكننا أن نعرّف مخطّطًا لنموذجنا بأكمله، أو

[01:58] يمكننا كذلك التحقّق من الحقول منفردةً باستخدام Zod. فيمكننا الذهاب، ولنقل، إلى حقل الاسم الأول هنا مثلًا، وسيبقى بإمكاننا امتلاك validators الخاصّة بنا هكذا، وسيبقى بإمكاننا أن نتحقّق عند التغيّر (onChange) بأنفسنا، غير أنّه هذه المرة، بدلًا من

[02:12] تمرير دالة، يمكننا تمرير z — نحتاج هذا ليكون سلسلةً نصّيّة (string). إذًا z.string، ثم يمكننا أن نقول لنحدّد حدًّا أدنى (minimum) للطول هو 2 مثلًا. فنستطيع تمرير min(2) هكذا، ونستطيع أيضًا تمرير رسالة (message)، فلنقل first

[02:30] name should be at least two characters.

[02:33] ويمكن أيضًا تمرير max وما إلى ذلك. فإذا كنت معتادًا على Zod فكنت معتادًا على هذا أصلًا.

[02:40] فالآن إذا كتبنا — لِنقُلْ — شيئًا فيه خطأ فسنحصل هنا على كائن (object)، لأنّه عندما نُجري تحقّقاتنا باستخدام Zod فسيكون خطأ التحقّق (validation error) لَدَينا مختلفًا قليلًا، ولن يكون

[02:52] سلسلةً نصّيّة. وكما ترى هنا، في أخطائنا (errors) مصفوفة (array) من الكائنات، لا مصفوفة من السلاسل النصّيّة. وفي كلّ كائن رسالتنا. فلدينا هنا first name should be at least two characters.

[03:04] إذًا ما نحتاج إلى فعله هو أنّنا نُعدّل الترميز (markup) الذي نعرض فيه خطأنا فحسب. إذًا بدلًا من عرض أخطائنا هكذا فسنحتاج إلى المرور على مصفوفة أخطائنا بحلقة (loop). فيمكننا فعل map هنا، وثمّ لكلّ خطأ

[03:17] سيكون لَدَينا دوالّ الخطأ التابعة لنا. فيمكننا، ولنقل، أن نعيد فقط error.message هكذا. وهذا سيعيد مصفوفة من رسائلنا، ونحتاج فقط إلى ضمّها (join) كي نجعلها سلسلةً نصّيّة. فنستطيع فحسب

[03:32] أن نستدعِ join، ولنجعل، من حيث الفاصل (separator)، الفاصلة (comma) هي الفاصل هكذا.

[03:37] الآن إذا حفظنا هذا فيجب أن نحصل على رسالة خطأنا هكذا. وبالطبع يمكننا فعل الشيء نفسه مع onBlur وما إلى ذلك. فلنضيف ربما واحدًا آخر مع onBlur، ولنقل إنّنا هذه المرة نحتاج إلى القيام بشيء لا نستطيع فعله

[03:51] بدوالّ Zod مباشرةً. أوه، عذرًا؛ ليس هنا، وليس في المستمعين (listeners) لدينا؛ بل في validators لدينا. ففي Zod لَدَينا دوالّ مثل min وmax وما إلى ذلك للتحقّق من الأشياء الأكثر شيوعًا. لكن لنقل إنّنا نحتاج إلى

[04:06] التحقّق، شبيهًا بما فعلناه في أحد الأوقات مسبقًا، من أنّ اسمنا لا يساوي — John. فما يمكننا فعله، في هذه الحالة، هو أنّ في Zod لَدَينا هذه الدالة refine التي يمكننا استخدامها. فيمكننا فعل z.string. فنحتاج

[04:20] هذا ليكون سلسلةً نصّيّة. غير أنّه، بدلًا من الحدّ الأدنى والحدّ الأقصى (minimum and maximum)، يمكننا تمرير refine هنا للتحقّق من شيء ما في سلسلتنا النصّيّة. ففي هنا يمكننا تمرير دالة، وسنحصل على قيمتنا.

[04:31] ويمكننا أن نتحقّق، ولنقل، من أنّ قيمتنا لا تساوي John. وإذا كان ما نعيده من هذه الدالة هو false، فسيُعتبر حقلنا غير صالح (invalid).

[04:41] ويمكننا أن نعيد رسالة هنا. فبإمكاننا تمرير كائن به رسالة (message) — عذرًا، به error في الحقيقة — ويمكننا أن نكتب الرسالة التالية مثلًا: first name cannot be John.

[04:56] فالآن، بما أنّنا نُجري هذا التحقّق على onBlur.

[04:59] فإذا كتبنا John هنا وأزلنا التركيز من حقلنا فسنحصل على خطأنا: first name cannot be John. وكذلك في error map لدينا ما زلنا نحصل على كلّ شيء. فنحصل على الخطأ من onBlur. غير أنّه هذه المرة ستكون مصفوفة من الكائنات لا مصفوفة

[05:14] من السلاسل النصّيّة. غير أنّه من المُريح على الأرجح حين نستخدم Zod.

[05:19] فبدلًا من التحقّق من كلّ حقل على مستوى الحقل، يمكننا تعريف مخطّط لنموذجنا بأكمله والتحقّق على مستوى النموذج بدلًا من ذلك.

[05:27] إذًا سأحذف هذه. أوه، فكانت هذه مجرّد مثال. فلنحذفها الآن. ولنجري التحقّق من الصحة لنموذجنا بأكمله بتعريف مخطّط واحد. ففي هنا، حسنًا، لا تقلق؛ فسننظّف الأمور

[05:40] لاحقًا. فنحن نضع كلّ شيء الآن في ملف واحد فحسب. ففي هنا، ربما، بعد هذه الواجهة (interface) سأعرّف مخطّطًا. فلنسمّه formSchema. وفي هنا نحتاج إلى تعريف مخطّط لكلّ حقولنا

[05:54] فسيكون كائن (object) في Zod يضمّ جميع حقولنا، وهي الاسم الأول، الاسم الأخير، وما إلى ذلك. فسألصق هذا هنا. وهو أمر واضح جدًا. ففي هنا لديّ مخطّطٌ حدّدت فيه قواعد التحقّق من الصحة لكلّ حقل فحسب.

[06:09] فالاسم الأول ينبغي أن يكون سلسلةً نصّيّة، وأن يمتلك حدًّا أدنى من اثنين (2) وحدًّا أقصى من خمسين (50) وما إلى ذلك. ولدينا كذلك age ينبغي أن يكون عددًا (number)، بحدٍّ أدنى وحدٍّ أقصى وما إلى ذلك، وacceptance ينبغي أن يكون من نوع boolean.

[06:23] وemail ينبغي أن يكون بريدًا إلكترونيًا (email). فبذلك سيُتحقَّق من حقل البريد الإلكتروني أنّه عنوان بريد إلكتروني صحيح وما إلى ذلك. فإذا كنت معتادًا على Zod فسيكون هذا بسيطًا جدًا. أما إن لم تكن كذلك فيمكن أن نعمل ربما فيديو حول

[06:35] Zod. فأُعلمْني إذا كنت تفضّل فيديو مخصّصًا لـZod وحده. وعلى كلّ حال، لدينا أيضًا address وهو كائن متشعّب (nested). فلدينا كائن Zod آخر، ثم لَدَينا street وcity وstate وما إلى ذلك.

[06:48] ثم مهاراتنا (skills) وهي مصفوفة (array) في Zod، وفي كلّ عنصر من عناصر المصفوفة لَدَينا كائنًا يحتوي على name وid ومستوى (level)، والمستوى هو تعداد (enum) بقيم beginner وintermediate وexpert وما إلى ذلك. لكن الآن وبعد أن

[07:02] أصبح عندنا هذا؛ فأولًا، تذكّر أنّنا عرّفنا قيمنا الافتراضية (default values) وأنّنا أعطيناه نوعًا هو register form، وهو الواجهة (interface) التي عرّفناها هنا. غير أنّه الآن وبعد امتلاكنا مخطّط Zod هذا، يمكننا ربما حذف هذا

[07:16] كليًّا، وباستخدام Zod يمكننا أن نستنتج نوع TypeScript لدينا من مخطّطنا. فبتعريف المخطّط يمكننا، بصورةٍ ما، إنشاء نوع TypeScript أو استنتاجه من المخطّط بدلًا من اضطرارنا إلى تعريف نوعنا هكذا. فحين نعطي نوعًا

[07:33] لقيمنا الافتراضية، بدلًا من فعل ذلك هكذا، يمكننا استخدام z.infer، ويمكننا أن نستنتج نوع مخطّطنا بوضع هذا هنا داخل وسيطٍ عام (generic)، أي typeof، ثم اسم مخطّطنا وهو formSchema، هكذا. والآن أخيرًا

[07:52] في نموذجنا، حين نعرّف نموذجنا باستخدام useForm، يمكننا تمرير validators الخاصة بنا، وهذه المرة مخطّط لا دالة. فبإمكاننا أن نمرّر هنا؛ لنقل إنّنا نحتاج إلى فعل ذلك على onChange. عذرًا، onChange، ويمكننا

[08:07] أن نمرّر formSchema الخاصّ بنا هكذا.

[08:11] إذًا رائع؛ فلنجرّب الأمر. ولنحذف أيضًا، ولنقل، هذا الكائن هنا كي نرى الأشياء بوضوح.

[08:19] فلنجري تحديثًا (refresh). ولنكتب شيئًا. وكما ترى لدينا أخطاء أثناء كتابتنا؛ فما علينا إلّا تعديل طريقة عرضنا للأخطاء، شبيهًا بما فعلناه مع الحقل الأول هذا. فلنفعل ذلك

[08:32] بسرعة. فلنسخ هذا. تذكّر أنّنا الآن نمرّ على مصفوفة من الكائنات بحلقة. فنحتاج إلى تعديل بقية الحقول. فلنسخ هذا ولنضعه لجميع حقولنا. فلنفعله، لنقل، لـ

[08:45] الاسم الأول. ولا تقلق أيضًا؛ فسننظّف الأمور لاحقًا. فنحن الآن نركّز فقط على المفاهيم. فلنفعل ذلك إذًا للاسم الأول. ولنستبدل هذا بهذا الحقل، وهو address.

[08:59] وكذلك لـpassword. فلنفعل ذلك لـpassword نفسه كذلك، لأنّ لدينا بعض قواعد التحقّق من الصحة لـpassword، ولنفعله لـconfirmPassword.

[09:12] وكان عندنا هنا أيضًا بالنسبة إلى مستوى المهارة (skill level). فلنحذف هذا، أي القديم، ونستبدله بالجديد.

[09:21] ولدينا كذلك حقل قبول الشروط (accept terms).

[09:24] فلنفعل ذلك كذلك: فإذا لم نقبل الشروط فسنحتاج إلى عرض خطأ، ولدينا أصلًا قاعدة تحقّق من الصحة لذلك معرَّفة في مخطّطنا، ولدينا رسالة خطأ كذلك. فإذا لم نقبل الشروط فسنحتاج إلى

[09:38] عرض خطأ. فلنذهب إذًا إلى حقل قبول الشروط (accept terms) الخاصّ بنا، وهو هنا.

[09:44] ولنعرض، بعد هذا label، هذا الخطأ كذلك.

[09:50] حسنًا. فالآن ينبغي أن تكون أخطاؤنا معروضة لجميع حقولنا.

[09:55] وهنا فوق، تذكّر، قبل نموذجنا كنّا نعرض أخطاء تتعلّق بالنموذج بأكمله لا بحقل معيّن فحسب.

[10:05] وهذه أيضًا الآن مصفوفة من الكائنات بدلًا من مصفوفة من السلاسل النصّيّة.

[10:09] فنحتاج إلى تعديل ذلك كذلك. غير أنّه لنحذفه كليًّا الآن فحسب. وسأعرض JSON لأخطاء نموذجنا فحسب. فإذا حفظنا هذا فسنحصل على مصفوفة من الأخطاء لجميع

[10:23] أخطائنا. غير أنّه لا نحتاج إلى ذلك. فما نعرضه هنا الآن كافٍ. لذا سأعلّق (comment) هذا الآن فحسب. صحيح.

[10:33] فالمشكلة الرئيسة هنا الآن هي أنّه إذا أجرينا تحديثًا، فإنّنا إذا كتبنا شيئًا في الاسم الأول فستُتحقَّق جميع حقولنا. وهذا يحدث افتراضيًّا (default) عندما نستخدم Zod، لأنّنا نمرّر مخطّط التحقّق من الصحة الخاصة بنا هنا، الذي يحدث

[10:47] على onChange، فجميع حقولنا ستُتحقَّق مقابل هذا المخطّط كلّما غيّرنا أيّ حقل.

[10:55] صحيح؟ فإذا أردنا ألّا نعرض خطأً حين لم نغيّر الحقل، فيمكننا فقط أن نأخذ مثالًا عند حقل street هذا مثلًا. فلنذهب إذًا إلى street. فحين نعرض خطأنا،

[11:11] يمكننا التحقّق من isValid. غير أنّه يمكننا أيضًا التحقّق مما إذا كان الحقل مُمسًّا (touched)، وألّا نعرض الخطأ إلّا إذا كان الحقل مُمسًّا. أو ربما يمكننا استخدام isDirty كذلك. فيمكننا التحقّق هنا في field.state.meta، لنقل، isDirty

[11:30] ونحفظ هذا. والآن، إذا أجرينا تحديثًا وكتبنا شيئًا هنا فخطأ street لن يظهر حتى نغيّر فعليًّا شيئًا في street.

[11:40] ويمكننا فعل ذلك لجميع أخطائنا كلّها. غير أنّي سأحذف هذا الآن، لأنّنا لاحقًا سننظّف الأمور وننقل طريقة عرضنا للأخطاء إلى مكوّن (component) منفصل وما إلى ذلك. فلنحذفه إذًا

[11:52] الآن فحسب، وسنتولّى الأمر لاحقًا، صحيح؟ غير أنّه لَدَينا أيضًا هذه