wandres.dev
LA FAMILIA UNIDIRECCIONAL · Redux, Elm, TCA, MVI

Qué aprender de cada uno

No vas a enviar a producción las cuatro arquitecturas, pero cada una perfeccionó una virtud que el ingeniero maduro roba con independencia de la herramienta que tenga en la mano. Redux enseña la disciplina: la ceremonia como forma de interiorizar la restricción. Elm enseña la pureza: efectos gestionados y funciones totales sin escotillas de escape. TCA enseña el testing: el TestStore exhaustivo como el estándar de oro de la familia. MVI enseña a separar los eventos del estado: lo continuo por un flujo, lo de una sola vez por otro. Cuatro lecciones portátiles que sobreviven a la muerte de cualquier librería.

⏱ 17 min

Nadie construye una carrera enviando a producción las cuatro arquitecturas de esta familia: eliges una por plataforma y sigues adelante. Pero sería un desperdicio estudiarlas como quien compara productos en una estantería, porque cada una perfeccionó una virtud distinta hasta un grado que las demás solo aproximan, y esas virtudes son portátiles. El ingeniero maduro no colecciona frameworks: destila de cada uno la lección que lo hizo grande y se la lleva puesta, use luego Zustand, SwiftUI o Jetpack Compose. Redux perfeccionó la disciplina, Elm la pureza, TCA el testing y MVI la separación entre lo que es estado y lo que es un evento. Cuatro lecciones, una por familia, que sobreviven a la salud de cualquier librería concreta.

🎯 Al terminar esta lección sabrás
  • Extraer de Redux la disciplina: la ceremonia como forma de interiorizar la restricción.
  • Extraer de Elm la pureza: efectos gestionados y funciones totales, sin escotillas de escape.
  • Extraer de TCA el testing: el TestStore exhaustivo como estándar de oro de la familia.
  • Extraer de MVI la separación de eventos: distinguir el estado continuo del evento de una sola vez.

Redux enseña disciplina

La virtud de Redux es una que muchos confunden con su defecto: la ceremonia.

Nombrar cada acción, escribir un reducer, despachar en lugar de asignar —todo eso que los detractores llaman boilerplate— tiene un valor pedagógico que trasciende a la librería. Te enseña, a fuerza de repetirlo, que un cambio de estado es un acontecimiento que merece un nombre, no una asignación cualquiera.

Te obliga a que cada cambio de estado tenga un nombre y pase por una sola puerta, y esa disciplina se te queda dentro aunque mañana uses una herramienta que no la imponga.

Cuando escribes Zustand con acciones nombradas en lugar de mutar el store desde cualquier parte, estás aplicando la lección de Redux sin Redux. La herramienta cambió; el hábito de nombrar y centralizar, que era lo valioso, se quedó.

Redux Toolkit, por cierto, redujo la ceremonia sin abandonar la disciplina: menos texto, misma obligación de nombrar y centralizar. La moraleja es que la disciplina no era el boilerplate, sino la restricción que el boilerplate hacía visible, y esa restricción se puede conservar aunque se elimine el texto sobrante.

El premio de esa disciplina es tangible: como cada cambio es un hecho con nombre, obtienes gratis un registro navegable de todo lo que pasó, el viaje en el tiempo de las DevTools y la capacidad de reproducir cualquier bug reenviando la lista de acciones. La ceremonia no era gratis, pero tampoco lo era lo que compraba.

Elm enseña pureza

La virtud de Elm es más radical: no tiene escotilla de escape. En update no puedes provocar un efecto secundario ni aunque quieras, porque el lenguaje no te lo permite; el efecto tiene que salir por Cmd o no sale.

A eso se suman las funciones totales y el case exhaustivo, que erradican los fallos en tiempo de ejecución hasta el punto de que la comunidad de Elm presume, con razón, de no verlos casi nunca.

Un programa que compila en Elm no explota en producción por un valor nulo inesperado, porque el compilador no dejó ningún caso sin cubrir. Esa pureza tiene un coste real —hablar con JavaScript exige puertos y ceremonia—, pero el coste es precisamente lo que compra la garantía.

La lección es el ideal que las demás aproximan: empuja todos los efectos al borde y mantén el centro matemáticamente limpio. Aunque trabajes en un lenguaje impuro como TypeScript o Kotlin, puedes imponerte voluntariamente la disciplina de Elm —no llamar a la red desde un reducer, tratar todos los casos de una unión— y cosechar buena parte de sus garantías sin cambiar de lenguaje.

TCA enseña testing

La virtud de TCA es la que más envidia debería dar al resto de la familia: el TestStore. Un test de TCA afirma cada mutación del estado y cada efecto que se dispara; si un solo cambio de estado queda sin justificar, el test falla.

Esa exhaustividad es lo que lo distingue de un test corriente. No compruebas algunas cosas y confías en el resto: o describes el estado exacto tras cada acción, o el marco te acusa de haber dejado un cambio sin explicar. El test se convierte en una demostración, no en un muestreo.

La combinación que lo hace posible es un reductor puro, efectos como valores y dependencias inyectables con @Dependency, que sustituyen la red, el reloj y el azar por versiones deterministas.

Sin esa base, la exhaustividad sería imposible; con ella, es casi gratis. Ese es el punto que conecta esta virtud con la lección anterior: el testing de TCA no es un accesorio, es la recompensa directa de haber tratado el efecto como un valor y no como una orden.

// TCA TestStore: cada cambio de estado y cada efecto se afirman
let store = TestStore(initialState: Feature.State()) { Feature() }
await store.send(.pedir) {
  $0.cargando = true          // si el estado no queda asi, el test falla
}
await store.receive(.recibido(datos)) {
  $0.cargando = false
  $0.datos = datos            // un efecto sin afirmar tambien hace fallar el test
}

El resultado es un test que se lee como un guion de hechos y aserciones: envío esta acción, el estado debe quedar así, este efecto debe dispararse y devolver este otro hecho.

Es el listón más alto de la familia, y aunque lo hayas conocido en Swift, la aspiración es portátil: escribe tus reducers de Redux o MVI pensando en que algún día quieras un TestStore así de exigente. Diseñar para la testabilidad exhaustiva mejora el código aunque nunca llegues a escribir ese test, porque te obliga a sacar los efectos del núcleo.

MVI enseña a separar los eventos del estado

La virtud de MVI es la más sutil y la que más bugs evita en la práctica: distinguir el estado continuo del evento de una sola vez. No todo lo que ocurre en una pantalla es estado, y tratarlo como si lo fuera es una fuente inagotable de errores.

El estado —StateFlow<UiState>— siempre tiene un valor actual y se reemite cuando alguien se suscribe de nuevo, por ejemplo al rotar la pantalla. El evento —navegar a otra pantalla, mostrar un aviso, vibrar— ocurre una vez y no debe repetirse.

Meter un aviso dentro del estado produce el bug clásico de Android: la notificación reaparece al rotar el dispositivo, porque el estado se vuelve a proyectar y arrastra consigo un evento que ya se había consumido.

Es un bug que casi todo el mundo comete una vez y ninguno olvida, y su cura no es más código defensivo sino una distinción conceptual: el aviso nunca fue estado, era un evento disfrazado de estado.

// El estado continuo: siempre tiene un valor actual y se reemite
val uiState: StateFlow<UiState>
// El evento de una sola vez: se consume una vez y no se reemite
val efectos: SharedFlow<Efecto>   // navegar, mostrar aviso, vibrar
// Meter el aviso dentro de UiState es el bug clasico de Android:
// reaparece al rotar la pantalla porque el estado se re-proyecta.

La lección es portátil a cualquier plataforma: pregúntate siempre, ante cada cosa que ocurre, si es un hecho persistente que describe cómo está la pantalla o un disparo puntual que se agota al consumirse.

Lo primero es estado; lo segundo, evento; y necesitan tuberías distintas. En la web, la misma distinción reaparece cuando separas el estado de un modal —abierto o cerrado, que sí es estado— del disparo de un aviso efímero, que no lo es.

🎖️

Redux · disciplina

La ceremonia interioriza la restricción: cada cambio tiene un nombre y una sola puerta. Te la llevas puesta aunque uses otra herramienta.

💧

Elm · pureza

Sin escotilla de escape: el efecto sale por Cmd o no sale. Funciones totales y case exhaustivo, casi sin fallos en tiempo de ejecución.

🔬

TCA · testing

El TestStore afirma cada mutación y cada efecto. Un reductor puro más dependencias inyectables hacen del test una demostración exhaustiva.

✂️

MVI · separar eventos

Estado continuo por StateFlow, evento de una sola vez por SharedFlow. No todo es estado, y confundirlos crea bugs al rotar.

flowchart TD
RD[Redux aporta disciplina] --> Y[tu arquitectura]
EL[Elm aporta pureza] --> Y
TC[TCA aporta testing] --> Y
MV[MVI aporta separar eventos del estado] --> Y
style Y fill:#cba6f7,color:#11111b
style RD fill:#f38ba8,color:#11111b
style EL fill:#a6e3a1,color:#11111b
style TC fill:#89b4fa,color:#11111b
style MV fill:#f9e2af,color:#11111b
El ingeniero ecléctico se lleva las virtudes, no las librerías

La marca de la madurez en el estado es dejar de preguntar cuál de las cuatro es la mejor y empezar a llevarse la mejor parte de cada una. Las librerías son locales —viven en una plataforma, envejecen, ceden su trono—, pero las virtudes son portátiles y no caducan. Puedes escribir Zustand con la disciplina de Redux, imponerte la pureza de Elm en un TypeScript que no te obliga a nada, exigirle a tus reducers de Android el nivel de testing del TestStore de TCA, y separar en cualquier plataforma el estado continuo del evento de una sola vez como enseñó MVI. Ninguna de esas cuatro cosas depende de instalar la librería que las hizo famosas; dependen de que hayas entendido por qué esa librería las perfeccionó. Este es, en el fondo, el sentido de estudiar las cuatro juntas en lugar de aprender solo la de tu plataforma: no para poder cambiar de trabajo, sino para robar cuatro ideas maduras y fundirlas en un criterio propio que ninguna de las cuatro, por separado, te habría dado. El ecléctico que domina las cuatro virtudes escribe mejor estado con Zustand que el purista que solo conoce Zustand, precisamente porque trae en la mochila lecciones que Zustand nunca le habría enseñado. Estudia la familia entera, por tanto, no como cuatro competidoras entre las que elegir, sino como cuatro maestras de las que aprender cosas distintas.

⚔️ Roba una virtud de cada familia
  1. Reescribe un store que uses hoy aplicando la disciplina de Redux: nombra cada cambio y hazlo pasar por una sola puerta, aunque la librería no te obligue.
  2. Toma una función que provoque un efecto a escondidas e impónle la pureza de Elm: saca el efecto al borde y deja el centro limpio.
  3. Escribe un test al estilo del TestStore de TCA que afirme, paso a paso, cada mutación del estado y cada efecto disparado.
  4. Revisa tu estado en busca de un aviso o una navegación guardados como si fueran estado, y sepáralos en un canal de eventos como haría MVI.
  5. Elige la virtud que más le falta a tu proyecto actual y aplícala esta semana, midiendo qué bug deja de ser posible.
  6. Escribe en cuatro frases qué le robarías a cada arquitectura si tuvieras que diseñar la tuya desde cero.