استرجاع - ليست هناك حاجة! استبدال useContext و useReducer في رد الفعل؟

صورة


يوم جيد ، خابروفسك!


أريد أن أتحدث عن الطريقة التي تعلمت بها مؤخرًا بعض "الخطافات" في React. ظهرت مؤخرًا نسبيًا ، في الإصدار [16.8.0] المؤرخ 6 فبراير 2019 (والذي ، وفقًا لسرعات التطوير لـ FrontEnd ، بالفعل منذ زمن طويل جدًا)


بعد قراءة الوثائق ، ركزت على خطاف useReducer وسألت نفسي على الفور السؤال: "هذا الشيء يمكن أن يحل محل Redux بالكامل؟" قضيت عدة أمسيات في التجارب وأريد الآن مشاركة النتائج واستنتاجاتي.


هل أحتاج إلى استبدال Redux مع useContext + useReducer؟


لفارغ الصبر - النتائج على الفور


الايجابيات:


  • يمكنك استخدام الخطافات (useContext + useReducer) بدلاً من Redux في التطبيقات الصغيرة (حيث لا توجد حاجة إلى مخفضات كبيرة مدمجة). في هذه الحالة ، قد يكون Redux بالفعل زائداً عن الحاجة.

سلبيات:


  • لقد تم بالفعل كتابة كمية كبيرة من التعليمات البرمجية على مجموعة من React + Redux ولا يبدو أنه من المستحسن إعادة كتابتها إلى خطافات (useContext + useReducer) ، على الأقل حتى الآن.
  • Redux هي مكتبة مثبتة ، والسنانير هي ابتكار ، وقد تتغير واجهاتها وسلوكها في المستقبل.
  • من أجل جعل استخدام useContext + useReducer مناسبًا جدًا ، سيتعين عليك كتابة بعض الدراجات.

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


دعنا نحاول معرفة ذلك


لنبدأ بمثال بسيط.


(reducer.js)


import React from "react"; export const ContextApp = React.createContext(); export const initialState = { app: { test: 'test_context' } }; export const testReducer = (state, action) => { switch(action.type) { case 'test_update': return { ...state, ...action.payload }; default: return state } }; 

حتى الآن ، يبدو المخفض لدينا تمامًا كما في Redux


(app.js)


 import React, {useReducer} from 'react' import {ContextApp, initialState, testReducer} from "./reducer.js"; import {IndexComponent} from "./IndexComponent.js" export const App = () => { //  reducer   state + dispatch   const [state, dispatch] = useReducer(testReducer, initialState); return ( //  ,     reducer   //  ContextApp   (dispatch  state) //      <ContextApp.Provider value={{dispatch, state}}> <IndexComponent/> </ContextApp.Provider> ) }; 

(IndexComponent.js)


 import React, {useContext} from "react"; import {ContextApp} from "./reducer.js"; export function IndexComponent() { //   useContext    ContextApp //  IndexComponent      ContextApp.Provider const {state, dispatch} = useContext(ContextApp); return ( //  dispatch    reducer.js   testReducer //    .    Redux <div onClick={() => {dispatch({ type: 'test_update', payload: { newVar: 123 } })}}> {JSON.stringify(state)} </div> ) } 

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


(reducer.js)


 ... export const testReducer = (state, data) => { return { ...state, ...data } ... 

(IndexComponent.js)


 ... return ( //      ,   type <div onClick={() => {dispatch({ newVar: 123 }> {JSON.stringify(state)} </div> ) ... 

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


أكثر تعقيدا الآن


لنلقِ نظرة على المثال التالي:


(IndexComponent.js)


 ... return ( //        //     -     //      ,     callback: <div onClick={() => { //  ,    callback, //   testReducer     state (state) => { const {tree_1} = state; return { tree_1: { ...tree_1, tree_2_1: { ...tree_1.tree_2_1, tree_3_1: 'tree_3_1 UPDATE' }, }, }; }> {JSON.stringify(state)} </div> ) ... 

(reducer.js)


 ... export const initialState = { tree_1: { tree_2_1: { tree_3_1: 'tree_3_1', tree_3_2: 'tree_3_2' }, tree_2_2: { tree_3_3: 'tree_3_3', tree_3_4: 'tree_3_4' } } }; export const testReducer = (state, callback) => { //      state      //      callback const action = callback(state); return { ...state, ...action } ... 

حسنًا ، اكتشفنا تحديث الشجرة أيضًا. رغم أنه في هذه الحالة ، من الأفضل بالفعل العودة إلى استخدام الأنواع الموجودة داخل testReducer وتحديث الشجرة وفقًا لنوع معين من الإجراءات. كل شيء يشبه في Redux ، فقط الحزمة الناتجة أصغر قليلاً [8].


عمليات غير متزامنة والإرسال


ولكن هل كل شيء على ما يرام؟ ماذا يحدث إذا ذهبنا لاستخدام عمليات غير متزامنة؟
للقيام بذلك ، سيتعين علينا تحديد الإرسال الخاص بنا. لنجربها!


(action.js)


 export const actions = { sendToServer: function ({dataForServer}) { //      ,   dispatch return function (dispatch) { //   dispatch    , //   state      dispatch(state => { return { pending: true } }); } } 

(IndexComponent.js)


 const [state, _dispatch] = useReducer(AppReducer, AppInitialState); //     dispatch   -> //    ,  Proxy const dispatch = (action) => action(_dispatch); ... dispatch(actions.sendToServer({dataForServer: 'data'})) ... 

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


(IndexComponent.js)


 ... dispatch( (dispatch) => dispatch(state => { return { {dataForServer: 'data'} } }) ) ... 

اتضح شيء مخيف ، أليس كذلك؟ للحصول على تحديث بسيط للبيانات ، أرغب بشدة في كتابة شيء مثل هذا:


(IndexComponent.js)


 ... dispatch({dataForServer: 'data'}) ... 

للقيام بذلك ، سيتعين عليك تغيير الخادم الوكيل لوظيفة الإرسال التي أنشأناها مسبقًا
(IndexComponent.js)


 const [state, _dispatch] = useReducer(AppReducer, AppInitialState); //  // const dispatch = (action) => action(_dispatch); //  const dispatch = (action) => { if (typeof action === "function") { action(_dispatch); } else { _dispatch(() => action) } }; ... 

الآن يمكننا تمرير كل من وظيفة الحركة وكائن بسيط لإرساله.
ولكن! مع النقل البسيط للكائن ، يجب أن تكون حذرًا ، فقد يتم إغراءك للقيام بذلك:


(IndexComponent.js)


 ... dispatch({ tree: { //  state         AppContext ...state.tree, data: 'newData' } }) ... 

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


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


(action.js)


 ... // ..  dispatch   callback,    //       (. reducer.js) dispatch(state => { return { dataFromServer: { ...state.dataFromServer, form_isPending: true } } }); axios({ method: 'post', url: `...`, data: {...} }).then(response => { dispatch(state => { //   axios     //         dispatch //     ,  state -    , // ..       testReducer (reducer.js) return { dataFromServer: { ...state.dataFromServer, form_isPending: false, form_request: response.data }, user: {} } }); }).catch(error => { dispatch(state => { // , state -    ) return { dataFromServer: { ...state.dataFromServer, form_isPending: false, form_request: { error: error.response.data } }, } }); ... 

الاستنتاجات:


  • لقد كانت تجربة مثيرة للاهتمام ، وعززت معرفتي الأكاديمية وتعلمت ميزات جديدة من رد الفعل
  • لن أستخدم هذا النهج في الإنتاج (على الأقل في الأشهر الستة المقبلة). للأسباب الموضحة أعلاه (هذه ميزة جديدة ، و Redux هي أداة مجربة وموثوقة) + ليس لدي أي مشاكل في الأداء لمطاردتها بعد الألف من الثانية التي يمكنك الفوز بها عن طريق التخلي عن المحرر [8]

يسعدني أن أعرف ، في التعليقات ، رأي الزملاء من الجزء الأمامي من Habrosobschestva لدينا!


المراجع:


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


All Articles