Saudações, hoje eu vou falar com você sobre como o Redutor está organizado. E para dizer por onde comecei e para onde vim.
Portanto, existe um certo padrão para organizar o Reducer e fica assim:
export default function someReducer(state = initialState, action) { switch (action.type) { case 'SOME_REDUCER_LABEL': return action.data || {}; default: return state; } }
Tudo é simples e claro aqui, mas, tendo trabalhado um pouco com essas construções, percebi que esse método tem várias dificuldades.
- As tags precisam ser armazenadas de alguma forma, porque começaram a se arrastar pelo projeto e a rastrear muito além dos controladores.
- As etiquetas precisavam ser únicas, porque, caso contrário, poderia haver uma interseção com outros redutores
- Na maioria das vezes, ao trabalhar com essa estrutura, passava-se a organizar o código, em vez de processar os dados recebidos.
- E quando há muitos rótulos no redutor, o código se torna desleixado e difícil de ler, e o espaço para nome geral não me agradou abertamente.
Na mesma época, começamos a usar sagas para processar efeitos colaterais. Isso nos permitiu facilitar bastante a comunicação com o lado do servidor sem o uso de retornos de chamada.
Agora tínhamos que deixar a saga saber qual redutor precisava ser chamado depois que o efeito colateral funcionasse.
A opção mais sensata que encontrei é criar um criador de ações.
E nosso código anterior começou a ficar assim:
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 é uma função geradora de ação (doravante denominada criador de ação) para uma saga que solicita dados de um servidor e os envia para um redutor, cujo rótulo foi passado para a função no estágio de inicialização (SOME_REDUCER_LABEL).
Agora, os rótulos do redutor foram exportados do redutor ou o criador da ação foi exportado do redutor para a saga e a padrão. Além disso, esse manipulador foi criado para cada rótulo. Isso só causou dor de cabeça, porque, depois que abri o redutor, contei 10 constantes dos rótulos definidores, algumas chamadas para vários criadores de ações para sagas e, em seguida, a função de processamento de estado do redutor, parecia algo assim
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; } }
Ao importar todas essas ações para o controlador, essa também foi inflada demais. E ficou no caminho.
Tendo examinado tantos redutores, imaginei que estamos escrevendo muito código de utilidade que nunca muda. Além disso, devemos garantir que enviamos o estado clonado ao componente.
Então tive a ideia de padronizar redutor. Tarefas diante dele não foram difíceis.
- Verifique a ação recebida e retorne o estado antigo se a ação não for para o redutor atual ou clone automaticamente o estado e passe-o para o método manipulador, que alterará o estado e o retornará ao componente.
- Você deve parar de usar rótulos. Em vez disso, o controlador deve receber um objeto contendo todos os criadores de ações para o redutor em que estamos interessados.
Assim, depois de ter importado esse conjunto uma vez, posso executar vários criadores de ações para despachar funções do redutor para o controlador sem a necessidade de reimportar - em vez de usar uma caixa de interrupção desajeitada com um espaço de nomes comum que o linter usa, quero ter um método separado para cada ação, no qual o estado clonado do redutor e a própria ação serão transferidos
- seria bom poder herdar um novo redutor do redutor. Caso a lógica se repita, mas, por exemplo, para um conjunto diferente de rótulos.
A ideia me pareceu viável e decidi tentar implementá-la.
Aqui está como o redutor médio começou a se parecer agora
// , 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 de dentro é o seguinte
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; }
Como fica no controlador? E assim
import { someReducerInstActions } from '../../../SomeReducer.js' const mapDispatchToProps = dispatch => ({ doSoAction: (params) => dispatch(someReducerInstActions.fetch(url, params)), doSoAction1: (value, block) => dispatch(someReducerInstActions.update({value, block})), });
Então, o que temos como resultado:
- se livrou de acumular tags
- se livrou de um monte de importações no controlador
- caixa de interruptores removida
- eles venceram as sagas uma vez e agora podemos expandir seu set em um só lugar, garantindo que todos os herdeiros recebam automaticamente manipuladores adicionais de efeitos colaterais
- Temos a oportunidade de herdar dos redutores, caso haja lógica relacionada (no momento isso nunca foi útil para mim =))
- Eles mudaram a responsabilidade de clonar do desenvolvedor para uma classe que definitivamente se lembraria de fazê-lo.
- menos rotina ao criar redutor
- Cada método possui um espaço para nome isolado
Tentei descrever tudo com o máximo de detalhes possível =) Desculpe, se confuso, o Chukchi não é escritor. Espero que minha experiência seja útil para alguém.
→ O exemplo atual pode ser visto aqui
Obrigado pela leitura!
UPD: erros corrigidos. Ele escreveu à noite, mal leu. Obrigado por apontá-los tão delicadamente =)