طلب API مع React Hooks أو HOC أو Render Prop


النظر في تنفيذ طلب البيانات إلى واجهة برمجة التطبيقات باستخدام صديق جديد React Hooks والأصدقاء القدامى Render Prop و HOC (مكون النظام العالي). تعرف على ما إذا كان الصديق الجديد أفضل حقًا من الاثنين القديمين.


الحياة لا تهدأ ، رد الفعل يتغير للأفضل. في فبراير 2019 ، ظهر React Hooks في React 16.8.0. الآن في المكونات الوظيفية ، يمكنك العمل مع الدولة المحلية وإجراء تأثيرات جانبية. لا أحد يعتقد أن ذلك ممكن ، لكن الجميع أراد ذلك دائمًا. إذا لم تكن مطلعًا على التفاصيل ، فانقر هنا للحصول على التفاصيل.


React Hooks تجعل من الممكن أخيرًا التخلي عن أنماط مثل HOC و Render Prop. لأنه أثناء الاستخدام ، تراكمت عدد من المطالبات ضدهم:


RPropHOC
1. العديد من مكونات المجمع التي يصعب فهمها في React DevTools وفي التعليمات البرمجية.(◕ (◕)(◕ (◕)
2. من الصعب الكتابة (Flow، TypeScript).(◕ (◕)
3. ليس من الواضح من الذي يقوم HOC بدعم الدعائم التي يتلقاها المكون ، مما يعقد عملية تصحيح الأخطاء وفهم كيفية عمل المكون.(◕ (◕)
4. Render Prop في أغلب الأحيان لا يضيف تخطيطات ، على الرغم من أنه يتم استخدامه داخليًا بواسطة JSX.(◕ (◕)
5. الدعائم الاصطدام الرئيسية. عند إرسال الدعائم من أولياء الأمور ، يمكن الكتابة على نفس المفاتيح بقيم من HOC.(◕ (◕)
6. من الصعب قراءة فرق git ، حيث يتم تبديل كل المسافة البادئة في JSX عند التفاف JSX في Render Prop.(◕ (◕)
7. إذا كان هناك العديد من HOCs ، فيمكنك ارتكاب خطأ في تسلسل التكوين. الترتيب الصحيح ليس واضحًا دائمًا ، حيث يتم إخفاء المنطق داخل HOC. على سبيل المثال ، عندما نتحقق أولاً مما إذا كان المستخدم مصرحًا له ، وعندها فقط نطلب بيانات شخصية.(◕ (◕)

لكي لا تكون بلا أساس ، دعنا نلقي نظرة على مثال حول كيف يكون React Hooks أفضل (أو ربما أسوأ) Render Prop. سننظر في Render Prop ، وليس HOC ، نظرًا لأنها متشابهة جدًا ولديها HOC المزيد من العيوب. دعنا نحاول كتابة أداة مساعدة تقوم بمعالجة طلب البيانات إلى واجهة برمجة التطبيقات. أنا متأكد من أن الكثيرين كتبوا هذا في حياتهم مئات المرات ، حسنًا ، دعنا نرى ما إذا كان ذلك ممكنًا وأسهل.


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


  • عملية الحصول على البيانات (isFetching)
  • تم استلام البيانات بنجاح (responseData)
  • خطأ في تلقي البيانات (خطأ)
  • إلغاء الطلب ، إذا كانت معلمات الطلب قد تغيرت أثناء تنفيذه وتحتاج إلى إرسال رسالة جديدة
  • إلغاء طلب إذا كان هذا المكون لم يعد في DOM

1. سيناريو بسيط


سنكتب الحالة الافتراضية ووظيفة (المخفض) التي تتغير الحالة اعتمادًا على نتيجة الطلب: النجاح / الخطأ.


ما هو المخفض؟

للرجوع اليها. جاء إلينا المخفض من البرمجة الوظيفية ، وبالنسبة لمعظم مطوري JS من Redux. هذه هي الوظيفة التي تأخذ حالة سابقة وإجراء وتعيد الحالة التالية.


const defaultState = { responseData: null, isFetching: true, error: null }; function reducer1(state, action) { switch (action.type) { case "fetched": return { ...state, isFetching: false, responseData: action.payload }; case "error": return { ...state, isFetching: false, error: action.payload }; default: return state; } } 

نعيد استخدام هذه الوظيفة بطريقتين.


تقديم الدعامة


 class RenderProp1 extends React.Component { state = defaultState; axiosSource = null; tryToCancel() { if (this.axiosSource) { this.axiosSource.cancel(); } } dispatch(action) { this.setState(prevState => reducer(prevState, action)); } fetch = () => { this.tryToCancel(); this.axiosSource = axios.CancelToken.source(); axios .get(this.props.url, { cancelToken: this.axiosSource.token }) .then(response => { this.dispatch({ type: "fetched", payload: response.data }); }) .catch(error => { this.dispatch({ type: "error", payload: error }); }); }; componentDidMount() { this.fetch(); } componentDidUpdate(prevProps) { if (prevProps.url !== this.props.url) { this.fetch(); } } componentWillUnmount() { this.tryToCancel(); } render() { return this.props.children(this.state); } 

رد فعل السنانير


 const useRequest1 = url => { const [state, dispatch] = React.useReducer(reducer, defaultState); React.useEffect(() => { const source = axios.CancelToken.source(); axios .get(url, { cancelToken: source.token }) .then(response => { dispatch({ type: "fetched", payload: response.data }); }) .catch(error => { dispatch({ type: "error", payload: error }); }); return source.cancel; }, [url]); return [state]; }; 

بواسطة url ، من المكون المستخدم ، نحصل على البيانات - axios.get (). نعالج النجاح والخطأ ، ونغير الحالة من خلال الإرسال (الإجراء). عودة الدولة إلى المكون. ولا تنسَ إلغاء الطلب إذا تغير عنوان url أو إذا تمت إزالة المكون من DOM. إنها بسيطة ، ولكن يمكنك الكتابة بطرق مختلفة. نسلط الضوء على إيجابيات وسلبيات النهجين:


السنانيرRProp
1. رمز أقل.(◑‿◐)
2. يسهل قراءة التأثير الجانبي (طلب البيانات في واجهة برمجة التطبيقات) ، حيث إنه مكتوب خطيًا ، ولا ينتشر عبر دورات حياة المكون.(◑‿◐)
3. يتم كتابة إلغاء الطلب مباشرة بعد استدعاء الطلب. كل ذلك في مكان واحد.(◑‿◐)
4. رمز بسيط يصف معلمات التتبع لتحريك الآثار الجانبية.(◑‿◐)
5. من الواضح ، في أي دورة حياة مكون سيتم تنفيذ الكود الخاص بنا.(◑‿◐)

تتيح لك React Hooks كتابة كود أقل ، وهذه حقيقة لا جدال فيها. وهذا يعني أن فعالية أنت كمطور آخذ في الازدياد. ولكن عليك أن تتقن نموذجا جديدا.


عندما تكون هناك أسماء لدورات حياة المكونات ، يكون كل شيء واضحًا للغاية. أولاً ، نحصل على البيانات بعد ظهور المكون على الشاشة (componentDidMount) ، ثم نحصل عليه مرة أخرى إذا تم تغيير props.url وقبل ذلك لا ننسى إلغاء الطلب السابق (componentDidUpdate) ، إذا تمت إزالة المكون من DOM ، فقم بإلغاء الطلب (componentWillUnmount) .


لكننا الآن نتسبب في آثار جانبية مباشرة في العرض ، لقد علمنا أن هذا غير ممكن. على الرغم من التوقف ، وليس حقا في تقديم. وداخل وظيفة useEffect ، والتي ستؤدي شيئًا غير متزامن بعد كل تجسيد ، أو بالأحرى تلتزم وتعرض DOM الجديد.


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


نموذج جديد

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


الخلاصة - استخدام useEffect. الكتابة أقل وأكثر أمانا.


وإحدى الميزات الرائعة الأخرى: يمكن لـ useEffect التنظيف بعد التأثير السابق وبعد إزالة المكون من DOM. بفضل Rx الذي ألهم فريق React لهذا النهج.


استخدام فائدتنا مع React Hooks هو أيضًا أكثر ملاءمة.


 const AvatarRenderProp1 = ({ username }) => ( <RenderProp url={`https://api.github.com/users/${username}`}> {state => { if (state.isFetching) { return "Loading"; } if (state.error) { return "Error"; } return <img src={state.responseData.avatar_url} alt="avatar" />; }} </RenderProp> ); 

 const AvatarWithHook1 = ({ username }) => { const [state] = useRequest(`https://api.github.com/users/${username}`); if (state.isFetching) { return "Loading"; } if (state.error) { return "Error"; } return <img src={state.responseData.avatar_url} alt="avatar" />; }; 

يبدو خيار React Hooks مرة أخرى أكثر إحكاما ووضوحًا.


سلبيات تقديم الدعامة:


1) من غير الواضح ما إذا كان التصميم مضافًا أم منطقًا فقط
2) إذا كنت بحاجة إلى معالجة الحالة من Render Prop في الولاية المحلية أو في دورات حياة المكون الفرعي ، فسيتعين عليك إنشاء مكون جديد


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


2) تحديث بيانات عمل المستخدم


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


سوف نستخدمها مثل هذا:


 const Avatar2 = ({ username }) => { ... <button onClick={() => update("https://api.github.com/users/NewUsername")} > Update avatar for New Username </button> ... }; 

دعنا نكتب التنفيذ. أدناه مكتوبة فقط التغييرات مقارنة بالإصدار الأصلي.


 function reducer2(state, action) { switch (action.type) { ... case "update url": return { ...state, isFetching: true, url: action.payload, defaultUrl: action.payload }; case "update url manually": return { ...state, isFetching: true, url: action.payload, defaultUrl: state.defaultUrl }; ... } } 

تقديم الدعامة


 class RenderProp2 extends React.Component { state = { responseData: null, url: this.props.url, defaultUrl: this.props.url, isFetching: true, error: null }; static getDerivedStateFromProps(props, state) { if (state.defaultUrl !== props.url) { return reducer(state, { type: "update url", payload: props.url }); } return null; } ... componentDidUpdate(prevProps, prevState) { if (prevState.url !== this.state.url) { this.fetch(); } } ... update = url => { this.dispatch({ type: "update url manually", payload: url }); }; render() { return this.props.children(this.state, this.update); } } 

رد فعل السنانير


 const useRequest2 = url => { const [state, dispatch] = React.useReducer(reducer, { url, defaultUrl: url, responseData: null, isFetching: true, error: null }); if (url !== state.defaultUrl) { dispatch({ type: "update url", payload: url }); } React.useEffect(() => { …(fetch data); }, [state.url]); const update = React.useCallback( url => { dispatch({ type: "update url manually", payload: url }); }, [dispatch] ); return [state, update]; }; 

إذا نظرت بعناية إلى الكود ، لاحظت:


  • بدأ تخزين عنوان url داخل فائدتنا.
  • يبدو defaultUrl لتحديد أنه تم تحديث عنوان url عبر الدعائم. نحتاج إلى مراقبة تغيير props.url ، وإلا لن يتم إرسال طلب جديد ؛
  • إضافة وظيفة التحديث ، والتي نعود إلى المكون لإرسال طلب جديد من خلال النقر على الزر.

إشعار مع Render Prop ، كان علينا استخدام getDerivedStateFromProps لتحديث الحالة المحلية في حالة حدوث تغييرات في props.url. ومع عدم وجود React Hooks تجريدات جديدة ، يمكنك على الفور استدعاء تحديث الحالة في التجسيد - يا رفاق ، الرفاق ، أخيرًا!


كانت المضاعفات الوحيدة مع React Hooks هي تذكير وظيفة التحديث بحيث لا تتغير بين تحديثات المكونات. عندما تكون وظيفة التحديث ، كما في Render Prop ، طريقة فصل.


3) الاقتراع API في نفس الفاصل الزمني أو الاقتراع


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


استخدام:


 const AvatarRenderProp3 = ({ username }) => ( <RenderProp url={`https://api.github.com/users/${username}`} pollInterval={1000}> ... 

 const AvatarWithHook3 = ({ username }) => { const [state, update] = useRequest( `https://api.github.com/users/${username}`, 1000 ); ... 

التنفيذ:


 function reducer3(state, action) { switch (action.type) { ... case "poll": return { ...state, requestId: state.requestId + 1, isFetching: true }; ... } } 

تقديم الدعامة


 class RenderProp3 extends React.Component { state = { ... requestId: 1, } ... timeoutId = null; ... tryToClearTimeout() { if (this.timeoutId) { clearTimeout(this.timeoutId); } } poll = () => { this.tryToClearTimeout(); this.timeoutId = setTimeout(() => { this.dispatch({ type: 'poll' }); }, this.props.pollInterval); }; ... componentDidUpdate(prevProps, prevState) { ... if (this.props.pollInterval) { if ( prevState.isFetching !== this.state.isFetching && !this.state.isFetching ) { this.poll(); } if (prevState.requestId !== this.state.requestId) { this.fetch(); } } } componentWillUnmount() { ... this.tryToClearTimeout(); } ... 

رد فعل السنانير


 const useRequest3 = (url, pollInterval) => { const [state, dispatch] = React.useReducer(reducer, { ... requestId: 1, }); React.useEffect(() => { …(fetch data) }, [state.url, state.requestId]); React.useEffect(() => { if (!pollInterval || state.isFetching) return; const timeoutId = setTimeout(() => { dispatch({ type: "poll" }); }, pollInterval); return () => { clearTimeout(timeoutId); }; }, [pollInterval, state.isFetching]); ... } 

لقد ظهرت دعامة جديدة - pollInterval. عند الانتهاء من الطلب السابق عبر setTimeout ، نقوم بزيادة requestId. مع السنانير ، لدينا استخدام آخرالتأثير ، والذي نسميه setTimeout. وبدأ استخدامنا القديم ، الذي يرسل طلبًا ، في مراقبة متغير آخر - requestId ، والذي يخبرنا أن setTimeout يعمل ، وحان الوقت لإرسال طلب الصورة الرمزية الجديدة.


في Render Prop ، كان علي أن أكتب:


  1. مقارنة قيم requestId و isFetching السابقة والجديدة
  2. مهلة واضحة في مكانين
  3. إضافة خاصية timeoutId إلى الفصل الدراسي

تتيح لك React Hooks أن تكتب بإيجاز وواضح ما استخدمناه لوصف بمزيد من التفصيل وليست واضحة دائمًا.


4) ماذا بعد؟
يمكننا الاستمرار في توسيع وظائف الأداة لدينا: قبول التكوينات المختلفة لمعلمات الاستعلام ، وتخزين البيانات مؤقتًا ، وتحويل الاستجابة والأخطاء ، وتحديث البيانات قسراً بنفس المعلمات - العمليات الروتينية في أي تطبيق ويب كبير. في مشروعنا ، أخذنا هذا منذ فترة طويلة في مكون منفصل (انتباه!). نعم ، لأنه كان تقديم الدعامة. ولكن مع إصدار Hooks ، قمنا بإعادة كتابة الوظيفة (useAxiosRequest) وحتى وجدنا بعض الأخطاء في التطبيق القديم. يمكنك أن ترى وتجرب هنا .

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


All Articles