v19.2Latest

إعادة استخدام المنطق باستخدام الـ Hooks المخصصة

يأتي React مزودًا بعدة Hooks مدمجة مثلuseState، وuseContext، وuseEffect. في بعض الأحيان، قد تتمنى وجود Hook لغرض أكثر تحديدًا: على سبيل المثال، لجلب البيانات، أو لتتبع ما إذا كان المستخدم متصلًا بالإنترنت، أو للاتصال بغرفة دردشة. قد لا تجد هذه الـ Hooks في React، ولكن يمكنك إنشاء Hooks مخصصة تلبي احتياجات تطبيقك.

ما ستتعلمه
  • ما هي الـ Hooks المخصصة، وكيف تكتب الخاصة بك
  • كيفية إعادة استخدام المنطق بين المكونات
  • كيفية تسمية وتنظيم الـ Hooks المخصصة الخاصة بك
  • متى ولماذا تستخرج الـ Hooks المخصصة

الـ Hooks المخصصة: مشاركة المنطق بين المكونات

تخيل أنك تقوم بتطوير تطبيق يعتمد بشكل كبير على الشبكة (كما تفعل معظم التطبيقات). تريد تحذير المستخدم إذا انقطع اتصال الشبكة الخاص به عن طريق الخطأ أثناء استخدامه لتطبيقك. كيف ستتعامل مع ذلك؟ يبدو أنك ستحتاج إلى شيئين في مكونك:

  1. قطعة من الحالة (state) تتبع ما إذا كانت الشبكة متصلة أم لا.
  2. تأثير (Effect) يشترك في الأحداث العامةonline وoffline، ويقوم بتحديث تلك الحالة.

سيحافظ هذا على تزامن مكونكمع حالة الشبكة. قد تبدأ بشيء مثل هذا:

جرب تشغيل وإيقاف شبكتك، ولاحظ كيف يقوم هذا المكونStatusBarبالتحديث استجابة لأفعالك.

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

للبدء، يمكنك نسخ ولصق حالةisOnlineوالتأثير في مكونSaveButton:

تحقق من أنه إذا قمت بإيقاف تشغيل الشبكة، فسيغير الزر مظهره.

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

استخراج خطافك المخصص الخاص من مكون

تخيل للحظة أنه، على غرارuseState و useEffect، كان هناك خطاف مدمج يسمىuseOnlineStatus. عندها يمكن تبسيط كلا المكونين وإزالة التكرار بينهما:

على الرغم من عدم وجود مثل هذا الخطاف المدمج، يمكنك كتابته بنفسك. قم بتعريف دالة تسمىuseOnlineStatusونقل كل الكود المكرر إليها من المكونات التي كتبتها سابقًا:

في نهاية الدالة، قم بإرجاعisOnline. هذا يسمح لمكوناتك بقراءة تلك القيمة:

تحقق من أن تبديل الشبكة تشغيلاً وإيقافاً يقوم بتحديث كلا المكونين.

الآن لم تعد مكوناتك تحتوي على الكثير من المنطق المتكرر.والأهم من ذلك، أن الكود بداخلها يصفما تريد أن تفعله(استخدم حالة الاتصال!) بدلاً منكيفية تنفيذ ذلك(عن طريق الاشتراك في أحداث المتصفح).

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

أسماء الـ Hooks تبدأ دائمًا بـuse

تُبنى تطبيقات React من المكونات. تُبنى المكونات من الـ Hooks، سواء كانت مدمجة أو مخصصة. من المحتمل أن تستخدم غالبًا Hooks مخصصة أنشأها آخرون، ولكن في بعض الأحيان قد تكتب واحدة بنفسك!

يجب عليك اتباع اصطلاحات التسمية هذه:

  1. يجب أن تبدأ أسماء مكونات React بحرف كبير،مثلStatusBar و SaveButton. تحتاج مكونات React أيضًا إلى إرجاع شيء يعرف React كيفية عرضه، مثل جزء من JSX.
  2. يجب أن تبدأ أسماء الـ Hooks بـuseمتبوعة بحرف كبير،مثلuseState(مدمج) أوuseOnlineStatus(مخصص، كما في السابق في الصفحة). قد تُرجع الـ Hooks قيمًا عشوائية.

يضمن هذا الاصطلاح أنه يمكنك دائمًا النظر إلى مكون ومعرفة أين قد "تختبئ" حالته، والتأثيرات، والميزات الأخرى لـ React. على سبيل المثال، إذا رأيت استدعاء دالةgetColor()داخل مكونك، يمكنك التأكد من أنها لا يمكن أن تحتوي على حالة React بداخلها لأن اسمها لا يبدأ بـuse. ومع ذلك، فإن استدعاء دالة مثلuseOnlineStatus()سيحتوي على الأرجح على استدعاءات لـ Hooks أخرى بداخله!

ملاحظة

إذا كان أداة التحقق من الأخطاء (linter) لديكمضبوطة لـ React،فستفرض اصطلاح التسمية هذا. انتقل لأعلى إلى صندوق الرمل أعلاه وغير اسمuseOnlineStatusإلىgetOnlineStatus. لاحظ أن أداة التحقق من الأخطاء لن تسمح لك باستدعاءuseStateأوuseEffectبداخلها بعد الآن. فقط الـ Hooks والمكونات يمكنها استدعاء Hooks أخرى!

Deep Dive
هل يجب أن تبدأ جميع الدوال التي يتم استدعاؤها أثناء التصيير ببادئة use؟

تتيح لك الهوكس المخصصة مشاركة المنطق ذي الحالة، وليس الحالة نفسها

في المثال السابق، عند تشغيل وإيقاف الشبكة، تم تحديث كلا المكونين معًا. ومع ذلك، من الخطأ الاعتقاد بأن متغير حالة واحدisOnlineيتم مشاركته بينهما. انظر إلى هذا الكود:

يعمل بنفس الطريقة كما كان قبل استخراج التكرار:

هاتان متغيرا حالة وتأثيران مستقلان تمامًا! لقد صادف أن لهما نفس القيمة في نفس الوقت لأنك قمت بمزامنتهما مع نفس القيمة الخارجية (ما إذا كانت الشبكة قيد التشغيل).

لتوضيح هذا بشكل أفضل، سنحتاج إلى مثال مختلف. ضع في اعتبارك مكونFormهذا:

يوجد بعض المنطق المتكرر لكل حقل في النموذج:

  1. يوجد جزء من الحالة (firstName و lastName).
  2. يوجد معالج تغيير (handleFirstNameChange و handleLastNameChange).
  3. يوجد جزء من JSX يحدد سماتvalue و onChangeلذلك الإدخال.

يمكنك استخراج المنطق المتكرر في خطاف مخصصuseFormInputهذا:

لاحظ أنه يعلن فقط عنمتغير حالة واحد يسمى value.

ومع ذلك، يستدعي مكونForm خطاف useFormInputمرتين:

لهذا السبب يعمل الأمر مثل تعريف متغيري حالة منفصلين!

تتيح لك الـ Hooks المخصصة مشاركةالمنطق الحاوي على حالةوليسالحالة نفسها.كل استدعاء لـ Hook مستقل تمامًا عن أي استدعاء آخر لنفس الـ Hook.هذا هو السبب في أن صندوقي الرمل أعلاه متكافئان تمامًا. إذا أردت، يمكنك التمرير للأعلى ومقارنتهما. السلوك قبل وبعد استخراج الـ Hook المخصص متطابق.

عندما تحتاج إلى مشاركة الحالة نفسها بين عدة مكونات،ارفعها للأعلى ومررها للأسفلبدلاً من ذلك.

تمرير القيم التفاعلية بين الـ Hooks

سيتم إعادة تشغيل الكود داخل الـ Hooks المخصصة الخاصة بك أثناء كل إعادة عرض لمكونك. لهذا السبب، مثل المكونات، تحتاج الـ Hooks المخصصةأن تكون نقية.فكر في كود الـ Hooks المخصصة كجزء من جسم مكونك!

لأن الـ Hooks المخصصة تُعاد عرضها مع مكونك، فهي تتلقى دائمًا أحدث الـ props والحالة. لترى ما يعنيه هذا، فكر في مثال غرفة الدردشة هذا. غيّر عنوان URL للخادم أو غرفة الدردشة:

عندما تغيرserverUrlأوroomId، فإن الـ Effect"يتفاعل" مع تغييراتكويعيد المزامنة. يمكنك معرفة ذلك من خلال رسائل وحدة التحكم حيث يعيد الدردشة الاتصال في كل مرة تغير فيها تبعيات الـ Effect الخاصة بك.

الآن انقل كود الـ Effect إلى Hook مخصص:

هذا يتيح لمكونChatRoomالخاص بك استدعاء الـ Hook المخصص دون القلق بشأن كيفية عمله داخليًا:

هذا يبدو أبسط بكثير! (لكنه يفعل نفس الشيء.)

لاحظ أن المنطقلا يزال يستجيبلتغييرات الـ props والحالة. حاول تعديل عنوان URL للخادم أو الغرفة المحددة:

لاحظ كيف تأخذ القيمة المُرجعة من أحد الـ Hooks:

وتمررها كمدخل إلى Hook آخر:

في كل مرة يعيد مكونChatRoomالتصيير، فإنه يمرر أحدث قيمتيroomId و serverUrlإلى الـ Hook الخاص بك. هذا هو السبب في أن الـ Effect الخاص بك يعيد الاتصال بالدردشة كلما كانت قيمهما مختلفة بعد إعادة التصيير. (إذا كنت قد عملت مع برامج معالجة الصوت أو الفيديو، فقد يذكرك ربط الـ Hooks بهذه الطريقة بربط المؤثرات المرئية أو الصوتية. الأمر كما لو أن ناتجuseState"يُغذي" مدخلاتuseChatRoom.)

تمرير معالجات الأحداث إلى الـ Hooks المخصصة

عندما تبدأ في استخدامuseChatRoomفي المزيد من المكونات، قد ترغب في السماح للمكونات بتخصيص سلوكها. على سبيل المثال، حاليًا، منطق ما يجب فعله عند وصول رسالة مُشفّر داخل الـ Hook:

لنفترض أنك تريد إعادة هذا المنطق إلى مكونك:

لجعل هذا يعمل، غيّر الـ Hook المخصص الخاص بك ليأخذonReceiveMessageكأحد خياراته المُسمّاة:

سيعمل هذا، ولكن هناك تحسين إضافي يمكنك إجراؤه عندما يقبل الـ Hook المخصص معالجات الأحداث.

إضافة تبعية علىonReceiveMessageليس مثاليًا لأنه سيتسبب في إعادة اتصال الدردشة في كل مرة يعيد فيها المكون التصيير.لفّ معالج الأحداث هذا في حدث Effect لإزالته من التبعيات:

الآن لن تعيد الدردشة الاتصال في كل مرة يعيد فيها مكونChatRoomالتصيير. إليك نموذج عمل كامل لتمرير معالج حدث إلى Hook مخصص يمكنك التجربة معه:

لاحظ كيف لم تعد بحاجة إلى معرفةكيفيةuseChatRoomعملها لاستخدامها. يمكنك إضافتها إلى أي مكون آخر، وتمرير أي خيارات أخرى، وستعمل بنفس الطريقة. هذه هي قوة الـ Hooks المخصصة.

متى تستخدم الـ Hooks المخصصة

لا تحتاج إلى استخراج Hook مخصص لكل جزء صغير مكرر من الكود. بعض التكرار مقبول. على سبيل المثال، استخراجuseFormInputHook لتغليف استدعاء واحد لـuseStateكما في المثال السابق ربما لا داعي له.

ومع ذلك، كلما كتبت Effect، فكر فيما إذا كان سيكون أكثر وضوحًا أيضًا أن تغلفه في Hook مخصص.لا يجب أن تحتاج إلى Effects كثيرًا،لذا إذا كنت تكتب واحدًا، فهذا يعني أنك بحاجة إلى "الخروج من React" للتزامن مع نظام خارجي ما أو للقيام بشيء ليس لدى React واجهة برمجة تطبيقات مدمجة له. تغليفه في Hook مخصص يتيح لك توصيل نيتك وكيفية تدفق البيانات من خلاله بدقة.

على سبيل المثال، ضع في اعتبارك مكونShippingFormالذي يعرض قائمتين منسدلتين: واحدة تعرض قائمة المدن، والأخرى تعرض قائمة المناطق في المدينة المحددة. قد تبدأ ببعض التعليمات البرمجية التي تبدو كالتالي:

على الرغم من أن هذا الكود متكرر إلى حد كبير،فمن الصحيح إبقاء هذه المؤثرات منفصلة عن بعضها البعض.إنها تزامن شيئين مختلفين، لذا لا يجب دمجها في مؤثر واحد. بدلاً من ذلك، يمكنك تبسيط مكونShippingFormأعلاه عن طريق استخراج المنطق المشترك بينهما إلى خطافك المخصصuseData:

الآن يمكنك استبدال كلا المؤثرين في مكوناتShippingFormباستدعاءات لـuseData:

استخراج خطاف مخصص يجعل تدفق البيانات صريحًا. تقوم بإدخالurlوتحصل علىdataكمخرج. من خلال "إخفاء" مؤثرك داخلuseData، فإنك تمنع أيضًا أي شخص يعمل على مكونShippingFormمن إضافةتبعيات غير ضروريةإليه. مع مرور الوقت، ستكون معظم مؤثرات تطبيقك في خطاطيف مخصصة.

Deep Dive
اجعل خطافاتك المخصصة مركزة على حالات استخدام عالية المستوى وملموسة

تساعدك الـ Custom Hooks على الانتقال إلى أنماط أفضل

تعتبر الـ Effects"مخرج طوارئ": تستخدمها عندما تحتاج إلى "الخروج من React" وعندما لا يوجد حل مدمج أفضل لحالة الاستخدام الخاصة بك. مع مرور الوقت، يهدف فريق React إلى تقليل عدد الـ Effects في تطبيقك إلى الحد الأدنى من خلال تقديم حلول أكثر تحديدًا لمشاكل أكثر تحديدًا. يجعل تغليف الـ Effects الخاصة بك في Custom Hooks من السهل ترقية الكود الخاص بك عندما تصبح هذه الحلول متاحة.

لنعد إلى هذا المثال:

في المثال أعلاه، تم تنفيذuseOnlineStatusباستخدام زوج منuseState و useEffect.ومع ذلك، هذا ليس أفضل حل ممكن. فهناك عدد من الحالات المتطرفة التي لا يأخذها في الاعتبار. على سبيل المثال، يفترض أنه عند تحميل المكون، تكونisOnlineبالفعلtrue، ولكن قد يكون هذا خطأً إذا كانت الشبكة قد انقطعت بالفعل. يمكنك استخدام واجهة برمجة التطبيقاتnavigator.onLineفي المتصفح للتحقق من ذلك، ولكن استخدامها مباشرة لن يعمل على الخادم لتوليد HTML الأولي. باختصار، يمكن تحسين هذا الكود.

يتضمن React واجهة برمجة تطبيقات مخصصة تسمىuseSyncExternalStoreوالتي تعتني بكل هذه المشاكل نيابة عنك. إليك الـ Hook الخاص بكuseOnlineStatus، مع إعادة كتابته للاستفادة من واجهة برمجة التطبيقات الجديدة هذه:

لاحظ كيفلم تكن بحاجة إلى تغيير أي من المكوناتلإجراء هذا الانتقال:

هذا سبب آخر يجعل تغليف التأثيرات في خطافات مخصصة مفيدًا في كثير من الأحيان:

  1. أنت تجعل تدفق البيانات من وإلى تأثيراتك واضحًا جدًا.
  2. أنت تسمح لمكوناتك بالتركيز على النية بدلاً من التركيز على التنفيذ الدقيق لتأثيراتك.
  3. عندما تضيف React ميزات جديدة، يمكنك إزالة تلك التأثيرات دون تغيير أي من مكوناتك.

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

Deep Dive
هل سيوفر React أي حل مدمج لجلب البيانات؟

هناك أكثر من طريقة لفعل ذلك

لنفترض أنك تريد تنفيذ رسوم متحركة للظهور التدريجيمن الصفرباستخدام واجهة برمجة المتصفحrequestAnimationFrame. قد تبدأ بتأثير يقوم بإعداد حلقة للرسوم المتحركة. خلال كل إطار من الرسوم المتحركة، يمكنك تغيير عتامة عقدة DOM التيتحتفظ بها في مرجعحتى تصل إلى1. قد يبدأ الكود الخاص بك هكذا:

لجعل المكون أكثر قابلية للقراءة، يمكنك استخراج المنطق إلى خطاف مخصصuseFadeIn:

يمكنك الاحتفاظ بكودuseFadeInكما هو، ولكن يمكنك أيضًا إعادة هيكلته أكثر. على سبيل المثال، يمكنك استخراج منطق إعداد حلقة الرسوم المتحركة منuseFadeInإلى خطاف مخصصuseAnimationLoop:

لكنك لم تكنمضطرًالفعل ذلك. كما هو الحال مع الدوال العادية، أنت من يقرر في النهاية أين ترسم الحدود بين أجزاء الكود المختلفة. يمكنك أيضًا اتباع نهج مختلف تمامًا. بدلاً من الاحتفاظ بالمنطق داخل التأثير، يمكنك نقل معظم المنطق الأمرّي داخلفئة JavaScript:

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

تفترض الأمثلة أعلاه أن منطق التلاشي التدريجي يحتاج إلى كتابته بلغة JavaScript. ومع ذلك، فإن حركة التلاشي التدريجي هذه المحددة هي أبسط وأكثر كفاءة بكثير عند تنفيذها باستخدامحركة CSS عادية:

في بعض الأحيان، لا تحتاج حتى إلى خطاف!

ملخص

  • تسمح لك الخطافات المخصصة بمشاركة المنطق بين المكونات.
  • يجب أن تبدأ أسماء الخطافات المخصصة بـuseمتبوعة بحرف كبير.
  • تشارك الخطافات المخصصة المنطق ذا الحالة فقط، وليس الحالة نفسها.
  • يمكنك تمرير قيم تفاعلية من خطاف إلى آخر، وتبقى محدثة.
  • تعيد جميع الخطافات التشغيل في كل مرة يعيد فيها المكون الخاص بك التصيير.

Try out some challenges

Challenge 1 of 5:Extract a useCounter Hook #

This component uses a state variable and an Effect to display a number that increments every second. Extract this logic into a custom Hook called useCounter. Your goal is to make the Counter component implementation look exactly like this:

export default function Counter() {
  const count = useCounter();
  return <h1>Seconds passed: {count}</h1>;
}

You’ll need to write your custom Hook in useCounter.js and import it into the App.js file.


إعادة استخدام المنطق باستخدام الخطافات المخصصة (Custom Hooks) | React Learn - Reflow Hub