Gerenciamento de Estado (Redux & Saga)
Em um ecossistema de saúde com múltiplos módulos, um estado global monolítico torna-se um gargalo de performance e manutenção. Nossa arquitetura utiliza Redux (Toolkit) e Redux-Saga com uma estratégia de Injeção Dinâmica, garantindo que a memória e o processamento sejam utilizados de forma inteligente.
Injeção Dinâmica (Dynamic Injection)
Diferente de aplicações tradicionais onde todos os Reducers e Sagas são combinados no momento da criação da Store, nós utilizamos um padrão de "Plug-and-Play". Cada módulo é responsável por injetar sua própria lógica de estado apenas quando o usuário entra em seu contexto.
Por que usar injeção dinâmica em sistemas médicos?
- Performance em Dispositivos Móveis: Médicos e pacientes muitas vezes acessam o sistema via conexões instáveis ou dispositivos com memória limitada. Carregar apenas o necessário reduz drasticamente o tempo de interatividade (TTI).
- Modularidade Extrema: Um desenvolvedor trabalhando no módulo de
Agendamentonão precisa se preocupar se o estado doProntuárioestá crescendo ou causando conflitos de nomes. - Isolamento de Erros: Um erro catastrófico em um Reducer injetado dinamicamente pode, em muitos casos, ser isolado sem derrubar a store global de outros módulos ativos.
Fluxo de Injeção no Ciclo de Vida do Componente
Utilizamos hooks de infraestrutura (packages/core/store-injector) para gerenciar esse ciclo. Quando o módulo de "Prescrição Médica" é montado, ele registra seus Reducers e Sagas. Quando desmontado, ele pode (opcionalmente) limpar seu estado para liberar memória.
import { useInjectReducer, useInjectSaga } from 'core/store-injector';
import { prescriptionReducer } from './store/slice';
import { prescriptionSaga } from './store/sagas';
/**
* Módulo de Prescrição Médica
* Este componente orquestra a injeção de sua própria lógica de estado.
*/
const PrescriptionModule = () => {
// Injeta o Reducer na chave 'prescription' da Store Global
useInjectReducer({ key: 'prescription', reducer: prescriptionReducer });
// Injeta o Saga para gerenciar efeitos colaterais (chamadas de API, etc)
useInjectSaga({ key: 'prescription', saga: prescriptionSaga });
return <PrescriptionView />;
};Exemplo Detalhado de Store de Domínio
Para garantir a consistência, cada módulo implementa seu estado seguindo o padrão abaixo, utilizando Redux Toolkit.
1. Slice (Estado e Reducers)
O Slice define o "como" os dados são armazenados. No contexto médico, priorizamos estados carregando indicadores de loading e error por entidade.
// packages/modules/consultation/store/slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Consultation } from 'contracts/consultation';
interface ConsultationState {
activeConsultation: Consultation | null;
loading: boolean;
error: string | null;
}
const initialState: ConsultationState = {
activeConsultation: null,
loading: false,
error: null,
};
export const consultationSlice = createSlice({
name: 'consultation',
initialState,
reducers: {
fetchConsultationRequest: (state, _action: PayloadAction<string>) => {
state.loading = true;
state.error = null;
},
fetchConsultationSuccess: (state, action: PayloadAction<Consultation>) => {
state.activeConsultation = action.payload;
state.loading = false;
},
fetchConsultationFailure: (state, action: PayloadAction<string>) => {
state.loading = false;
state.error = action.payload;
},
},
});
export const { actions: consultationActions, reducer: consultationReducer } = consultationSlice;2. Selectors (Acesso aos Dados)
Os seletores encapsulam a estrutura da store. Se a chave da store mudar de activeConsultation para currentSession, apenas o seletor precisa ser atualizado, mantendo a UI intacta.
// packages/modules/consultation/store/selectors.ts
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'core/store/types';
const selectConsultationDomain = (state: RootState) => state.consultation;
export const selectActiveConsultation = createSelector(
selectConsultationDomain,
(consultation) => consultation?.activeConsultation
);
export const selectIsConsultationLoading = createSelector(
selectConsultationDomain,
(consultation) => consultation?.loading || false
);3. Sagas (Efeitos Colaterais)
Os Sagas gerenciam a complexidade assíncrona. Eles são ideais para fluxos que exigem múltiplas etapas, como validar uma prescrição antes de enviá-la para a API.
// packages/modules/consultation/store/sagas.ts
import { call, put, takeLatest } from 'redux-saga/effects';
import { consultationActions } from './slice';
import { consultationApi } from '../api';
function* handleFetchConsultation(action: ReturnType<typeof consultationActions.fetchConsultationRequest>) {
try {
const data = yield call(consultationApi.getById, action.payload);
yield put(consultationActions.fetchConsultationSuccess(data));
} catch (error: any) {
yield put(consultationActions.fetchConsultationFailure(error.message));
}
}
export function* consultationSaga() {
yield takeLatest(consultationActions.fetchConsultationRequest.type, handleFetchConsultation);
}Organização Interna do Módulo
Para manter a consistência entre todos os módulos (Consultas, Chat, Exames), seguimos uma estrutura interna padronizada para o estado:
slice.ts: Utiliza o Redux Toolkit para definir o estado inicial e as ações. Focamos em estados normalizados para facilitar a atualização de registros médicos.selectors.ts: Encapsula a estrutura do estado. Os componentes nunca acessamstate.prescription.datadiretamente; eles usamselectPrescriptionData(state). Isso permite mudar a estrutura da store sem quebrar a UI.sagas.ts: Gerencia o fluxo assíncrono. No contexto médico, sagas são vitais para garantir a ordem de operações críticas, como salvar um prontuário antes de gerar o PDF da receita.types.ts: Define as interfaces TypeScript para o estado, garantindo que não existam propriedades nulas inesperadas durante o atendimento.