Máquinas de estados: hacer imposibles los estados imposibles
Por qué los booleanos 'isLoading' generan errores. Una inmersión profunda en las máquinas de estados finitos (FSM), Statecharts y XState para una lógica de interfaz de usuario a prueba de balas.
Veamos un componente típico escrito por un desarrollador junior.
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [datos, setData] = useState(nulo);
const fetchData = async () => {
setIsLoading(verdadero);
prueba {
const res = esperar api.get();
establecerDatos(res);
} atrapar (errar) {
setIsError(verdadero);
}
setIsLoading(falso); // Error: ¿Qué pasa si ocurre un error? división lógica.
};
El error: Si se ejecuta el bloque catch, isError se vuelve verdadero. Luego se ejecuta setIsLoading(false).
Pero, ¿qué pasa si el usuario hace clic en “Reintentar”? Utilice comenzar a buscar nuevamente. isLoading es verdadero. isError también es verdadero (de la ejecución anterior).
La interfaz de usuario muestra un control giratorio Y un mensaje de error simultáneamente.
Este es un Estado imposible.
Lo solucionas agregando setIsError(false) al principio.
Luego agrega isSuccess. Ahora tienes 3 booleanos. €2^3 = 8€ combinaciones posibles. Sólo 4 son válidos. El 50% de su espacio estatal son errores.
En Maison Code Paris, optimizamos la confiabilidad. Rechazamos la “sopa booleana”. Usamos Máquinas de Estados.
Por qué Maison Code habla de esto
En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.
Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.
Por qué Maison Code modela la lógica formalmente
Creamos paneles financieros donde un “fallo” no es sólo molesto; es una responsabilidad. Utilizamos Máquinas de Estado (XState) para garantizar la corrección:
- Seguridad: Los estados imposibles (Cargando + Error) son matemáticamente irrepresentables.
- Documentación: El código es el diagrama. Exportamos gráficos de estado para mostrar a las partes interesadas exactamente cómo funciona el flujo de pago.
- Testability: Generamos automáticamente pruebas de cobertura de ruta del 100% a partir de la definición de la máquina. No esperamos que funcione; demostramos que funciona.
La máquina de estados finitos (FSM)
Una máquina de estados es un modelo de comportamiento. Consta de:
- Estados: (p. ej., “inactivo”, “cargando”, “éxito”, “fallo”).
- Eventos: (por ejemplo,
FETCH,RETRY,CANCEL). - Transiciones: (por ejemplo,
inactivo+FETCH->cargando).
Fundamentalmente, la máquina sólo puede estar en un estado a la vez. Si se encuentra efectivamente en el estado de “cargando” y ocurre el evento “FETCH” (el usuario hace doble clic), la máquina lo ignora (a menos que lo permita explícitamente). Las condiciones de carrera desaparecen.
XState: La Biblioteca
Usamos XState. Es el estándar para FSM en JavaScript. Implementa Statecharts (estándar W3C SCXML), que permite:
- Estados jerárquicos (padre/hijo).
- Estados paralelos (regiones ortogonales).
- Estados del historial (Recordando dónde lo dejaste).
Implementación
importar {crearMáquina, asignar} desde 'xstate';
const fetchMachine = createMachine({
identificación: 'buscar',
inicial: 'inactivo',
contexto: {
datos: nulo,
error: nulo,
},
estados: {
inactivo: {
en: { FETCH: 'cargando' }
},
cargando: {
// Invocar una promesa (servicio)
invocar: {
src: 'fetchData',
listo: {
objetivo: 'éxito',
acciones: asignar({ datos: (contexto, evento) => evento.datos })
},
enError: {
objetivo: 'fracaso',
acciones: asignar ({ error: (contexto, evento) => evento.datos })
}
}
},
éxito: {
// ¿Estado terminal? O tal vez permitir la actualización
en: { ACTUALIZAR: 'cargando' }
},
fracaso: {
en: { RETRY: 'cargando' }
}
}
});
Note la claridad. ¿Puedes “REINTENTAR” cuando tengas “éxito”? No. La transición no está definida. ¿Puedes “FETCH” al “cargar”? No. La lógica es estricta por diseño.
Guardias y contexto
A veces, las transiciones son condicionales.
“El usuario puede pasar al estado pago SÓLO SI formIsValid es verdadero”.
// guardia
en: {
SIGUIENTE: {
objetivo: 'pago',
cond: (contexto) => contexto.formIsValid
}
}
Esto efectivamente mueve la “Lógica de Negocios” fuera de la Capa de Vista (Componentes de React) y dentro de la Capa de Modelo (Máquina).
El componente React se vuelve tonto. Simplemente representa el estado y envía eventos.
máquina.enviar('SIGUIENTE'). No le importa si es el siguiente. La máquina decide.
Estados paralelos (regiones ortogonales)
Las aplicaciones reales son complejas. Imagine un widget de carga.
- Está subiendo un archivo (0% -> 100%).
- El Usuario puede Minimizar/Maximizar el widget.
Estos son independientes. Puede estar “subiendo” Y “minimizado”. XState maneja esto a través de Estados paralelos.
estados: {
proceso de carga: {
inicial: 'pendiente',
estados: {pendiente: {}, cargando: {}, completo: {} }
},
interfaz de usuario: {
inicial: 'ampliado',
estados: { expandido: {}, minimizado: {} }
}
}
Visualizador y Comunicación
La mejor característica de XState es el Visualizador.
Puede copiar y pegar su código en stately.ai/viz y generará un diagrama interactivo.
Usamos esto para comunicarnos con los gerentes de producto.
PM: “El usuario no debería poder cancelar una vez que comience el pago”.
Desarrollador: “Mire el diagrama. No hay ninguna flecha CANCELAR en el estado procesamiento_pago”.
Alinea el Modelo Mental con el Modelo de Código.
Pruebas basadas en modelos
Dado que la implementación actual es un gráfico, podemos generar pruebas automáticamente.
@xstate/test puede calcular la ruta más corta a cada estado.
Generará un plan de prueba:
- Comience en “inactivo”.
- Dispara “FETCH”.
- Espere “cargar”.
- Resuelva la promesa.
- Espere “éxito”.
Garantiza que tenga una cobertura del 100% de sus flujos lógicos.
10. El modelo de actor (actores XState)
Una sola máquina es genial.
¿Pero qué pasa si tienes 10 widgets de carga?
No quieres una “uploadMachine” gigante.
Quieres generar 10 pequeños uploadActors.
La máquina principal (la página) se comunica con el actor secundario (el widget) a través de mensajes (send({ type: 'UPLOAD_COMPLETE' })).
Este es el Modelo Actor (popularizado por Erlang/Elixir).
Proporciona aislamiento. Si un actor falla, no falla toda la aplicación.
XState hace que esto sea trivial usando spawn().
11. Diseñadores de estados visuales
¿Por qué escribir código? Stately.ai le permite arrastrar y soltar cuadros para diseñar la lógica. Debido a que el código es el diagrama (isomorfo), el diseñador exporta el JSON que importa su código. Esto abre la puerta a la Lógica de código bajo mantenida por ingenieros superiores. Garantiza que las “Reglas de Negocio” sean visibles para las partes interesadas, no ocultas en espaguetis de “useEffect”.
13. Estados Jerárquicos (Estados Compuestos)
Un Checkout no es sólo una lista de estados. Tiene fases.
- Pagar:
- Envío: (
dirección,método) - Pago: (
card_entry,3ds_verification) Si cancelasPago, ¿vuelves aEnvío? Con estados jerárquicos, puede realizar la transición acheckout.shipping.history. Esto nos permite modelar flujos complejos sin “explosión de estados”. Agrupamos estados relacionados en un nodo padre. El padre maneja eventos globales (por ejemplo,LOGOUTtransiciones ahomedesde cualquier subestado).
- Envío: (
14. Pruebas: El modelo ES la prueba
Con @xstate/test, no escribimos pruebas E2E manuales para cada clic de botón.
Escribimos Aserciones de ruta.
- En el estado de “envío”, asevere “getByText(“Dirección de envío”)`.
- En estado de “pago”, afirmar “getByText (“Tarjeta de crédito”)”. Luego, la biblioteca ejecuta un recorrido de gráfico dirigido (ruta más corta) y ejecuta Puppeteer/Playwright. Genera cientos de pruebas automáticamente. Si agrega un nuevo estado, las pruebas se actualizan solas.
15. Manejo de la explosión estatal
La mayor crítica a los FSM es la “explosión estatal”.
Si tiene 10 valores booleanos, tiene €2^{10} = 1024€ estados.
Enumerarlos todos en una máquina es imposible.
Solución: Estados Paralelos (Ortogonalidad).
En lugar de loading_and_modal_open, loading_and_modal_closed, success_and_modal_open…
Tiene dos regiones: datos: {cargando, éxito} Y ui: {modal_open, modal_closed}.
Esto reduce la complejidad de Multiplicativo (€M * N€) a Aditivo (€M + N€).
16. Verificación Formal (Seguridad)
Debido a que XState es un gráfico matemático, podemos probar cosas matemáticamente. “¿Es posible llegar al estado de “pago” sin pasar por el “envío”?” Podemos ejecutar un algoritmo gráfico para comprobar si existe una ruta. Si es así, la compilación falla. Esta es Verificación formal. Generalmente está reservado para NASA/Avionics, pero XState lo lleva a los formularios de React. Esto nos da la confianza de que nuestra lógica de “control de acceso” es inquebrantable.
17. Conclusión
Las máquinas de estados añaden verbosidad. Escribir una máquina lleva más tiempo que “useState”. Pero para flujos complejos (pago, incorporación, asistentes), el retorno de la inversión es enorme. Se intercambia “Velocidad de implementación” por “Velocidad de mantenimiento” y “Confiabilidad”.
Creemos que UI Logic es diseño de algoritmos. Merece un modelo formal.
¿Pago con errores?
¿Tiene condiciones de carrera en su flujo de pago?
Refactorizar a máquinas de estados. Contrate a nuestros arquitectos.