استخراج منطق الحالة إلى Reducer
يمكن أن تصبح المكونات التي تحتوي على العديد من تحديثات الحالة المنتشرة عبر العديد من معالجي الأحداث مربكة. في هذه الحالات، يمكنك توحيد كل منطق تحديث الحالة خارج مكونك في دالة واحدة، تسمىreducer.
سوف تتعلم
- ما هي دالة الـ reducer
- كيفية إعادة هيكلة
useStateإلىuseReducer - متى تستخدم reducer
- كيف تكتبها بشكل جيد
توحيد منطق الحالة باستخدام reducer
مع نمو تعقيد مكوناتك، قد يصبح من الصعب رؤية جميع الطرق المختلفة التي يتم فيها تحديث حالة المكون بنظرة سريعة. على سبيل المثال، يحتفظ مكونTaskAppأدناه بمصفوفة منtasksفي الحالة ويستخدم ثلاثة معالجات أحداث مختلفة لإضافة المهام وإزالتها وتعديلها:
كل من معالجات الأحداث الخاصة به يستدعيsetTasksمن أجل تحديث الحالة. مع نمو هذا المكون، يزداد أيضًا مقدار منطق الحالة المنتشر فيه. لتقليل هذا التعقيد والحفاظ على كل منطقك في مكان واحد يسهل الوصول إليه، يمكنك نقل منطق الحالة هذا إلى دالة واحدة خارج مكونك،تسمى "reducer".
الـ reducers هي طريقة مختلفة للتعامل مع الحالة. يمكنك الانتقال منuseStateإلىuseReducerفي ثلاث خطوات:
- الانتقالمن تعيين الحالة إلى إرسال الإجراءات.
- كتابةدالة المُخفض.
- استخدامالمُخفض من المكون الخاص بك.
الخطوة 1: الانتقال من تعيين الحالة إلى إرسال الإجراءات
معالجات الأحداث الحالية تحددما يجب فعلهعن طريق تعيين الحالة:
قم بإزالة كل منطق تعيين الحالة. ما يتبقى لديك هو ثلاثة معالجات للأحداث:
handleAddTask(text)يتم استدعاؤها عندما يضغط المستخدم على "إضافة".handleChangeTask(task)يتم استدعاؤها عندما يقوم المستخدم بتبديل حالة مهمة أو يضغط على "حفظ".handleDeleteTask(taskId)يتم استدعاؤها عندما يضغط المستخدم على "حذف".
إدارة الحالة باستخدام المُخفضات تختلف قليلاً عن تعيين الحالة مباشرة. بدلاً من إخبار React "بما يجب فعله" عن طريق تعيين الحالة، تحدد "ما فعله المستخدم للتو" عن طريق إرسال "إجراءات" من معالجات الأحداث الخاصة بك. (سيتم وضع منطق تحديث الحالة في مكان آخر!) لذا بدلاً من "تعيينtasks" عبر معالج حدث، تقوم بإرسال إجراء "تمت إضافة/تغيير/حذف مهمة". هذا أكثر وصفًا لنية المستخدم.
الكائن الذي تمرره إلىdispatchيُسمى "إجراء":
إنه كائن JavaScript عادي. أنت تقرر ما تضعه فيه، ولكن بشكل عام يجب أن يحتوي على الحد الأدنى من المعلومات حولما حدث. (ستقوم بإضافة دالةdispatchنفسها في خطوة لاحقة.)
ملاحظة
يمكن أن يكون لكائن الإجراء أي شكل.
عادةً ما يُتبع اصطلاح يتمثل في إعطائه سلسلة نصيةtypeتصف ما حدث، وتمرير أي معلومات إضافية في حقول أخرى. إنtypeخاص بالمكون، لذا في هذا المثال فإن'added'أو'added_task'ستكون مناسبة. اختر اسمًا يصف ما حدث!
الخطوة 2: كتابة دالة المُخفض (reducer)
دالة المُخفض (reducer) هي المكان الذي ستضع فيه منطق الحالة. تأخذ وسيطين، الحالة الحالية وكائن الإجراء، وتُرجع الحالة التالية:
سيقوم React بتعيين الحالة إلى ما تُرجعه من دالة الاختزال.
لنقل منطق تعيين الحالة من معالجات الأحداث إلى دالة اختزال في هذا المثال، ستقوم بما يلي:
- تعلن عن الحالة الحالية (
tasks) كوسيط أول. - تعلن عن كائن
actionكوسيط ثانٍ. - ترجع الحالةالتاليةمن دالة الاختزال (والتي سيستخدمها React لتعيين الحالة).
إليك كل منطق تعيين الحالة الذي تم نقله إلى دالة اختزال:
لأن دالة الاختزال تأخذ الحالة (tasks) كوسيط، يمكنكتعلنها خارج المكون الخاص بك.هذا يقلل من مستوى المسافة البادئة ويمكن أن يجعل قراءة الكود أسهل.
ملاحظة
يستخدم الكود أعلاه عبارات if/else، ولكن من المعتاد استخدامعبارات switchداخل دوال الاختزال. النتيجة هي نفسها، ولكن قد يكون من الأسهل قراءة عبارات switch بنظرة سريعة.
سنستخدمها خلال بقية هذا التوثيق كما يلي:
نوصي بتغليف كل كتلةcaseداخل الأقواس المعقوفة{ و }بحيث لا تتعارض المتغيرات المُعلنة داخل حالاتcaseمختلفة مع بعضها البعض. أيضًا، يجب أن تنتهيcase عادةً بـ return. إذا نسيتreturn، فسوف "ينتقل" الكود إلىcaseالتالية، مما قد يؤدي إلى أخطاء!
إذا لم تكن مرتاحًا بعد لعبارات switch، فإن استخدام if/else مقبول تمامًا.
الخطوة 3: استخدم دالة الاختزال من المكون الخاص بك
أخيرًا، تحتاج إلى توصيلtasksReducerبمكونك. استورد خطافuseReducerمن React:
ثم يمكنك استبدالuseState:
بـuseReducerكما يلي:
خطافuseReducerمشابه لـuseState—يجب أن تمرر إليه حالة أولية ويعيد قيمة ذات حالة وطريقة لتعيين الحالة (في هذه الحالة، دالة الإرسال). لكنه مختلف قليلاً.
يأخذ خطافuseReducer وسيطين:
- دالة اختزال
- حالة أولية
ويعيد:
- قيمة ذات حالة
- دالة إرسال (لـ"إرسال" إجراءات المستخدم إلى دالة الاختزال)
الآن أصبح متصلاً بالكامل! هنا، تم التصريح عن دالة الاختزال في أسفل ملف المكون:
إذا أردت، يمكنك حتى نقل الـ reducer إلى ملف مختلف:
يمكن أن يكون منطق المكون أسهل في القراءة عندما تفصل الاهتمامات بهذه الطريقة. الآن معالجات الأحداث تحدد فقطما حدثعن طريق إرسال الإجراءات، وتحدد دالة الـ reducerكيف يتم تحديث الحالةاستجابة لها.
مقارنةuseState و useReducer
الـ reducers ليست خالية من العيوب! إليك بعض الطرق التي يمكنك من خلالها مقارنتها:
- حجم الكود:بشكل عام، مع
useStateعليك كتابة كود أقل في البداية. معuseReducer، عليك كتابة كل من دالة المُخفضوإرسال الإجراءات. ومع ذلك، يمكن لـuseReducerأن يساعد في تقليل الكود إذا كانت العديد من معالجات الأحداث تُعدل الحالة بطريقة مماثلة. - سهولة القراءة:
useStateسهل القراءة للغاية عندما تكون تحديثات الحالة بسيطة. عندما تصبح أكثر تعقيدًا، يمكن أن تنتفخ كود المكون الخاص بك وتجعل من الصعب مسحه ضوئيًا. في هذه الحالة، يتيح لكuseReducerفصل منطق التحديثكيفية عن ما حدثفي معالجات الأحداث بشكل نظيف. - التصحيح:عندما يكون لديك خطأ مع
useState، قد يكون من الصعب معرفةأينتم تعيين الحالة بشكل غير صحيح، ولماذا. معuseReducer، يمكنك إضافة سجل وحدة التحكم في المُخفض الخاص بك لرؤية كل تحديث للحالة، ولماذاحدث (بسبب أيaction). إذا كان كلactionصحيحًا، فستعرف أن الخطأ موجود في منطق المُخفض نفسه. ومع ذلك، عليك المرور عبر كود أكثر مما هو عليه معuseState. - الاختبار:المُخفض هو دالة نقية لا تعتمد على المكون الخاص بك. هذا يعني أنه يمكنك تصديرها واختبارها بشكل منفصل ومعزول. بينما يُفضل عمومًا اختبار المكونات في بيئة أكثر واقعية، بالنسبة لمنطق تحديث الحالة المعقد، يمكن أن يكون من المفيد التأكد من أن المُخفض الخاص بك يُرجع حالة معينة لحالة أولية وإجراء معين.
- التفضيل الشخصي:بعض الأشخاص يحبون المُخفضات، والبعض الآخر لا يحبونها. هذا مقبول. إنها مسألة تفضيل. يمكنك دائمًا التحويل بين
useStateوuseReducerذهابًا وإيابًا: فهما متكافئان!
نوصي باستخدام مُخفض إذا كنت تواجه أخطاء بشكل متكرر بسبب تحديثات حالة غير صحيحة في بعض المكونات، وتريد إدخال المزيد من التنظيم إلى كودها. ليس عليك استخدام المُخفضات لكل شيء: لا تتردد في المزج والمطابقة! يمكنك حتى استخدامuseState و useReducerفي نفس المكون.
كتابة المُخفضات بشكل جيد
تذكر هاتين النصيحتين عند كتابة المُخفضات:
- يجب أن تكون المخفضات نقية.على غراردوال تحديث الحالة، تعمل المخفضات أثناء التصيير! (يتم قائمة الإجراءات حتى التصيير التالي.) هذا يعني أن المخفضاتيجب أن تكون نقية—نفس المدخلات تؤدي دائمًا إلى نفس المخرجات. لا ينبغي لها إرسال طلبات، أو جدولة مهلات زمنية، أو تنفيذ أي تأثيرات جانبية (عمليات تؤثر على أشياء خارج المكون). يجب أن تقوم بتحديثالكائنات و المصفوفاتدون حدوث طفرات.
- يصف كل إجراء تفاعلًا واحدًا للمستخدم، حتى لو أدى ذلك إلى تغييرات متعددة في البيانات.على سبيل المثال، إذا ضغط مستخدم على "إعادة تعيين" في نموذج يحتوي على خمسة حقول تُدار بواسطة مخفض، فمن المنطقي أكثر إرسال إجراء واحد
reset_formبدلاً من خمسة إجراءات منفصلةset_field. إذا قمت بتسجيل كل إجراء في مخفض، يجب أن يكون سجل التسجيل واضحًا بما يكفي لإعادة بناء التفاعلات أو الاستجابات التي حدثت وبأي ترتيب. وهذا يساعد في التصحيح!
كتابة مخفضات موجزة باستخدام Immer
تمامًا كما هو الحال معتحديث الكائنات و المصفوفاتفي الحالة العادية، يمكنك استخدام مكتبة Immer لجعل المخفضات أكثر إيجازًا. هنا، تتيح لكuseImmerReducerتغيير الحالة باستخدامpushأو تعيينarr[i] =:
يجب أن تكون المخفضات نقية، لذا لا ينبغي لها تغيير الحالة. لكن Immer تزودك بكائنdraftخاص يمكن تغييره بأمان. في الخلفية، سيقوم Immer بإنشاء نسخة من حالتك مع التغييرات التي أجريتها علىdraft. هذا هو السبب في أن المخفضات التي تُدار بواسطةuseImmerReducerيمكنها تغيير وسيطها الأول ولا تحتاج إلى إرجاع الحالة.
ملخص
- للتحويل من
useStateإلىuseReducer:- أرسل الإجراءات من معالجي الأحداث.
- اكتب دالة مخفضة تُرجع الحالة التالية لحالة وإجراء معينين.
- استبدل
useStateبـuseReducer.
- تتطلب المخفضات كتابة كمية أكبر قليلاً من الكود، لكنها تساعد في التصحيح والاختبار.
- يجب أن تكون المخفضات نقية.
- يصف كل إجراء تفاعلًا واحدًا للمستخدم.
- استخدم Immer إذا كنت تريد كتابة المخفضات بأسلوب تغييري.
Try out some challenges
Challenge 1 of 4:Dispatch actions from event handlers #
Currently, the event handlers in ContactList.js and Chat.js have // TODO comments. This is why typing into the input doesn’t work, and clicking on the buttons doesn’t change the selected recipient.
Replace these two // TODOs with the code to dispatch the corresponding actions. To see the expected shape and the type of the actions, check the reducer in messengerReducer.js. The reducer is already written so you won’t need to change it. You only need to dispatch the actions in ContactList.js and Chat.js.
