लेवल 80 ओवरराइटिंग या रिड्यूसर: स्विच-केस से कक्षाओं तक का रास्ता

छवि


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


आइए पिछले कुछ वर्षों में मेरे Redux / NGRX अनुप्रयोगों में reducers के रूपांतरों को देखें। ओक switch-case शुरू करना, कुंजी द्वारा ऑब्जेक्ट से चयन जारी रखना और डेकोरेटर्स, लाठी और टाइपस्क्रिप्ट के साथ कक्षाओं के साथ समाप्त होना। हम न केवल इस मार्ग के इतिहास की समीक्षा करने की कोशिश करेंगे, बल्कि कुछ कारण संबंध भी पाएंगे।


यदि आप और साथ ही मैं Redux / NGRX में एक बॉयलरप्लेट के निपटान के प्रश्न पूछते हैं, तो यह लेख आपके लिए दिलचस्प हो सकता है।

यदि आप पहले से ही किसी ऑब्जेक्ट से रीड्यूसर को चुनने के लिए एप्रोच का उपयोग करते हैं और इससे तंग आ चुके हैं, तो आप तुरंत "क्लास-आधारित रीड्यूसर" पर फ्लिप कर सकते हैं।

चॉकलेट स्विच-केस


आमतौर पर switch-case वनीला है, लेकिन मुझे ऐसा लगा कि यह अन्य सभी प्रकार के switch-case गंभीरता से भेदभाव करता switch-case

तो, आइए एक इकाई की अतुल्यकालिक निर्माण की एक विशिष्ट समस्या पर एक नज़र डालें, उदाहरण के लिए, जेडी।


 const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false, //   data: [], error: undefined, } const reducerJedi = (state = reducerJediInitialState, action) => { switch (action.type) { case actionTypeJediCreateInit: return { ...state, loading: true, } case actionTypeJediCreateSuccess: return { loading: false, data: [...state.data, action.payload], error: undefined, } case actionTypeJediCreateError: return { ...state, loading: false, error: action.payload, } default: return state } } 

मैं बहुत स्पष्ट हूँ और मानता हूँ कि मैंने अपने व्यवहार में switch-case कभी उपयोग नहीं किया है। मेरा मानना ​​है कि मेरे पास इसके कारणों की एक सूची है:


  • switch-case तोड़ना बहुत आसान है: आप break सम्मिलित कर सकते हैं, आप default बारे में भूल सकते हैं।
  • switch-case भी क्रिया है।
  • switch-case लगभग O (n) है। यह अपने आप में बहुत महत्वपूर्ण नहीं है, क्योंकि Redux अपने आप में लुभावने प्रदर्शन का दावा नहीं करता है, लेकिन यह तथ्य सुंदरता के मेरे आंतरिक पारखी को प्रभावित करता है।

इस सब को कंघी करने का तार्किक तरीका आधिकारिक Redux प्रलेखन द्वारा पेश किया गया है - कुंजी द्वारा ऑब्जेक्ट से एक reducer चुनने के लिए।


कुंजी द्वारा किसी ऑब्जेक्ट से रिड्यूसर का चयन करना


विचार सरल है - प्रत्येक राज्य परिवर्तन को राज्य और कार्रवाई के एक फ़ंक्शन द्वारा वर्णित किया जा सकता है, और इस तरह के प्रत्येक फ़ंक्शन में एक निश्चित कुंजी (कार्रवाई में फ़ील्ड) होती है जो इसके अनुरूप होती है। क्योंकि type एक स्ट्रिंग है, कुछ भी हमें ऐसे सभी कार्यों के लिए एक वस्तु का पता लगाने से रोकता है, जहां कुंजी type और मूल्य एक शुद्ध राज्य रूपांतरण फ़ंक्शन (reducer) है। इस मामले में, हम कुंजी (O (1)) द्वारा आवश्यक reducer चुन सकते हैं, जब एक नया एक्शन रूट reducer पर आता है।


 const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false, data: [], error: undefined, } const reducerJediMap = { [actionTypeJediCreateInit]: (state) => ({ ...state, loading: true, }), [actionTypeJediCreateSuccess]: (state, action) => ({ loading: false, data: [...state.data, action.payload], error: undefined, }), [actionTypeJediCreateError]: (state, action) => ({ ...state, loading: false, error: action.payload, }), } const reducerJedi = (state = reducerJediInitialState, action) => { //    `type`  const reducer = reducerJediMap[action.type] if (!reducer) { //   ,        return state } //        return reducer(state, action) } 

सबसे स्वादिष्ट बात यह है कि reducerJedi अंदर तर्क किसी भी reducer के लिए समान है, और हम इसे पुन: उपयोग कर सकते हैं। यहां तक ​​कि इसके लिए एक redux-create-reducer नैनो-लाइब्रेरी भी है


 import { createReducer } from 'redux-create-reducer' const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false, data: [], error: undefined, } const reducerJedi = createReducer(reducerJediInitialState, { [actionTypeJediCreateInit]: (state) => ({ ...state, loading: true, }), [actionTypeJediCreateSuccess]: (state, action) => ({ loading: false, data: [...state.data, action.payload], error: undefined, }), [actionTypeJediCreateError]: (state, action) => ({ ...state, loading: false, error: action.payload, }), }) 

ऐसा लगता है कि कुछ भी नहीं हुआ। सच है, एक चम्मच शहद टार के एक बैरल के बिना नहीं है:


  • जटिल रिड्यूसर के लिए, हमें टिप्पणियों को छोड़ना होगा, क्योंकि यह विधि कुछ व्याख्यात्मक मेटा-जानकारी प्रदान करने के लिए बॉक्स से बाहर का रास्ता प्रदान नहीं करती है।
  • Reducers और कुंजियों के एक समूह के साथ वस्तुओं को अच्छी तरह से नहीं पढ़ा जाता है।
  • प्रत्येक reducer के पास केवल एक कुंजी है। लेकिन क्या होगा अगर आप कई एक्शन गेम्स के लिए एक ही रिड्यूसर चलाना चाहते हैं?

जब मैं क्लास-आधारित रेड्यूसर में चला गया, तो मैं लगभग खुशी के आँसू में बह गया, और नीचे मैं बताऊंगा कि क्यों।


कक्षा आधारित रेड्यूसर


रोटी:


  • क्लास के तरीके हमारे रिड्यूसर हैं, और विधियों के नाम हैं। बस बहुत ही मेटा जानकारी जो बताती है कि यह reducer क्या करता है।
  • क्लास के तरीकों को सजाया जा सकता है, जो कि रिड्यूसर्स और उनके संबंधित कार्यों (अर्थात्, एक्शन, न सिर्फ एक्शन!) को जोड़ने के लिए एक सरल घोषणात्मक तरीका है!
  • हुड के तहत, आप ओ (1) प्राप्त करने के लिए सभी समान वस्तुओं का उपयोग कर सकते हैं।

अंत में, मैं ऐसा कुछ प्राप्त करना चाहूंगा।


 const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' class ReducerJedi { //     "Class field delcaratrions",    Stage 3. // https://github.com/tc39/proposal-class-fields initialState = { loading: false, data: [], error: undefined, } @Action(actionTypeJediCreateInit) startLoading(state) { return { ...state, loading: true, } } @Action(actionTypeJediCreateSuccess) addNewJedi(state, action) { return { loading: false, data: [...state.data, action.payload], error: undefined, } } @Action(actionTypeJediCreateError) error(state, action) { return { ...state, loading: false, error: action.payload, } } } 

मैं लक्ष्य देखता हूं, मुझे कोई बाधा नहीं दिखती है।


चरण 1. डेकोरेटर @Action


हमें जरूरत है कि इस डेकोरेटर में हम कितने भी एक्शन गेम चिपका सकते हैं, और इन सेवाओं को कुछ मेटा-जानकारी के रूप में सहेजा जाता है, जिन्हें बाद में एक्सेस किया जा सकता है। ऐसा करने के लिए, हम अद्भुत पॉलीफिल रिफ्लेक्ट -मेटाडेटा का उपयोग कर सकते हैं, जो रिफ्लेक्ट को पैच करता है।


 const METADATA_KEY_ACTION = 'reducer-class-action-metadata' export const Action = (...actionTypes) => (target, propertyKey, descriptor) => { Reflect.defineMetadata(METADATA_KEY_ACTION, actionTypes, target, propertyKey) } 

चरण 2. वास्तव में, एक reducer कक्षा को चालू करें।


एक सर्कल बनाएं, एक दूसरा ड्रा करें, और अब थोड़ा जादू करें और एक उल्लू प्राप्त करें!

जैसा कि हम जानते हैं, प्रत्येक reducer एक शुद्ध कार्य है जो वर्तमान स्थिति और क्रिया को लेता है और एक नया राज्य देता है। एक वर्ग, निश्चित रूप से, एक फ़ंक्शन है, लेकिन ठीक उसी तरह से नहीं जिसकी हमें ज़रूरत है, और ES6 वर्गों को new बिना नहीं बुलाया जा सकता है। सामान्य तौर पर, हमें किसी तरह इसे बदलने की जरूरत है।


तो, हमें एक फ़ंक्शन की आवश्यकता है जो वर्तमान वर्ग को ले जाएगा, इसके प्रत्येक तरीके से गुजरें, प्रकार की क्रियाओं के साथ मेटा-जानकारी एकत्र करें, रीड्यूसर के साथ एक ऑब्जेक्ट इकट्ठा करें और इस ऑब्जेक्ट से अंतिम रेड्यूसर बनाएं।


आइए मेटा जानकारी एकत्र करके शुरू करें।


 const getReducerClassMethodsWthActionTypes = (instance) => { //       const proto = Object.getPrototypeOf(instance) const methodNames = Object.getOwnPropertyNames(proto).filter( (name) => name !== 'constructor', ) //             const res = [] methodNames.forEach((methodName) => { const actionTypes = Reflect.getMetadata( METADATA_KEY_ACTION, instance, methodName, ) //     `this`    const method = instance[methodName].bind(instance) //  ,         actionTypes.forEach((actionType) => res.push({ actionType, method, }), ) }) return res } 

अब हम परिणामी संग्रह को एक वस्तु में बदल सकते हैं


 const getReducerMap = (methodsWithActionTypes) => methodsWithActionTypes.reduce((reducerMap, { method, actionType }) => { reducerMap[actionType] = method return reducerMap }, {}) 

तो अंतिम कार्य इस तरह दिख सकता है:


 import { createReducer } from 'redux-create-reducer' const createClassReducer = (ReducerClass) => { const reducerClass = new ReducerClass() const methodsWithActionTypes = getReducerClassMethodsWthActionTypes( reducerClass, ) const reducerMap = getReducerMap(methodsWithActionTypes) const initialState = reducerClass.initialState const reducer = createReducer(initialState, reducerMap) return reducer } 

आगे हम इसे अपने ReducerJedi वर्ग पर लागू कर सकते हैं।


 const reducerJedi = createClassReducer(ReducerJedi) 

चरण 3. हम देखते हैं कि परिणामस्वरूप क्या हुआ।


 //       import { Action, createClassReducer } from 'utils/reducer-class' const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' class ReducerJedi { //     "Class field delcaratrions",    Stage 3. // https://github.com/tc39/proposal-class-fields initialState = { loading: false, data: [], error: undefined, } @Action(actionTypeJediCreateInit) startLoading(state) { return { ...state, loading: true, } } @Action(actionTypeJediCreateSuccess) addNewJedi(state, action) { return { loading: false, data: [...state.data, action.payload], error: undefined, } } @Action(actionTypeJediCreateError) error(state, action) { return { ...state, loading: false, error: action.payload, } } } export const reducerJedi = createClassReducer(ReducerJedi) 

कैसे जीना है?


कुछ हमने पर्दे के पीछे छोड़ दिया:


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

एक छोटे से reducer वर्ग पुस्तकालय में अतिरिक्त उदाहरणों के साथ यह सब कार्यक्षमता है।


यह ध्यान देने योग्य है कि रीड्यूसर के लिए कक्षाओं का उपयोग करने का विचार नया नहीं है। @amcdnl ने एक बार ngrx-actions की एक शानदार लाइब्रेरी बनाई थी, लेकिन ऐसा लगता है कि उन्होंने अब इसे बनाया है और NGXS पर स्विच कर दिया है। इसके अलावा, मैं अधिक कठोर टाइपिंग चाहता था और एंगुलर फंक्शनलिटी के लिए विशेष रूप में गिट्टी को रीसेट करता हूं। यहाँ reducer- वर्ग और ngrx- कार्यों के बीच महत्वपूर्ण अंतर की एक सूची है।


यदि आपको रिड्यूसर के लिए कक्षाओं का विचार पसंद आया है, तो आप अपने कार्यों के लिए कक्षाओं का उपयोग करना भी पसंद कर सकते हैं। फ्लक्स-एक्शन-क्लास पर एक नज़र डालें।

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

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


All Articles