فصل الأحداث عن التأثيرات
معالجات الأحداث تعيد التشغيل فقط عندما تقوم بنفس التفاعل مرة أخرى. على عكس معالجات الأحداث، تعيد التأثيرات المزامنة إذا كانت بعض القيم التي تقرأها، مثل خاصية أو متغير حالة، مختلفة عما كانت عليه أثناء آخر عملية عرض. في بعض الأحيان، تريد أيضًا مزيجًا من السلوكين: تأثير يعيد التشغيل استجابةً لبعض القيم ولكن ليس غيرها. هذه الصفحة ستعلّمك كيفية القيام بذلك.
سوف تتعلم
- كيفية الاختيار بين معالج حدث وتأثير
- لماذا التأثيرات تفاعلية، ومعالجات الأحداث ليست كذلك
- ماذا تفعل عندما تريد أن يكون جزء من كود تأثيرك غير تفاعلي
- ما هي أحداث التأثير، وكيف تستخرجها من تأثيراتك
- كيف تقرأ أحدث الخصائص والحالة من التأثيرات باستخدام أحداث التأثير
الاختيار بين معالجات الأحداث والتأثيرات
أولاً، دعنا نلخص الفرق بين معالجات الأحداث والتأثيرات.
تخيل أنك تنفذ مكون غرفة دردشة. متطلباتك تبدو كالتالي:
- يجب أن يتصل مكونك تلقائيًا بغرفة الدردشة المحددة.
- عند النقر على زر "إرسال"، يجب أن يرسل رسالة إلى الدردشة.
لنفترض أنك نفذت بالفعل الكود الخاص بهما، لكنك غير متأكد من مكان وضعه. هل يجب أن تستخدم معالجات الأحداث أم التأثيرات؟ في كل مرة تحتاج للإجابة على هذا السؤال، فكر فيسبب الحاجة لتشغيل الكود.
تعمل معالجات الأحداث استجابةً لتفاعلات محددة
من منظور المستخدم، يجب أن يحدث إرسال الرسالةلأنزر "إرسال" المحدد تم النقر عليه. سيشعر المستخدم بالاستياء الشديد إذا أرسلت رسالته في أي وقت آخر أو لأي سبب آخر. لهذا السبب يجب أن يكون إرسال الرسالة معالج حدث. تتيح لك معالجات الأحداث التعامل مع تفاعلات محددة:
باستخدام معالج حدث، يمكنك التأكد من أنsendMessage(message)سوففقطيعمل إذا ضغط المستخدم على الزر.
تعمل التأثيرات كلما كانت المزامنة مطلوبة
تذكر أنك تحتاج أيضًا إلى إبقاء المكون متصلًا بغرفة الدردشة. أين يذهب هذا الكود؟
السببلتشغيل هذا الكود ليس تفاعلًا محددًا. لا يهم لماذا أو كيف انتقل المستخدم إلى شاشة غرفة الدردشة. الآن بعد أن ينظر إليها ويمكنه التفاعل معها، يحتاج المكون إلى البقاء متصلًا بخادم الدردشة المحدد. حتى لو كان مكون غرفة الدردشة هو الشاشة الأولية لتطبيقك، ولم يقم المستخدم بأي تفاعلات على الإطلاق، فإنكستظلبحاجة إلى الاتصال. هذا هو السبب في أنه تأثير:
بهذا الكود، يمكنك التأكد من وجود اتصال نشط دائمًا بخادم الدردشة المحدد حاليًا،بغض النظرعن التفاعلات المحددة التي قام بها المستخدم. سواء كان المستخدم قد فتح تطبيقك فقط، أو اختار غرفة مختلفة، أو انتقل إلى شاشة أخرى وعاد، فإن تأثيرك يضمن أن المكون سوفيبقى متزامنًامع الغرفة المحددة حاليًا، وسوفيعيد الاتصال كلما كان ذلك ضروريًا.
القيم التفاعلية والمنطق التفاعلي
بشكل بديهي، يمكنك القول إن معالجات الأحداث يتم تشغيلها دائمًا "يدويًا"، على سبيل المثال عن طريق النقر على زر. التأثيرات، من ناحية أخرى، "تلقائية": تعمل وتعيد التشغيل بقدر ما هو ضروري للبقاء متزامنة.
هناك طريقة أكثر دقة للتفكير في هذا الأمر.
الخصائص، والحالة، والمتغيرات المعلنة داخل جسم مكونك تسمىقيم تفاعلية. في هذا المثال،
يمكن أن تتغير القيم التفاعلية مثل هذه بسبب إعادة التصيير. على سبيل المثال، قد يقوم المستخدم بتحريرmessageأو اختيارroomIdمختلف في القائمة المنسدلة. تستجيب معالجات الأحداث والتأثيرات للتغييرات بشكل مختلف:
- المنطق داخل معالجات الأحداثليس تفاعليًا.لن يعمل مرة أخرى إلا إذا قام المستخدم بتنفيذ نفس التفاعل (مثل النقر) مرة أخرى. يمكن لمعالجات الأحداث قراءة القيم التفاعلية دون "الاستجابة" لتغييراتها.
- المنطق داخل التأثيراتتفاعلي.إذا قرأ تأثيرك قيمة تفاعلية،يجب عليك تحديدها كاعتماد.ثم، إذا تسبب إعادة التصيير في تغيير تلك القيمة، سيعيد React تشغيل منطق تأثيرك بالقيمة الجديدة.
دعنا نراجع المثال السابق لتوضيح هذا الاختلاف.
المنطق داخل معالجات الأحداث ليس تفاعليًا
ألق نظرة على هذا السطر من الكود. هل يجب أن يكون هذا المنطق تفاعليًا أم لا؟
من منظور المستخدم،تغييرmessage لايعني أنهم يريدون إرسال رسالة.إنه يعني فقط أن المستخدم يكتب. بمعنى آخر، المنطق الذي يرسل رسالة يجب ألا يكون تفاعليًا. يجب ألا يعمل مرة أخرى فقط لأنالقيمة التفاعليةقد تغيرت. لهذا السبب ينتمي إلى معالج الحدث:
معالجات الأحداث ليست تفاعلية، لذا فإنsendMessage(message)سيعمل فقط عندما ينقر المستخدم على زر الإرسال.
المنطق داخل التأثيرات تفاعلي
الآن دعنا نعود إلى هذه الأسطر:
من منظور المستخدم،تغييرroomIdيعنيأنهم يريدون الاتصال بغرفة مختلفة.بمعنى آخر، منطق الاتصال بالغرفة يجب أن يكون تفاعليًا. أنتتريدأن "تواكب" هذه الأسطر من الكودالقيمة التفاعلية، وأن تعمل مرة أخرى إذا كانت تلك القيمة مختلفة. لهذا السبب ينتمي إلى تأثير:
التأثيرات تفاعلية، لذا فإنcreateConnection(serverUrl, roomId) و connection.connect()سيعملان لكل قيمة مميزة منroomId. يحافظ تأثيرك على اتصال الدردشة متزامنًا مع الغرفة المحددة حاليًا.
استخراج المنطق غير التفاعلي من التأثيرات
تصبح الأمور أكثر تعقيدًا عندما تريد خلط المنطق التفاعلي مع المنطق غير التفاعلي.
على سبيل المثال، تخيل أنك تريد عرض إشعار عندما يتصل المستخدم بالدردشة. تقرأ السمة الحالية (داكنة أو فاتحة) من الخصائص (props) حتى تتمكن من عرض الإشعار باللون الصحيح:
ومع ذلك، فإنthemeهي قيمة تفاعلية (يمكن أن تتغير نتيجة لإعادة التصيير)، ويجب الإعلان عن كل قيمة تفاعلية يقرأها تأثير كاعتماد له.الآن يجب عليك تحديدthemeكاعتماد لتأثيرك:
جرب هذا المثال وانظر إذا كان بإمكانك اكتشاف المشكلة في تجربة المستخدم هذه:
عندما يتغيرroomId، يعيد الدردشة الاتصال كما هو متوقع. ولكن بما أنthemeهو أيضًا تبعية، فإن الدردشةأيضًاتعيد الاتصال في كل مرة تنتقل فيها بين سمة الظلام والضوء. هذا ليس جيدًا!
بمعنى آخر، أنتلا تريدأن يكون هذا السطر تفاعليًا، حتى وإن كان داخل Effect (وهو تفاعلي):
أنت بحاجة إلى طريقة لفصل هذا المنطق غير التفاعلي عن Effect التفاعلي المحيط به.
إعلان حدث Effect
استخدم خطافًا خاصًا يُسمىuseEffectEventلاستخراج هذا المنطق غير التفاعلي من Effect الخاص بك:
هنا، يُسمىonConnected حدث Effect.إنه جزء من منطق Effect الخاص بك، لكنه يتصرف بشكل يشبه كثيرًا معالج الأحداث. المنطق بداخله ليس تفاعليًا، وهو دائمًا "يرى" أحدث قيم الخصائص والحالة.
الآن يمكنك استدعاء حدث EffectonConnectedمن داخل Effect الخاص بك:
هذا يحل المشكلة. لاحظ أنه كان عليكإزالةthemeمن قائمة تبعيات Effect الخاص بك، لأنه لم يعد مستخدمًا في Effect. كما أنك لا تحتاج إلىإضافةonConnectedإليها، لأنأحداث Effect ليست تفاعلية ويجب حذفها من التبعيات.
تحقق من أن السلوك الجديد يعمل كما هو متوقع:
يمكنك التفكير في أحداث Effect على أنها تشبه إلى حد كبير معالجات الأحداث. الفرق الرئيسي هو أن معالجات الأحداث تعمل استجابةً لتفاعلات المستخدم، بينما يتم تشغيل أحداث Effect بواسطتك من داخل Effects. تتيح لك أحداث Effect "كسر السلسلة" بين تفاعلية Effects والكود الذي لا ينبغي أن يكون تفاعليًا.
قراءة أحدث الخصائص والحالة باستخدام أحداث Effect
تتيح لك أحداث Effect إصلاح العديد من الأنماط التي قد تميل فيها إلى قمع مدقق التبعيات.
على سبيل المثال، لنفترض أن لديك Effect لتسجيل زيارات الصفحة:
لاحقًا، تضيف مسارات متعددة إلى موقعك. الآن مكونPageيتلقى خاصيةurlمع المسار الحالي. تريد تمريرurlكجزء من استدعاءlogVisitالخاص بك، لكن مدقق التبعيات يشكو:
فكر فيما تريد أن يفعله الكود. أنتتريدتسجيل زيارة منفصلة لعناوين URL المختلفة لأن كل عنوان URL يمثل صفحة مختلفة. بعبارة أخرى، هذا الاستدعاءlogVisit يجبأن يكون تفاعليًا فيما يتعلق بـurl. لهذا السبب، في هذه الحالة، من المنطقي اتباع مدقق التبعيات، وإضافةurlكتبعية:
لنفترض الآن أنك تريد تضمين عدد العناصر في سلة التسوق مع كل زيارة صفحة:
لقد استخدمتnumberOfItemsداخل Effect، لذا يطلب منك المدقق إضافته كتبعية. ومع ذلك، أنتلا تريدأن يكون استدعاءlogVisitتفاعليًا فيما يتعلق بـnumberOfItems. إذا وضع المستخدم شيئًا في سلة التسوق، وتغيرnumberOfItems، فهذالا يعنيأن المستخدم زار الصفحة مرة أخرى. بعبارة أخرى،زيارة الصفحةهي، بمعنى ما، "حدث". يحدث في لحظة زمنية محددة.
قسّم الكود إلى جزأين:
هنا،onVisitهو حدث Effect. الكود بداخله ليس تفاعليًا. هذا هو السبب الذي يسمح لك باستخدامnumberOfItems(أي قيمة تفاعلية أخرى!) دون القلق من أن يتسبب ذلك في إعادة تنفيذ الكود المحيط عند التغييرات.
من ناحية أخرى، يظل التأثير نفسه تفاعليًا. يستخدم الكود داخل التأثير الخاصيةurl، لذا سيعيد التأثير التشغيل بعد كل إعادة عرض بقيمةurlمختلفة. وهذا بدوره سينادي حدث التأثيرonVisit.
نتيجة لذلك، ستناديlogVisitلكل تغيير فيurl، وستقرأ دائمًا أحدث قيمة لـnumberOfItems. ومع ذلك، إذا تغيرnumberOfItemsبمفرده، فلن يؤدي ذلك إلى إعادة تشغيل أي من الكود.
ملاحظة
قد تتساءل عما إذا كان بإمكانك استدعاءonVisit()بدون وسيطات، وقراءةurlداخلها:
هذا سيعمل، لكن من الأفضل تمرير هذاurlإلى حدث التأثير بشكل صريح.بتمريرurlكوسيطة إلى حدث التأثير الخاص بك، فأنت تقول أن زيارة صفحة بـurlمختلف تشكل "حدثًا" منفصلاً من منظور المستخدم.إنvisitedUrlهوجزءمن "الحدث" الذي وقع:
بما أن حدث التأثير الخاص بك "يطلب" بشكل صريحvisitedUrl، الآن لا يمكنك إزالةurlمن تبعيات التأثير عن طريق الخطأ. إذا قمت بإزالة تبعيةurl(مما يؤدي إلى احتساب زيارات الصفحات المميزة كزيارة واحدة)، فسوف يحذرك المدقق من ذلك. أنت تريد أن يكونonVisitتفاعليًا فيما يتعلق بـurl، لذا بدلاً من قراءةurlداخله (حيث لن يكون تفاعليًا)، فإنك تمررهمنتأثيرك.
يصبح هذا مهمًا بشكل خاص إذا كان هناك منطق غير متزامن داخل التأثير:
هنا،urlداخلonVisitيتوافق معأحدثurl(والذي قد يكون قد تغير بالفعل)، لكنvisitedUrlيتوافق معurlالذي تسبب في الأصل في تشغيل هذا التأثير (وهذا الاستدعاءonVisit).
قيود أحداث التأثير
أحداث التأثير محدودة للغاية في كيفية استخدامها:
- استدعها فقط من داخل التأثيرات.
- لا تمررها أبدًا إلى مكونات أو هوكس أخرى.
على سبيل المثال، لا تُعلن عن حدث تأثير وتمرره هكذا:
بدلاً من ذلك، قم دائمًا بتعريف أحداث التأثير مباشرة بجوار التأثيرات التي تستخدمها:
أحداث التأثير هي "قطع" غير تفاعلية من كود التأثير الخاص بك. يجب أن تكون بجوار التأثير الذي يستخدمها.
ملخص
- معالجات الأحداث تعمل استجابةً لتفاعلات محددة.
- التأثيرات تعمل كلما دعت الحاجة إلى المزامنة.
- المنطق داخل معالجات الأحداث ليس تفاعليًا.
- المنطق داخل التأثيرات تفاعلي.
- يمكنك نقل المنطق غير التفاعلي من التأثيرات إلى أحداث التأثير.
- استدعِ أحداث التأثير فقط من داخل التأثيرات.
- لا تمرر أحداث التأثير إلى مكونات أو هوكس أخرى.
Try out some challenges
Challenge 1 of 4:Fix a variable that doesn’t update #
This Timer component keeps a count state variable which increases every second. The value by which it’s increasing is stored in the increment state variable. You can control the increment variable with the plus and minus buttons.
However, no matter how many times you click the plus button, the counter is still incremented by one every second. What’s wrong with this code? Why is increment always equal to 1 inside the Effect’s code? Find the mistake and fix it.
