إدارة الحالة باستخدام خطافات التفاعل - بدون واجهة برمجة تطبيقات للإرجاع والاسترجاع

مرحبا بالجميع! اسمي آرثر ، أعمل في فكونتاكتي كفريق ويب للجوال ، وأنا منخرط في مشروع VKUI - مكتبة مكونات التفاعل ، بمساعدة بعض واجهاتنا في تطبيقات الهاتف المحمول. لا تزال قضية العمل مع دولة عالمية مفتوحة لنا. هناك العديد من الأساليب المعروفة: Redux و MobX و Context API. صادفت مؤخرًا مقالًا لـ André Gardi State Management مع React Hooks - No Redux أو Context API ، حيث اقترح المؤلف استخدام React Hooks للتحكم في حالة التطبيق.

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

صورة

هوكس رد فعل أكثر قوة مما تعتقدون


سنقوم اليوم بدراسة React Hooks وتطوير ربط مخصص لإدارة الحالة العامة للتطبيق ، والتي ستكون أبسط من تطبيق Redux وأكثر إنتاجية من API Context.

رد فعل هوكس أساسيات


يمكنك تخطي هذا الجزء إذا كنت معتادًا على السنانير.

useState ()


قبل ظهور الخطافات ، لم يكن للمكونات الوظيفية القدرة على تعيين حالة محلية. لقد تغير الموقف مع ظهور useState() .



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

 import React, { useState } from 'react'; function Example() { const [state, setState] = useState({counter:0}); const add1ToCounter = () => { const newCounterValue = state.counter + 1; setState({ counter: newCounterValue}); } return ( <div> <p>You clicked {state.counter} times</p> <button onClick={add1ToCounter}> Click me </button> </div> ); } 

useEffect ()


تستجيب مكونات الفئة للآثار الجانبية باستخدام أساليب دورة الحياة مثل componentDidMount() . يسمح لك useEffect() بالقيام بنفس الشيء في المكونات الوظيفية.

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

 //     useEffect(() => { console.log('     '); }); //    useEffect(() => { console.log('     valueA'); }, [valueA]); 

لتحقيق نتيجة مشابهة لـ componentDidMount() ، سنقوم بتمرير صفيف فارغ إلى المعلمة الثانية. نظرًا لأن محتويات صفيف فارغ تظل دائمًا على حالها ، سيتم تنفيذ التأثير مرة واحدة فقط.

 //     useEffect(() => { console.log('    '); }, []); 

تقاسم الدولة


رأينا أن حالة الخطاف تعمل تمامًا مثل حالة مكون الفئة. كل مثيل مكون له حالته الداخلية الخاصة.

لمشاركة الحالة بين المكونات ، سنقوم بإنشاء رابط خاص بنا.



والفكرة هي إنشاء مجموعة من المستمعين وحالة واحدة فقط. في كل مرة يتغير فيها أحد المكونات ، تتصل جميع المكونات المشتركة بـ getState() ويتم تحديثها بسبب ذلك.

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

انتظر لحظة كيف تجعل حياتي أسهل؟


نعم انت على حق لقد أنشأت حزمة NPM التي تحتوي على كل المنطق الموصوف.

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

 npm install -s use-global-hook 

لفهم كيفية التعامل مع حزمة ، قم بدراسة الأمثلة في الوثائق. والآن أقترح التركيز على كيفية ترتيب الحزمة في الداخل.

الإصدار الأول


 import { useState, useEffect } from 'react'; let listeners = []; let state = { counter: 0 }; const setState = (newState) => { state = { ...state, ...newState }; listeners.forEach((listener) => { listener(state); }); }; const useCustom = () => { const newListener = useState()[1]; useEffect(() => { listeners.push(newListener); }, []); return [state, setState]; }; export default useCustom; 

استخدام في المكون


 import React from 'react'; import useCustom from './customHook'; const Counter = () => { const [globalState, setGlobalState] = useCustom(); const add1Global = () => { const newCounterValue = globalState.counter + 1; setGlobalState({ counter: newCounterValue }); }; return ( <div> <p> counter: {globalState.counter} </p> <button type="button" onClick={add1Global}> +1 to global </button> </div> ); }; export default Counter; 

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

ولكن يمكننا أن نفعل ما هو أفضل


ماذا تريد:

  • إزالة المستمع من الصفيف عند إلغاء تحميل المكون ؛
  • جعل الخطاف أكثر تجريدًا لاستخدامه في المشروعات الأخرى ؛
  • إدارة initialState باستخدام المعلمات ؛
  • أعد كتابة الخطاف بأسلوب أكثر فاعلية.

استدعاء وظيفة فقط قبل إلغاء تثبيت مكون


لقد اكتشفنا بالفعل أن استدعاء useEffect(function, []) مع صفيف فارغ يعمل بنفس طريقة componentDidMount() . ولكن إذا كانت الدالة التي تم تمريرها في المعلمة الأولى تُرجع دالة أخرى ، فسيتم استدعاء الوظيفة الثانية مباشرة قبل إلغاء تثبيت المكون. تماما مثل componentWillUnmount() .

لذلك ، في رمز الوظيفة الثانية ، يمكنك كتابة المنطق لإزالة مكون من مجموعة من المستمعين.

 const useCustom = () => { const newListener = useState()[1]; useEffect(() => { //     listeners.push(newListener); return () => { //     listeners = listeners.filter(listener => listener !== newListener); }; }, []); return [state, setState]; }; 

الإصدار الثاني


بالإضافة إلى هذا التحديث ، نخطط أيضًا:

  • تمرير رد فعل المعلمة والتخلص من الاستيراد ؛
  • تصدير لا customHook ، ولكن دالة تقوم بإرجاع customHook مع initalState المعطى ؛
  • إنشاء كائن store يحتوي على قيمة state setState() ؛
  • setState() وظائف السهم بالوظائف المعتادة في setState() useCustom() بحيث يمكنك ربط store this .

 function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { //     this.listeners.push(newListener); return () => { //     this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.setState]; } const useGlobalHook = (React, initialState) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); return useCustom.bind(store, React); }; export default useGlobalHook; 

إجراءات منفصلة عن المكونات


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

سيكون من الأصح فصل منطق العمل عن طريق إنشاء إجراءات لتغيير الحالة. لذلك ، أريد أن يوفر أحدث إصدار من الحزمة وصولاً للمكونات ليس إلى setState() ، ولكن إلى مجموعة من الإجراءات.

للقيام بذلك ، نحن useGlobalHook(React, initialState, actions) لدينا useGlobalHook(React, initialState, actions) الوسيطة الثالثة. فقط أريد أن أضيف بعض التعليقات.

  • الإجراءات لديها حق الوصول إلى store . بهذه الطريقة ، يمكن للإجراءات قراءة محتويات store.state ، وتحديث store.setState() استدعاء store.setState() وحتى استدعاء store.actions أخرى عن طريق store.actions .
  • لتجنب الفوضى ، قد يحتوي كائن الإجراء على مشاريع فرعية. وبالتالي ، يمكنك نقل actions.addToCounter(amount ) إلى ملف فرعي مع كل إجراءات العداد: actions.counter.add(amount) .

النسخة النهائية


المقتطف التالي هو الإصدار الحالي من حزمة NPM use-global-hook .

 function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { this.listeners.push(newListener); return () => { this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.actions]; } function associateActions(store, actions) { const associatedActions = {}; Object.keys(actions).forEach((key) => { if (typeof actions[key] === 'function') { associatedActions[key] = actions[key].bind(null, store); } if (typeof actions[key] === 'object') { associatedActions[key] = associateActions(store, actions[key]); } }); return associatedActions; } const useGlobalHook = (React, initialState, actions) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); store.actions = associateActions(store, actions); return useCustom.bind(store, React); }; export default useGlobalHook; 

أمثلة الاستخدام


لم يعد عليك التعامل مع useGlobalHook.js . الآن يمكنك التركيز على التطبيق الخاص بك. فيما يلي مثالان لاستخدام الحزمة.

عدادات متعددة ، قيمة واحدة


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

طلبات اياكس غير متزامن


بحث مستودعات جيثب حسب اسم المستخدم. نحن نعالج طلبات اياكس بشكل غير متزامن باستخدام المزامنة / الانتظار. نقوم بتحديث عداد الاستعلام مع كل بحث جديد.
مثال حي .

حسنا هذا كل شيء


لدينا الآن مكتبة إدارة الحالة الخاصة بنا على React Hooks.

تعليق المترجم


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

Source: https://habr.com/ru/post/ar454348/


All Articles