Organisation des Reduzierers durch eine Standardklasse

Grüße, heute werde ich mit Ihnen darüber sprechen, wie Reducer organisiert ist. Und um zu sagen, wo ich angefangen habe und wo ich hingekommen bin.


Es gibt also einen bestimmten Standard für die Organisation von Reducer und dieser sieht folgendermaßen aus:


export default function someReducer(state = initialState, action) { switch (action.type) { case 'SOME_REDUCER_LABEL': return action.data || {}; default: return state; } } 

Hier ist alles einfach und klar, aber nachdem ich ein wenig mit solchen Konstruktionen gearbeitet hatte, stellte ich fest, dass diese Methode eine Reihe von Schwierigkeiten hat.


  • Tags müssen irgendwie gespeichert werden, da sie sich entlang des Projekts schleichen und weit über die Controller hinauskriechen.
  • Etiketten mussten eindeutig gemacht werden, da es sonst zu einem Schnittpunkt mit anderen Reduzierstücken kommen könnte
  • Die meiste Zeit bei der Arbeit mit einer solchen Struktur wurde für die Organisation des Codes aufgewendet, anstatt für die Verarbeitung eingehender Daten
  • Und wenn der Reduzierer viele Beschriftungen enthält, wird der Code schlampig und schwer zu lesen, und der allgemeine Namespace hat mir nicht offen gefallen.
    Etwa zur gleichen Zeit begannen wir, Sagen zu verwenden, um Nebenwirkungen zu verarbeiten. Dies ermöglichte es uns, die Kommunikation mit der Serverseite ohne Rückrufe erheblich zu vereinfachen.

Jetzt mussten wir der Saga mitteilen, welcher Reduzierer aufgerufen werden musste, nachdem die Nebenwirkung behoben war.


Die sinnvollste Option, die ich gefunden habe, ist, einen Aktionsersteller zu erstellen.


Und unser vorheriger Code sah folgendermaßen aus:


  import { FetchSaga } from '../../helpers/sagasHelpers'; const SOME_REDUCER_LABEL = 'SOME_REDUCER_LABEL'; export const someReducerLabelActionCreator = FetchSaga.bind(this, SOME_REDUCER_LABEL); export default function someReducer(state = initialState, action) { switch (action.type) { case SOME_REDUCER_LABEL: return action.data || {}; default: return state; } } 

FetchSaga ist eine Aktionsgeneratorfunktion (im Folgenden als Aktionsersteller bezeichnet) für eine Saga, die Daten von einem Server anfordert und an einen Reduzierer sendet, dessen Bezeichnung in der Initialisierungsphase an die Funktion übergeben wurde (SOME_REDUCER_LABEL).


Jetzt wurden die Etiketten des Reduzierers entweder aus dem Reduzierer exportiert oder der Aktionsersteller wurde aus dem Reduzierer sowohl für die Saga als auch für die Standard-Saga exportiert. Darüber hinaus wurde für jedes Etikett ein solcher Handler erstellt. Dies verursachte nur Kopfschmerzen, da ich nach dem Öffnen des Reduzierers 10 Konstanten der definierenden Beschriftungen gezählt habe, dann ein paar Aufrufe für verschiedene Aktionsersteller für Sagen und dann die Statusverarbeitungsfunktion des Reduzierers. Es sah ungefähr so ​​aus


 import { FetchSaga } from '../../helpers/sagasHelpers'; const SOME_REDUCER_LABEL1 = 'SOME_REDUCER_LABEL1';  .... const SOME_REDUCER_LABEL10 = 'SOME_REDUCER_LABEL10'; export const someReducerLabelActionCreator1 = FetchSaga.bind(this, SOME_REDUCER_LABEL1);   ..... export const someReducerLabelActionCreator10 = FetchSaga.bind(this, SOME_REDUCER_LABEL10); export default function someReducer(state = initialState, action) { switch (action.type) { case SOME_REDUCER_LABEL: return action.data || {}; case SOME_REDUCER_LABEL1: return action.data || {}; case SOME_REDUCER_LABEL2: return action.data || {}; case SOME_REDUCER_LABEL3: return action.data || {}; .... default: return state; } } 

Beim Importieren all dieser Aktionen in den Controller wurde diese ebenfalls so stark aufgeblasen. Und es störte.


Nachdem ich mir so viele Reduzierungen angesehen hatte, stellte ich fest, dass wir eine Menge Utility-Code schreiben, der sich nie ändert. Außerdem müssen wir sicherstellen, dass wir den geklonten Status an die Komponente senden.


Dann kam mir die Idee, das Reduzierstück zu standardisieren. Aufgaben vor ihm waren nicht schwierig.


  1. Überprüfen Sie die eingehende Aktion und geben Sie den alten Status zurück, wenn die Aktion nicht für den aktuellen Reduzierer oder den automatisch geklonten Status gilt, und übergeben Sie sie an die Handler-Methode, die den Status ändert und an die Komponente zurückgibt.
  2. Sie sollten die Verwendung von Beschriftungen einstellen. Stattdessen sollte der Controller ein Objekt erhalten, das alle Aktionsersteller für den Reduzierer enthält, an dem wir interessiert sind.
    Nachdem ich einen solchen Satz einmal importiert habe, kann ich eine beliebige Anzahl von Aktionserstellern durchlaufen, um Funktionen vom Reduzierer an die Steuerung zu senden, ohne dass ein erneuter Import erforderlich ist
  3. Anstatt einen ungeschickten Switch-Case mit einem gemeinsamen Namespace zu verwenden, den der Linter verwendet, möchte ich für jede Aktion eine separate Methode haben, in die der geklonte Zustand des Reduzierers und die Aktion selbst übertragen werden
  4. Es wäre schön, einen neuen Reduzierer vom Reduzierer erben zu können. Falls sich die Logik wiederholt, aber zum Beispiel für einen anderen Satz von Beschriftungen.

Die Idee schien mir realisierbar und ich beschloss, sie umzusetzen.


So sah der durchschnittliche Reduzierer jetzt aus


  //    ,        reducer' import stdReducerClass from '../../../helpers/reducer_helpers/stdReducer'; class SomeReducer extends stdReducerClass { constructor() { super(); /**   reducer'.   reducer    action,     */ this.prefix = 'SOME_REDUCER__'; } /**   ,     reducer - type - ,    . -        ,     action creator,  SOME_REDUCE__FETCH.   type        action creator  someReduceInstActions - method - ,      action,            - sagas -    ,   ,       .    ,   action creator  ,      SOME_REDUCE__FETCH,  ,   ,      reducer    . */ config = () => [ { type: 'fetch', method: this.fetch, saga: 'fetch' }, { type: 'update', method: this.update }, ]; //           action creators init = () => this.subscribeReduceOnActions(this.config()); //  ,      fetch = (clone, action) => { //       return clone; }; //  ,         update = (clone, action) => { //       return clone; }; } const someReducerInst = new SomeReducer(); someReducerInst.init(); //   action creators   config //    action creator      export const someReducerInstActions = someReducerInst.getActionCreators(); //    .   checkActionForState    Action  ,      reducer'   export default someReducerInst.checkActionForState; 

stdReducerClass von innen ist wie folgt


 import { cloneDeep } from 'lodash'; //    lodash //        ,            import { FetchSaga } from '../helpers/sagasHelpers/actions'; export default class StdReducer { _actions = {}; actionCreators = {}; /** UNIQUE PREFIX BLOCK START */ /**      ,  .   ,   ,    ,  reducer    action       */ uniquePrefix = ''; set prefix(value) { const lowedValue = value ? value.toLowerCase() : ''; this.uniquePrefix = lowedValue; } get prefix() { return this.uniquePrefix; } /** INITIAL STATE BLOCK START */ /**   initialState      reducer'. */ initialStateValues = {}; set initialState(value) { this.initialStateValues = value; } get initialState() { return this.initialStateValues; } /** PUBLIC BLOCK START */ /** *        init() .   ,      Config, action creator   _subscribeAction * actionsConfig -     ,     {type, method, saga?}     ,     action creator          */ subscribeReducerOnActions = actionsConfig => actionsConfig.forEach(this._subscribeAction); /**      _subscribeAction,    ,        type.  , reducer  ,       action. */ _subscribeAction = (action) => { const type = action.type.toLowerCase(); this._actions[type] = action.method; //       this.actionCreators[type] = this._subscribeActionCreator(type, action.saga); //   action creator   } /** _subscribeActionCreator -   , action creator          -   saga    ,      -   fetch           ,             type    ,   ,    action creator,  ,     SOME_Reducer__,      FETCH,      SOME_Reducer__FETCH,     action creator */ _subscribeActionCreator = (type, creatorType) => { const label = (this.prefix + type).toUpperCase(); switch (creatorType) { case 'fetch': return this._getFetchSaga(label); default: return this._getActionCreator(label); } } /** _getFetchSaga -     ,          */ _getFetchSaga = label => FetchSaga.bind(this, label); /** _getActionCreator -  action creator,      ,   ,    . */ _getActionCreator = label => (params = {}) => ({ type: label, ...params }); /**    ,      playload.     action   ,    */ checkActionForState = (state = this.initialState || {}, action) => { if (!action.type) return state; const type = action.type.toLowerCase(); const prefix = this.prefix;       ,    ,   . const internalType = type.replace(prefix, ''); //        if (this._actions[internalType]) { //     -    const clone = cloneDeep(state); //  ,    ,  action  ,     //        return this._actions[internalType](clone, action); } //   ,   action   .     return state; } /**       action creator,    reducer */ getActionCreators = () => this.actionCreators; } 

Wie sieht es im Controller aus? Und so


 import { someReducerInstActions } from '../../../SomeReducer.js' const mapDispatchToProps = dispatch => ({ doSoAction: (params) => dispatch(someReducerInstActions.fetch(url, params)), doSoAction1: (value, block) => dispatch(someReducerInstActions.update({value, block})), }); 

Also, was haben wir als Ergebnis:


  1. Tags loswerden
  2. habe eine Reihe von Importen in der Steuerung beseitigt
  3. Schaltkasten entfernt
  4. Sie haben die Sagen einmal geschlagen und jetzt können wir ihr Set an einem Ort erweitern, wobei sichergestellt ist, dass alle Erben automatisch zusätzliche Handler für Nebenwirkungen erhalten
  5. Wir hatten die Möglichkeit, von Reduzierern zu erben, falls es eine verwandte Logik gibt (im Moment war dies für mich nie nützlich =))
  6. Sie haben die Verantwortung für das Klonen vom Entwickler auf eine Klasse verlagert, die sich definitiv daran erinnern würde, dies zu tun.
  7. weniger Routine beim Erstellen des Reduzierers
  8. Jede Methode hat einen isolierten Namespace

Ich habe versucht, alles so detailliert wie möglich zu beschreiben =) Entschuldigung, wenn verwirrt, ist der Chukchi kein Schriftsteller. Ich hoffe, dass meine Erfahrung jemandem nützlich sein wird.


Das aktuelle Beispiel finden Sie hier


Danke fürs Lesen!


UPD: Fehler behoben. Er schrieb nachts, las es schlecht. Danke, dass du sie so fein gezeigt hast =)

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


All Articles