एक और Redux बॉयलरप्लेट रिडक्शन गाइड (NGRX)


इसके बारे में क्या होगा?


हम एंटरप्राइज के भगवान के लिए कई (पांच, विशिष्ट होने के लिए) तरीकों, चाल, खूनी बलिदानों के बारे में बात करेंगे, जो हमारे Redux (और NGRX!) अनुप्रयोगों में अधिक संक्षिप्त और अभिव्यंजक कोड लिखने में हमारी मदद करते हैं। तरीके पसीने और कॉफी से ग्रस्त हैं। कृपया किक करें और कड़ी आलोचना करें। हम एक साथ बेहतर कोड बनाना सीखेंगे।


ईमानदारी से, सबसे पहले मैं दुनिया को अपनी नई माइक्रो-लाइब्रेरी (कोड की 35 लाइनें!) फ्लक्स-एक्शन-क्लास के बारे में बताना चाहता था, लेकिन बढ़ती संख्याओं को देखते हुए कि जल्द ही हेबर ट्विटर बन जाएगा, और सबसे अधिक भाग के लिए उनके साथ सहमत होते हुए, मैंने कुछ हद तक अधिक पढ़ने की कोशिश करने का फैसला किया। तो, हम आपके Redux एप्लिकेशन को अपग्रेड करने के लिए 5 तरीके से मिलते हैं!


बॉयलरप्लेट बाहर आता है


रेडक्स के लिए एक AJAX अनुरोध भेजने के लिए एक विशिष्ट उदाहरण पर विचार करें। आइए कल्पना करें कि हमें वास्तव में सर्वर से जवानों की सूची की आवश्यकता है।


import { createSelector } from 'reselect' const actionTypeCatsGetInit = 'CATS_GET_INIT' const actionTypeCatsGetSuccess = 'CATS_GET_SUCCESS' const actionTypeCatsGetError = 'CATS_GET_ERROR' const actionCatsGetInit = () => ({ type: actionTypeCatsGetInit }) const actionCatsGetSuccess = (payload) => ({ type: actionTypeCatsGetSuccess, payload, }) const actionCatsGetError = (error) => ({ type: actionTypeCatsGetError, payload: error, }) const reducerCatsInitialState = { error: undefined, data: undefined, loading: false, } const reducerCats = (state = reducerCatsInitialState, action) => { switch (action.type) { case actionTypeCatsGetInit: return { ...state, loading: true, } case actionCatsGetSuccess: return { error: undefined, data: action.payload, loading: false, } case actionCatsGetError: return { ...data, error: action.payload, loading: false, } default: return state } } const makeSelectorCatsData = () => createSelector( (state) => state.cats.data, (cats) => cats, ) const makeSelectorCatsLoading = () => createSelector( (state) => state.cats.loading, (loading) => loading, ) const makeSelectorCatsError = () => createSelector( (state) => state.cats.error, (error) => error, ) 

यदि आप यह नहीं समझते हैं कि चयनकर्ताओं के लिए कारखानों की आवश्यकता क्यों है, तो आप इसके बारे में यहाँ पढ़ सकते हैं


मैं जानबूझकर यहाँ दुष्प्रभावों पर विचार नहीं करता। यह किशोरों के गुस्से और मौजूदा पारिस्थितिकी तंत्र की आलोचना से भरे एक अलग लेख का विषय है: डी


इस कोड में कई कमजोर बिंदु हैं:


  • कार्रवाई के कारखाने अपने आप में अद्वितीय हैं, लेकिन हम अभी भी कार्रवाई प्रकारों का उपयोग करते हैं।
  • जैसे ही नई इकाइयाँ जोड़ी जाती हैं, हम loading फ्लैग को सेट करने के लिए उसी तर्क की नकल करते रहते हैं। डेटा जिसे हम data में संग्रहीत data , और उनका फॉर्म अनुरोध से अनुरोध तक काफी भिन्न हो सकता है, लेकिन डाउनलोड संकेतक ( loading फ्लैग) अभी भी समान होगा।
  • स्विच रन टाइम O (n) (वेल, लगभग ) है। यह अपने आप में एक बहुत मजबूत तर्क नहीं है, क्योंकि Redux, सिद्धांत रूप में, प्रदर्शन के बारे में नहीं है। यह मुझे और अधिक प्रभावित करता है कि प्रत्येक case आपको सेवारत कोड की कुछ अतिरिक्त पंक्तियों को लिखना होगा, और यह कि एक switch को आसानी से और खूबसूरती से कई में विभाजित नहीं किया जा सकता है।
  • क्या हमें वास्तव में प्रत्येक इकाई के लिए त्रुटि स्थिति को अलग से संग्रहीत करने की आवश्यकता है?
  • चयनकर्ता शांत हैं। ज्ञापन चयनकर्ता दोगुने शांत हैं। वे हमें अपने पक्ष में एक अमूर्तता देते हैं, ताकि बाद में हमें फॉर्म को बदलते समय आधे आवेदन को फिर से न करना पड़े। हम सिर्फ चयनकर्ता को बदलते हैं। आंख को भाता नहीं है, आदिम कारखानों का एक सेट है जो केवल पुनरावृत्ति में संस्मरण की ख़ासियतों के कारण आवश्यक हैं।

विधि 1: कार्रवाई प्रकार से छुटकारा पाएं


खैर, वास्तव में नहीं। हम सिर्फ जेएस को उनके लिए बनाते हैं।


आइए एक दूसरे के बारे में सोचें कि हमें आम तौर पर कार्रवाई के प्रकारों की आवश्यकता क्यों है। खैर, जाहिर है, हमारे reducer में तर्क की वांछित शाखा शुरू करने और तदनुसार आवेदन की स्थिति को बदलने के लिए। असली सवाल यह है कि क्या एक प्रकार का तार होना चाहिए? लेकिन क्या होगा अगर हम कक्षाओं का उपयोग करते हैं और प्रकार से switch ?


 class CatsGetInit {} class CatsGetSuccess { constructor(responseData) { this.payload = responseData } } class CatsGetError { constructor(error) { this.payload = error this.error = true } } const reducerCatsInitialState = { error: undefined, data: undefined, loading: false, } const reducerCats = (state = reducerCatsInitialState, action) => { switch (action.constructor) { case CatsGetInit: return { ...state, loading: true, } case CatsGetSuccess: return { error: undefined, data: action.payload, loading: false, } case CatsGetError: return { ...data, error: action.payload, loading: false, } default: return state } } 

सब कुछ बहुत अच्छा लग रहा है, लेकिन एक समस्या है: हमने अपने कार्यों की क्रमबद्धता खो दी। ये अब सरल वस्तुएँ नहीं हैं जिन्हें हम एक स्ट्रिंग में बदल सकते हैं और इसके विपरीत। अब हम इस तथ्य पर भरोसा करते हैं कि प्रत्येक कार्रवाई का अपना अनूठा प्रोटोटाइप है, जो वास्तव में, इस तरह के डिजाइन को action.constructor पर switch अनुमति देता है। काम करने के लिए action.constructor । आप जानते हैं, मुझे वास्तव में अपने कार्यों को एक स्ट्रिंग में क्रमबद्ध करने और उन्हें बग रिपोर्ट के साथ भेजने का विचार पसंद है, और मैं इसे मना करने के लिए तैयार नहीं हूं।


तो, प्रत्येक क्रिया में एक type फ़ील्ड होना चाहिए ( यहां आप देख सकते हैं कि हर क्रिया-प्रतिक्रिया करने वाले को और क्या करना चाहिए)। सौभाग्य से, प्रत्येक वर्ग का एक नाम है जो एक स्ट्रिंग की तरह है। आइए प्रत्येक वर्ग में एक गेटर type जोड़ते हैं जो इस वर्ग का नाम वापस कर देगा।


 class CatsGetInit { constructor() { this.type = this.constructor.name } } const reducerCats = (state, action) => { switch (action.type) { case CatsGetInit.name: return { ...state, loading: true, } //... } } 

यह भी काम करता है, लेकिन मैं प्रत्येक प्रकार के लिए एक उपसर्ग छड़ी करना चाहूंगा, जैसा कि श्री एरिक बतख-मॉड्यूलर-रेडक्स में सुझाव देता है (मैं फिर से बतख के कांटे को देखने की सलाह देता हूं, जो कि मेरे लिए भी कूलर है)। एक उपसर्ग जोड़ने के लिए, हमें सीधे कक्षा के नाम का उपयोग करना बंद करना होगा, और दूसरे गेटर को जोड़ना होगा। अब स्थिर।


 class CatsGetInit { get static type () { return `prefix/${this.name}` } constructor () { this.type = this.constructor.type } } const reducerCats = (state, action) => { switch (action.type) { case CatsGetInit.type: return { ...state, loading: true, } //... } } 

इस पूरी बात को थोड़ा कंघी करते हैं। कॉपी-पेस्ट को कम से कम करें और एक और शर्त जोड़ें: यदि कार्रवाई एक त्रुटि प्रस्तुत करती है, तो उसका payload टाइप Error का होना चाहिए।


 class ActionStandard { get static type () { return `prefix/${this.name}` } constructor(payload) { this.type = this.constructor.type this.payload = payload this.error = payload instanceof Error } } class CatsGetInit extends ActionStandard {} class CatsGetSuccess extends ActionStandard {} class CatsGetError extends ActionStandard {} const reducerCatsInitialState = { error: undefined, data: undefined, loading: false, } const reducerCats = (state = reducerCatsInitialState, action) => { switch (action.type) { case CatsGetInit.type: return { ...state, loading: true, } case CatsGetSuccess.type: return { error: undefined, data: action.payload, loading: false, } case CatsGetError.type: return { ...data, error: action.payload, loading: false, } default: return state } } 

इस स्तर पर, यह कोड NGRX के साथ ठीक काम करता है, लेकिन Redux इसे चबाने में सक्षम नहीं है। वह कसम खाता है कि कार्रवाई सरल वस्तुओं होनी चाहिए। सौभाग्य से, जेएस हमें डिजाइनर से लगभग कुछ भी वापस करने की अनुमति देता है, लेकिन कार्रवाई करने के बाद हमें वास्तव में प्रोटोटाइप श्रृंखला की आवश्यकता नहीं है।


 class ActionStandard { get static type () { return `prefix/${this.name}` } constructor(payload) { return { type: this.constructor.type, payload, error: payload instanceof Error } } } class CatsGetInit extends ActionStandard {} class CatsGetSuccess extends ActionStandard {} class CatsGetError extends ActionStandard {} const reducerCatsInitialState = { error: undefined, data: undefined, loading: false, } const reducerCats = (state = reducerCatsInitialState, action) => { switch (action.type) { case CatsGetInit.type: return { ...state, loading: true, } case CatsGetSuccess.type: return { error: undefined, data: action.payload, loading: false, } case CatsGetError.type: return { ...data, error: action.payload, loading: false, } default: return state } } 

उपरोक्त विचारों के आधार पर, फ्लक्स-एक्शन-क्लास माइक्रो लाइब्रेरी लिखा गया था। टेस्ट, 100% टेस्ट कवरेज और टाइपस्क्रिप्ट आवश्यकताओं के लिए जेनेरिक के साथ लगभग एक ही ActionStandard क्लास का ActionStandard । टाइपस्क्रिप्ट और जावास्क्रिप्ट दोनों के साथ काम करता है।


विधि 2: हम CombineReducers का उपयोग करने के लिए डर नहीं रहे हैं


यह विचार घृणित करने के लिए सरल है: संयोजनरेड्यूसर का उपयोग न केवल शीर्ष-स्तरीय रेड्यूसर के लिए करें, बल्कि तर्क को और अधिक तोड़ने और loading लिए एक अलग रेड्यूसर बनाने के लिए भी करें।


 const reducerLoading = (actionInit, actionSuccess, actionError) => ( state = false, action, ) => { switch (action.type) { case actionInit.type: return true case actionSuccess.type: return false case actionError.type: return false } } class CatsGetInit extends ActionStandard {} class CatsGetSuccess extends ActionStandard {} class CatsGetError extends ActionStandard {} const reducerCatsData = (state = undefined, action) => { switch (action.type) { case CatsGetSuccess.type: return action.payload default: return state } } const reducerCatsError = (state = undefined, action) => { switch (action.type) { case CatsGetError.type: return action.payload default: return state } } const reducerCats = combineReducers({ data: reducerCatsData, loading: reducerLoading(CatsGetInit, CatsGetSuccess, CatsGetError), error: reducerCatsError, }) 

विधि 3: स्विच से छुटकारा पाएं


और फिर से एक बहुत ही सरल विचार: switch-case बजाय switch-case एक ऑब्जेक्ट का उपयोग करें जिसमें से कुंजी द्वारा वांछित फ़ील्ड का चयन करें। कुंजी द्वारा ऑब्जेक्ट के क्षेत्र तक पहुंच O (1) है, और यह मेरी विनम्र राय में थोड़ा साफ दिखता है।


 const createReducer = (initialState, reducerMap) => ( state = initialState, action, ) => { //       const reducer = state[action.type] if (!reducer) { return state } //  ,    return reducer(state, action) } const reducerLoading = (actionInit, actionSuccess, actionError) => createReducer(false, { [actionInit.type]: () => true, [actionSuccess.type]: () => false, [actionError.type]: () => false, }) class CatsGetInit extends ActionStandard {} class CatsGetSuccess extends ActionStandard {} class CatsGetError extends ActionStandard {} const reducerCatsData = createReducer(undefined, { [CatsGetSuccess.type]: () => action.payload, }) const reducerCatsError = createReducer(undefined, { [CatsGetError.type]: () => action.payload, }) const reducerCats = combineReducers({ data: reducerCatsData, loading: reducerLoading(CatsGetInit, CatsGetSuccess, CatsGetError), error: reducerCatsError, }) 

आइए reducerLoading । अब, reducerLoading लिए मैप्स (ऑब्जेक्ट्स) के बारे में जानते हुए, हम इस reducerLoading से इस नक्शे को वापस कर सकते हैं, बजाय एक पूरे reducerLoading । संभावित रूप से, यह कार्यक्षमता के विस्तार के लिए असीमित गुंजाइश खोलता है।


 const createReducer = (initialState, reducerMap) => ( state = initialState, action, ) => { //       const reducer = state[action.type] if (!reducer) { return state } //  ,    return reducer(state, action) } const reducerLoadingMap = (actionInit, actionSuccess, actionError) => ({ [actionInit.type]: () => true, [actionSuccess.type]: () => false, [actionError.type]: () => false, }) class CatsGetInit extends ActionStandard {} class CatsGetSuccess extends ActionStandard {} class CatsGetError extends ActionStandard {} const reducerCatsLoading = createReducer( false, reducerLoadingMap(CatsGetInit, CatsGetSuccess, CatsGetError), ) /*       reducerCatsLoading: const reducerCatsLoading = createReducer( false, { ...reducerLoadingMap(CatsGetInit, CatsGetSuccess, CatsGetError), ... some custom stuff } ) */ const reducerCatsData = createReducer(undefined, { [CatsGetSuccess.type]: () => action.payload, }) const reducerCatsError = createReducer(undefined, { [CatsGetError.type]: () => action.payload, }) const reducerCats = combineReducers({ data: reducerCatsData, loading: reducerCatsLoading), error: reducerCatsError, }) 

Redux पर आधिकारिक प्रलेखन भी इस दृष्टिकोण के बारे में बात करता है , हालांकि, किसी अज्ञात कारण से, मुझे switch-case का उपयोग करके बहुत सारी परियोजनाएं दिखाई देती हैं। आधिकारिक दस्तावेज से कोड के आधार पर, श्री मोशे ने createReducer लिए हमारे लिए एक पुस्तकालय तैयार किया है।


विधि 4: वैश्विक त्रुटि हैंडलर का उपयोग करें


हमें पूरी तरह से प्रत्येक इकाई के लिए त्रुटि को अलग से रखने की आवश्यकता नहीं है। ज्यादातर मामलों में, हम सिर्फ संवाद दिखाना चाहते हैं। सभी संस्थाओं के लिए गतिशील पाठ के साथ एक ही संवाद।


एक वैश्विक त्रुटि हैंडलर बनाएँ। सबसे सरल मामले में, यह इस तरह दिख सकता है:


 class GlobalErrorInit extends ActionStandard {} class GlobalErrorClear extends ActionStandard {} const reducerError = createReducer(undefined, { [GlobalErrorInit.type]: (state, action) => action.payload, [GlobalErrorClear.type]: (state, action) => undefined, }) 

तब हमारे साइड इफेक्ट में हम ErrorInit एक्शन को catch में भेजेंगे। Redux-thunk का उपयोग करते समय यह कुछ इस तरह दिख सकता है:


 const catsGetAsync = async (dispatch) => { dispatch(new CatsGetInit()) try { const res = await fetch('https://cats.com/api/v1/cats') const body = await res.json() dispatch(new CatsGetSuccess(body)) } catch (error) { dispatch(new CatsGetError(error)) dispatch(new GlobalErrorInit(error)) } } 

अब हम अपने कैट स्टोर में error क्षेत्र से छुटकारा पा सकते हैं और loading ध्वज को स्विच करने के लिए केवल CatsGetError उपयोग कर सकते हैं।


 class CatsGetInit extends ActionStandard {} class CatsGetSuccess extends ActionStandard {} class CatsGetError extends ActionStandard {} const reducerCatsLoading = createReducer( false, reducerLoadingMap(CatsGetInit, CatsGetSuccess, CatsGetError), ) const reducerCatsData = createReducer(undefined, { [CatsGetSuccess.type]: () => action.payload, }) const reducerCats = combineReducers({ data: reducerCatsData, loading: reducerCatsLoading) }) 

विधि 5: याद करने से पहले सोचें


चलो चयनकर्ताओं के लिए कारखानों के ढेर को फिर से देखें।


मैंने makeSelectorCatsError फेंक दिया क्योंकि इसकी अब आवश्यकता नहीं है, जैसा कि हमने पिछले अध्याय में पाया था।


 const makeSelectorCatsData = () => createSelector( (state) => state.cats.data, (cats) => cats, ) const makeSelectorCatsLoading = () => createSelector( (state) => state.cats.loading, (loading) => loading, ) 

हमें यहां संस्मरणकर्ताओं की आवश्यकता क्यों है? वास्तव में हम क्या याद करने की कोशिश कर रहे हैं? कुंजी द्वारा ऑब्जेक्ट फ़ील्ड तक पहुंच, जो यहां होता है, वह ओ (1) है। हम सामान्य गैर-ज्ञापन कार्यों का उपयोग कर सकते हैं। संस्मरण का उपयोग केवल तब करें जब आप घटक को देने से पहले स्टोर से डेटा बदलना चाहते हैं।


 const selectorCatsData = (state) => state.cats.data const selectorCatsLoading = (state) => state.cats.loading 

मक्खी पर परिणाम की गणना के मामले में संस्मरण समझ में आता है। नीचे दिए गए उदाहरण के लिए, आइए कल्पना करें कि प्रत्येक बिल्ली name फ़ील्ड के साथ एक ऑब्जेक्ट है, और हम सभी बिल्लियों के नामों से युक्त एक स्ट्रिंग प्राप्त करना चाहते हैं।


 const makeSelectorCatNames = () => createSelector( (state) => state.cats.data, (cats) => cats.data.reduce((accum, { name }) => `${accum} ${name}`, ''), ) 

निष्कर्ष


आइए फिर देखें कि हमने कहाँ शुरू किया:


 import { createSelector } from 'reselect' const actionTypeCatsGetInit = 'CATS_GET_INIT' const actionTypeCatsGetSuccess = 'CATS_GET_SUCCESS' const actionTypeCatsGetError = 'CATS_GET_ERROR' const actionCatsGetInit = () => ({ type: actionTypeCatsGetInit }) const actionCatsGetSuccess = () => ({ type: actionTypeCatsGetSuccess }) const actionCatsGetError = () => ({ type: actionTypeCatsGetError }) const reducerCatsInitialState = { error: undefined, data: undefined, loading: false, } const reducerCats = (state = reducerCatsInitialState, action) => { switch (action.type) { case actionTypeCatsGetInit: return { ...state, loading: true, } case actionCatsGetSuccess: return { error: undefined, data: action.payload, loading: false, } case actionCatsGetError: return { ...data, error: action.payload, loading: false, } default: return state } } const makeSelectorCatsData = () => createSelector( (state) => state.cats.data, (cats) => cats, ) const makeSelectorCatsLoading = () => createSelector( (state) => state.cats.loading, (loading) => loading, ) const makeSelectorCatsError = () => createSelector( (state) => state.cats.error, (error) => error, ) 

और क्या आया:


 class CatsGetInit extends ActionStandard {} class CatsGetSuccess extends ActionStandard {} class CatsGetError extends ActionStandard {} const reducerCatsLoading = createReducer( false, reducerLoadingMap(CatsGetInit, CatsGetSuccess, CatsGetError), ) const reducerCatsData = createReducer(undefined, { [CatsGetSuccess.type]: () => action.payload, }) const reducerCats = combineReducers({ data: reducerCatsData, loading: reducerCatsLoading) }) const selectorCatsData = (state) => state.cats.data const selectorCatsLoading = (state) => state.cats.loading 

मुझे आशा है कि आपने समय बर्बाद नहीं किया, और लेख आपके लिए कम से कम उपयोगी था। जैसा कि मैंने शुरुआत में ही कहा था, कृपया किक करें और कड़ी आलोचना करें। हम एक साथ बेहतर कोड करना सीखेंगे।

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


All Articles