كيفية إنشاء خطاف تفاعل مخصص لجلب البيانات وتخزينها مؤقتًا

نشرت: 2022-03-10
ملخص سريع ↬ هناك احتمال كبير بأن الكثير من المكونات في تطبيق React الخاص بك سوف تضطر إلى إجراء مكالمات إلى واجهة برمجة التطبيقات لاسترداد البيانات التي سيتم عرضها لمستخدميك. من الممكن فعل ذلك بالفعل باستخدام طريقة دورة حياة componentDidMount() ، ولكن مع إدخال الخطافات ، يمكنك إنشاء خطاف مخصص يجلب البيانات ويخزنها مؤقتًا. هذا ما سيغطي هذا البرنامج التعليمي.

إذا كنت مبتدئًا في React Hooks ، يمكنك البدء بمراجعة الوثائق الرسمية لفهمها. بعد ذلك ، أوصي بقراءة كتاب Shedrack Akintayo "الشروع في استخدام واجهة برمجة تطبيقات React Hooks". لضمان متابعتك ، هناك أيضًا مقال كتبه Adeneye David Abiodun يغطي أفضل الممارسات مع React Hooks والتي أنا متأكد من أنها ستكون مفيدة لك.

خلال هذه المقالة ، سنستخدم Hacker News Search API لإنشاء خطاف مخصص يمكننا استخدامه لجلب البيانات. على الرغم من أن هذا البرنامج التعليمي سيغطي واجهة برمجة تطبيقات Hacker News Search ، فإننا سنعمل على ربط العمل بطريقة تعيد الاستجابة من أي رابط صالح لواجهة برمجة التطبيقات نمرره إليه.

أفضل ممارسات التفاعل

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

إحضار البيانات في مكون رد فعل

قبل خطافات React ، كان من المعتاد جلب البيانات الأولية في طريقة دورة حياة componentDidMount() والبيانات المستندة إلى تغييرات الخاصية أو الحالة في طريقة دورة حياة componentDidUpdate() .

وإليك كيف يعمل:

 componentDidMount() { const fetchData = async () => { const response = await fetch( `https://hn.algolia.com/api/v1/search?query=JavaScript` ); const data = await response.json(); this.setState({ data }); }; fetchData(); } componentDidUpdate(previousProps, previousState) { if (previousState.query !== this.state.query) { const fetchData = async () => { const response = await fetch( `https://hn.algolia.com/api/v1/search?query=${this.state.query}` ); const data = await response.json(); this.setState({ data }); }; fetchData(); } }

يتم استدعاء طريقة دورة حياة componentDidMount بمجرد تثبيت المكون ، وعندما يتم ذلك ، ما فعلناه هو تقديم طلب للبحث عن "JavaScript" عبر Hacker News API وتحديث الحالة بناءً على الاستجابة.

من ناحية أخرى ، يتم استدعاء طريقة دورة حياة componentDidUpdate عند حدوث تغيير في المكوِّن. قمنا بمقارنة الاستعلام السابق في الحالة مع الاستعلام الحالي لمنع استدعاء الطريقة في كل مرة نقوم فيها بتعيين "البيانات" في الحالة. أحد الأشياء التي نحصل عليها من استخدام الخطافات هو الجمع بين كل من طريقتي دورة الحياة بطريقة أنظف - مما يعني أننا لن نحتاج إلى طريقتين لدورة الحياة عند تثبيت المكون ووقت تحديثه.

المزيد بعد القفز! أكمل القراءة أدناه ↓

إحضار البيانات باستخدام خطاف useEffect

يتم استدعاء الخطاف useEffect بمجرد تركيب المكون. إذا احتجنا إلى إعادة تشغيل الخطاف بناءً على بعض التغييرات الخاصة بالخاصية أو الحالة ، فسنحتاج إلى تمريرها إلى مصفوفة التبعية (وهي الوسيطة الثانية useEffect ).

دعنا نستكشف كيفية إحضار البيانات باستخدام الخطافات:

 import { useState, useEffect } from 'react'; const [status, setStatus] = useState('idle'); const [query, setQuery] = useState(''); const [data, setData] = useState([]); useEffect(() => { if (!query) return; const fetchData = async () => { setStatus('fetching'); const response = await fetch( `https://hn.algolia.com/api/v1/search?query=${query}` ); const data = await response.json(); setData(data.hits); setStatus('fetched'); }; fetchData(); }, [query]);

في المثال أعلاه ، مررنا query باعتباره تبعية إلى الخطاف useEffect بنا. من خلال القيام بذلك ، فإننا نخبر useEffect بتتبع تغييرات الاستعلام. إذا لم تكن قيمة query السابقة مماثلة للقيمة الحالية ، فسيتم استدعاء useEffect مرة أخرى.

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

من المهم تعيين البيانات قبل محاولة ضبط الحالة على fetched حتى تتمكن من منع الوميض الذي يحدث نتيجة أن تكون البيانات فارغة أثناء قيامك بتعيين حالة fetched .

إنشاء خطاف مخصص

"الخطاف المخصص هو وظيفة JavaScript يبدأ اسمها بـ" use "وقد تستدعي الخطافات الأخرى."

- React Docs

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

لقد أعطاه التعريف من React Docs بعيدًا ولكن دعنا نرى كيف يعمل عمليًا مع خطاف مخصص مضاد:

 const useCounter = (initialState = 0) => { const [count, setCount] = useState(initialState); const add = () => setCount(count + 1); const subtract = () => setCount(count - 1); return { count, add, subtract }; };

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

في كل مكان في تطبيقنا حيث نحتاج إلى عداد ، يمكننا استدعاء useCounter عادية واجتياز الحالة initialState حتى نعرف من أين نبدأ العد. عندما لا يكون لدينا حالة أولية ، فإننا نتخلى عن 0.

إليك كيف تعمل في الممارسة:

 import { useCounter } from './customHookPath'; const { count, add, subtract } = useCounter(100); eventHandler(() => { add(); // or subtract(); });

ما فعلناه هنا هو استيراد الخطاف المخصص من الملف الذي أعلناه فيه ، حتى نتمكن من الاستفادة منه في تطبيقنا. قمنا بتعيين حالته الأولية على 100 ، لذلك كلما استدعينا add() ، فإنه يزيد count بمقدار 1 ، وكلما نسميه subtract() ، فإنه ينقص count بمقدار 1.

إنشاء useFetch Hook

الآن بعد أن تعلمنا كيفية إنشاء خطاف مخصص بسيط ، فلنستخرج منطقنا لجلب البيانات إلى خطاف مخصص.

 const useFetch = (query) => { const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!query) return; const fetchData = async () => { setStatus('fetching'); const response = await fetch( `https://hn.algolia.com/api/v1/search?query=${query}` ); const data = await response.json(); setData(data.hits); setStatus('fetched'); }; fetchData(); }, [query]); return { status, data }; };

إنه إلى حد كبير نفس الشيء الذي فعلناه أعلاه باستثناء كونه وظيفة تأخذ query وتعيد status data . وهذا هو خطاف useFetch الذي يمكننا استخدامه في عدة مكونات في تطبيق React الخاص بنا.

يعمل هذا ، ولكن مشكلة هذا التطبيق الآن هي أنه خاص بـ Hacker News لذلك قد نسميها فقط useHackerNews . ما نعتزم القيام به هو إنشاء خطاف useFetch يمكن استخدامه لاستدعاء أي عنوان URL. دعنا نجدده ليأخذ عنوان URL بدلاً من ذلك!

 const useFetch = (url) => { const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!url) return; const fetchData = async () => { setStatus('fetching'); const response = await fetch(url); const data = await response.json(); setData(data); setStatus('fetched'); }; fetchData(); }, [url]); return { status, data }; };

الآن ، خطاف الجلب الخاص بنا عام ويمكننا استخدامه كما نريد في مكوناتنا المختلفة.

إليك طريقة واحدة لاستهلاكه:

 const [query, setQuery] = useState(''); const url = query && `https://hn.algolia.com/api/v1/search?query=${query}`; const { status, data } = useFetch(url);

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

Memoizing البيانات التي تم جلبها

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

ملاحظة : لمزيد من السياق ، يمكنك التحقق من شرح Wikipedia حول Memoization.

دعنا نستكشف كيف يمكننا فعل ذلك!

 const cache = {}; const useFetch = (url) => { const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!url) return; const fetchData = async () => { setStatus('fetching'); if (cache[url]) { const data = cache[url]; setData(data); setStatus('fetched'); } else { const response = await fetch(url); const data = await response.json(); cache[url] = data; // set response in cache; setData(data); setStatus('fetched'); } }; fetchData(); }, [url]); return { status, data }; };

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

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

Memoizing البيانات مع useRef

" useRef يشبه الصندوق الذي يمكنه الاحتفاظ بقيمة قابلة للتغيير في .current property ."

- React Docs

باستخدام useRef ، يمكننا تعيين واسترداد القيم القابلة للتغيير بسهولة ، وتستمر قيمتها طوال دورة حياة المكون.

دعنا نستبدل تنفيذ ذاكرة التخزين المؤقت لدينا ببعض useRef magic!

 const useFetch = (url) => { const cache = useRef({}); const [status, setStatus] = useState('idle'); const [data, setData] = useState([]); useEffect(() => { if (!url) return; const fetchData = async () => { setStatus('fetching'); if (cache.current[url]) { const data = cache.current[url]; setData(data); setStatus('fetched'); } else { const response = await fetch(url); const data = await response.json(); cache.current[url] = data; // set response in cache; setData(data); setStatus('fetched'); } }; fetchData(); }, [url]); return { status, data }; };

هنا ، ذاكرة التخزين المؤقت لدينا هي الآن في خطاف useFetch مع كائن فارغ كقيمة أولية.

تغليف

حسنًا ، لقد ذكرت أن تعيين البيانات قبل تعيين حالة الجلب فكرة جيدة ، ولكن هناك مشكلتان محتملتان يمكن أن نواجههما في ذلك أيضًا:

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

لنقم بتنظيف نهائي لخطاف useFetch بنا. ، سنبدأ بتحويل useState بنا إلى useReducer . دعونا نرى كيف يعمل ذلك!

 const initialState = { status: 'idle', error: null, data: [], }; const [state, dispatch] = useReducer((state, action) => { switch (action.type) { case 'FETCHING': return { ...initialState, status: 'fetching' }; case 'FETCHED': return { ...initialState, status: 'fetched', data: action.payload }; case 'FETCH_ERROR': return { ...initialState, status: 'error', error: action.payload }; default: return state; } }, initialState);

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

هذا يحل المشكلتين اللتين ناقشناهما سابقًا ، حيث نصل ​​الآن إلى تعيين الحالة والبيانات في نفس الوقت للمساعدة في منع الحالات المستحيلة وعمليات إعادة العرض غير الضرورية.

لم يتبق سوى شيء واحد: تنظيف الآثار الجانبية. تقوم Fetch بتنفيذ Promise API ، بمعنى أنه يمكن حلها أو رفضها. إذا حاول الخطاف إجراء تحديث بينما لم يتم تثبيت المكون بسبب حل بعض Promise للتو ، فسيعود React Can't perform a React state update on an unmounted component.

دعونا نرى كيف يمكننا إصلاح ذلك من خلال تنظيف useEffect !

 useEffect(() => { let cancelRequest = false; if (!url) return; const fetchData = async () => { dispatch({ type: 'FETCHING' }); if (cache.current[url]) { const data = cache.current[url]; dispatch({ type: 'FETCHED', payload: data }); } else { try { const response = await fetch(url); const data = await response.json(); cache.current[url] = data; if (cancelRequest) return; dispatch({ type: 'FETCHED', payload: data }); } catch (error) { if (cancelRequest) return; dispatch({ type: 'FETCH_ERROR', payload: error.message }); } } }; fetchData(); return function cleanup() { cancelRequest = true; }; }, [url]);

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

خاتمة

لقد استكشفنا العديد من مفاهيم الخطافات للمساعدة في جلب البيانات وتخزينها مؤقتًا في مكوناتنا. لقد مررنا أيضًا بتنظيف خطاف useEffect الذي يساعد في منع عدد كبير من المشاكل في تطبيقنا.

إذا كان لديك أي أسئلة ، فلا تتردد في تركها في قسم التعليقات أدناه!

  • انظر الريبو لهذه المقالة →

مراجع

  • "تقديم الخطافات" ، React Docs
  • "الشروع في استخدام واجهة برمجة تطبيقات خطافات React" ، شيدراك أكينتايو
  • "أفضل الممارسات مع React Hooks" ، Adeneye David Abiodun
  • "البرمجة الوظيفية: وظائف خالصة" ، أرني براسور