El flujo de datos unidireccional es un patrón en el que el estado cambia mediante acciones controladas y después vuelve a proyectarse sobre la vista.
Nuestro State Container sencillo ya encapsulaba un contador, pero un estado con colecciones y muchas operaciones puede terminar exponiendo mutaciones como estas:
// El problema de la escalabilidad
State.Carrito.Add(producto); // Componente A añade
State.Carrito.Clear(); // Componente B borra sin avisar
State.Carrito = null; // Componente C rompe la appEn una aplicación pequeña, esto es aceptable. Pero en una aplicación empresarial, esto se convierte en una pesadilla de depuración. Si el estado cambia de forma impredecible, los bugs son imposibles de rastrear.
Flux y Redux popularizaron una forma más estricta de organizar estos cambios mediante un único flujo predecible.
El principio Flux: un solo camino
La idea es simple: Los componentes NUNCA deben modificar el estado directamente.
En su lugar, los componentes solo pueden “pedir” que se haga algo (una Acción). Esa acción es procesada por un gestor central (Store), que decide cómo actualizar el estado. Finalmente, el nuevo estado fluye hacia los componentes.
- View (Vista): El usuario hace clic en “Comprar”.
- Action (Acción): Se lanza un mensaje:
ActionComprarProducto(id: 5). - Store (Almacén): Recibe el mensaje, verifica stock, añade a la lista y crea un nuevo estado.
- View: Se actualiza al recibir el nuevo estado inmutable.
Servicios reactivos en Blazor
No necesitamos instalar librerías pesadas de JavaScript para esto. En C#, podemos implementar una versión limpia de este patrón usando Servicios Reactivos.
La clave es encapsular las mutaciones.
Estado de solo lectura
El estado no debe ser modificable desde fuera. Usamos propiedades de solo lectura (get) o listas inmutables (IReadOnlyList).
public class CarritoStore
{
// Estado interno privado
private List<Producto> _items = new();
// Estado público de SOLO LECTURA
// Nadie puede hacer State.Items.Add(...) desde fuera
public IReadOnlyList<Producto> Items => _items.AsReadOnly();
public decimal Total => _items.Sum(x => x.Precio);
// Evento de notificación
public event Action? OnChange;
}Acciones expresadas como métodos
En lugar de dejar que el componente toque la lista, exponemos métodos que representan intenciones del usuario, no operaciones de datos crudas.
// Método que actúa como "Dispatcher" / "Reducer"
public void AgregarProducto(Producto producto)
{
if (producto == null) return;
// Aquí va la lógica de negocio (validaciones, límites, etc.)
if (_items.Count >= 10)
{
Console.WriteLine("Carrito lleno");
return;
}
_items.Add(producto);
// Notificamos el cambio
NotifyStateChanged();
}
public void VaciarCarrito()
{
_items.Clear();
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();Con este pequeño cambio, hemos ganado mucho:
- Control: Solo la clase
Storesabe cómo modificar los datos. - Depuración: Si pones un punto de interrupción en
AgregarProducto, sabrás exactamente cuándo y quién añade cosas. - Validación: No pueden entrar datos corruptos al estado.
Librerías: Fluxor
Si tu aplicación es realmente compleja (cientos de pantallas, flujos de trabajo complicados), escribir estos servicios a mano puede volverse tedioso.
Para esos casos, Fluxor es una opción popular y mantenida para aplicar Flux/Redux en aplicaciones .NET y Blazor.
Fluxor te obliga a separar el código en piezas muy pequeñas:
- Feature: Define el estado inicial.
- Action: Clases simples (records) que definen mensajes (
public record AddItemAction(Item item);). - Reducer: Funciones puras que toman el Estado Viejo + Acción y devuelven el Estado Nuevo.
- Effect: Lógica para efectos secundarios (llamadas API).
// Ejemplo conceptual de Fluxor
// El componente solo hace esto:
Dispatcher.Dispatch(new AddItemAction(nuevoProducto));Fluxor añade estructura y más piezas, pero puede integrarse con Redux DevTools para inspeccionar acciones y estados durante la depuración.
¿Qué patrón debo usar?
Mi recomendación práctica sería esta:
- State Container Simple: Para apps pequeñas, demos o datos que no son críticos (ej: ¿el menú lateral está abierto o cerrado?).
- Servicios reactivos: Cuando quieres encapsular reglas y notificaciones sin adoptar un framework completo.
- Fluxor: Cuando el historial de acciones, los reductores, los efectos y unas convenciones rígidas compensan el código adicional.