El auge de las máquinas estatales

 

 

 

  • Patrones de diseño de interfaces inteligentes, vídeo de 10h + formación UX
  • SmashingConf Friburgo 2024

  • Índice
    1. Una introducción a las máquinas de estados
    2. ¿Cómo es mejor una máquina de estados?
    3. Creando una máquina de estados en JavaScript
    4. Gestión de máquinas de estados con una biblioteca
    5. Creando máquinas
    6. Conexión de las máquinas a la lógica de renderizado
    7. ¿Qué es el estado en el contexto del stent?
    8. Trabajar con la máquina de estados
    9. Controladores de entrada y acción
    10. Diversión con generadores
    11. Cómo el stent está resolviendo mis preocupaciones sobre Redux
      1. Demasiado código repetitivo
      2. Cambios de estado impredecibles
      3. Estados, no transiciones
    12. Conclusión
      1. Otras lecturas

    El desarrollo de la interfaz de usuario se volvió difícil en los últimos años. Esto se debe a que enviamos la administración del estado al navegador. Y gestionar el Estado es lo que hace que nuestro trabajo sea un desafío. Si lo hacemos correctamente veremos como nuestra aplicación escala fácilmente y sin errores. En este artículo, Krasimir Tsonev verá cómo utilizar el concepto de máquina de estados para resolver problemas de gestión del estado.

     

    Ya estamos en 2018 e innumerables desarrolladores front-end siguen liderando una batalla contra la complejidad y la inmovilidad. Mes tras mes, han buscado el santo grial: una arquitectura de aplicación libre de errores que les ayude a realizar entregas rápidas y con alta calidad. Soy uno de esos desarrolladores y encontré algo interesante que podría ayudar.

    Hemos dado un buen paso adelante con herramientas como React y Redux . Sin embargo, no son suficientes por sí solos en aplicaciones a gran escala. Este artículo le presentará el concepto de máquinas de estados en el contexto del desarrollo front-end. Probablemente ya hayas creado varios de ellos sin darte cuenta.

    Una introducción a las máquinas de estados

    Una máquina de estados es un modelo matemático de computación. Es un concepto abstracto según el cual la máquina puede tener diferentes estados, pero en un momento dado cumple sólo uno de ellos. Existen diferentes tipos de máquinas de estados. La más famosa, creo, es la máquina de Turing . Es una máquina de estados infinita, lo que significa que puede tener un número incontable de estados. La máquina de Turing no encaja bien en el desarrollo de la interfaz de usuario actual porque en la mayoría de los casos tenemos un número finito de estados. Ésta es la razón por la que las máquinas de estados finitos, como Mealy y Moore , tienen más sentido.

    La diferencia entre ellos es que la máquina de Moore cambia su estado basándose únicamente en su estado anterior. Desafortunadamente, tenemos muchos factores externos, como las interacciones del usuario y los procesos de red, lo que significa que la máquina de Moore tampoco es lo suficientemente buena para nosotros. Lo que buscamos es la máquina Mealy. Tiene un estado inicial y luego pasa a nuevos estados según la entrada y su estado actual.

    Una de las formas más sencillas de ilustrar cómo funciona una máquina de estados es mirar un torniquete. Tiene un número finito de estados: bloqueado y desbloqueado. Aquí tienes un gráfico sencillo que nos muestra estos estados, con sus posibles entradas y transiciones.

    El estado inicial del torniquete es bloqueado. No importa cuántas veces lo presionemos, permanecerá en ese estado bloqueado. Sin embargo, si le pasamos una moneda, pasa al estado desbloqueado. Otra moneda en este punto no serviría de nada; todavía estaría en el estado desbloqueado. Un empujón desde el otro lado funcionaría y podríamos pasar. Esta acción también hace que la máquina pase al estado bloqueado inicial.

    Si quisiéramos implementar una única función que controle el torniquete, probablemente terminaríamos con dos argumentos: el estado actual y una acción. Y si usas Redux, esto probablemente te suene familiar. Es similar a la conocida función reductora , donde recibimos el estado actual y, en función de la carga útil de la acción, decidimos cuál será el siguiente estado. El reductor es la transición en el contexto de las máquinas de estados. De hecho, cualquier aplicación que tenga un estado que podamos cambiar de alguna manera puede denominarse máquina de estados. Es solo que implementamos todo manualmente una y otra vez.

     

    ¿Cómo es mejor una máquina de estados?

    En el trabajo usamos Redux y estoy bastante contento con él. Sin embargo, comencé a ver patrones que no me gustan. Por "no me gusta" no me refiero a que no funcionan. Es más, añaden complejidad y me obligan a escribir más código. Tuve que emprender un proyecto paralelo en el que tenía espacio para experimentar y decidí repensar nuestras prácticas de desarrollo de React y Redux. Comencé a tomar notas sobre las cosas que me preocupaban y me di cuenta de que una abstracción de una máquina de estados realmente resolvería algunos de estos problemas. Saltemos y veamos cómo implementar una máquina de estados en JavaScript.

    Atacaremos un problema simple. Queremos obtener datos de una API de back-end y mostrárselos al usuario. El primer paso es aprender a pensar en estados, en lugar de transiciones. Antes de entrar en las máquinas de estado, mi flujo de trabajo para crear una característica de este tipo solía verse así:

    • Mostramos un botón de recuperación de datos.
    • El usuario hace clic en el botón de búsqueda de datos.
    • Encienda la solicitud al back-end.
    • Recupere los datos y analícelos.
    • Muéstralo al usuario.
    • O, si hay un error, muestre el mensaje de error y muestre el botón de recuperación de datos para que podamos activar el proceso nuevamente.

    Pensamos de forma lineal y básicamente intentamos cubrir todas las direcciones posibles hasta el resultado final. Un paso lleva al otro y rápidamente comenzaríamos a bifurcar nuestro código. ¿Qué pasa con problemas como que el usuario haga doble clic en el botón, o que el usuario haga clic en el botón mientras esperamos la respuesta del back-end, o que la solicitud se realice correctamente pero los datos estén dañados? En estos casos probablemente tendríamos varias banderas que nos muestran lo sucedido. Tener banderas significa más ifcláusulas y, en aplicaciones más complejas, más conflictos.

    Esto se debe a que estamos pensando en transiciones. Nos estamos centrando en cómo ocurren estas transiciones y en qué orden. En cambio, centrarse en los distintos estados de la aplicación sería mucho más sencillo. ¿Cuántos estados tenemos y cuáles son sus posibles aportaciones? Usando el mismo ejemplo:

    • inactivo
      En este estado, mostramos el botón de búsqueda de datos, nos sentamos y esperamos. La posible acción es:
      • hacer clic
        Cuando el usuario hace clic en el botón, activamos la solicitud al backend y luego hacemos la transición de la máquina a un estado de "obtención".
    • fetching
      La solicitud está en vuelo y nos sentamos y esperamos. Las acciones son:
      • éxito
        Los datos llegan exitosamente y no están dañados. Usamos los datos de alguna manera y volvemos al estado "inactivo".
      • falla
        Si hay un error al realizar la solicitud o analizar los datos, pasamos a un estado de "error".
    • error
      Mostramos un mensaje de error y mostramos el botón de recuperación de datos. Este estado acepta una acción:
      • reintentar
        Cuando el usuario hace clic en el botón de reintentar, activamos la solicitud nuevamente y hacemos la transición de la máquina al estado de "obtención".

    Hemos descrito aproximadamente los mismos procesos, pero con estados y entradas.

     

    Esto simplifica la lógica y la hace más predecible. También resuelve algunos de los problemas mencionados anteriormente. Tenga en cuenta que, mientras estamos en el estado "obteniendo", no aceptamos ningún clic. Entonces, incluso si el usuario hace clic en el botón, no sucederá nada porque la máquina no está configurada para responder a esa acción mientras se encuentra en ese estado. Este enfoque elimina automáticamente la ramificación impredecible de nuestra lógica de código. Esto significa que tendremos menos código que cubrir durante las pruebas . Además, algunos tipos de pruebas, como las pruebas de integración, se pueden automatizar. Piense en cómo tendríamos una idea muy clara de lo que hace nuestra aplicación y podríamos crear un script que repase los estados y transiciones definidos y que genere aserciones. Estas afirmaciones podrían probar que hemos alcanzado todos los estados posibles o hemos recorrido un viaje particular.

    De hecho, escribir todos los estados posibles es más fácil que escribir todas las transiciones posibles porque sabemos qué estados necesitamos o tenemos. Por cierto, en la mayoría de los casos, los estados describirían la lógica empresarial de nuestra aplicación, mientras que las transiciones suelen ser desconocidas al principio. Los errores en nuestro software son el resultado de acciones enviadas en un estado incorrecto y/o en el momento incorrecto. Dejan nuestra aplicación en un estado que no conocemos y esto rompe nuestro programa o hace que se comporte incorrectamente. Por supuesto, no queremos estar en una situación así. Las máquinas de estado son buenos cortafuegos . Nos protegen de alcanzar estados desconocidos porque establecemos límites sobre lo que puede suceder y cuándo, sin decir explícitamente cómo. El concepto de máquina de estados combina muy bien con un flujo de datos unidireccional. Juntos, reducen la complejidad del código y aclaran el misterio de dónde se originó un estado.

    Creando una máquina de estados en JavaScript

    Basta de hablar, veamos algo de código. Usaremos el mismo ejemplo. Según la lista anterior, comenzaremos con lo siguiente:

    const machine = { 'idle': { click: function () { ... } }, 'fetching': { success: function () { ... }, failure: function () { ... } }, 'error': { 'retry': function () { ... } }}

    Tenemos los estados como objetos y sus posibles entradas como funciones. Sin embargo, falta el estado inicial. Cambiemos el código de arriba a esto:

    const machine = { state: 'idle', transitions: { 'idle': { click: function() { ... } }, 'fetching': { success: function() { ... }, failure: function() { ... } }, 'error': { 'retry': function() { ... } } }}

    Una vez que definimos todos los estados que tienen sentido para nosotros, estamos listos para enviar la entrada y cambiar el estado. Lo haremos utilizando los dos métodos auxiliares siguientes:

    const machine = { dispatch(actionName, ...payload) { const actions = this.transitions[this.state]; const action = this.transitions[this.state][actionName]; if (action) { action.apply(machine, ...payload); } }, changeStateTo(newState) { this.state = newState; }, ...}

    La dispatchfunción comprueba si hay una acción con el nombre de pila en las transiciones del estado actual. Si es así, lo dispara con la carga útil dada. También llamamos al actioncontrolador con machinecontexto, para que podamos enviar otras acciones con `this.dispatch()` o cambiar el estado con `this.changeStateTo()`.

     

    Siguiendo el recorrido del usuario de nuestro ejemplo, la primera acción que debemos realizar es click. Así es como se ve el controlador de esa acción:

    transitions: { 'idle': { click: function () { this.changeStateTo('fetching'); service.getData().then( data = { try { this.dispatch('success', JSON.parse(data)); } catch (error) { this.dispatch('failure', error) } }, error = this.dispatch('failure', error) ); } }, ...}machine.dispatch('click');

    Primero cambiamos el estado de la máquina a fetching. Luego, activamos la solicitud al back-end. Supongamos que tenemos un servicio con un método getDataque devuelve una promesa. Una vez que se resuelva y el análisis de datos sea correcto, lo enviaremos success, si no failure.

    Hasta ahora, todo bien. A continuación, tenemos que implementar successacciones failuree insumos bajo el fetchingEstado:

    transitions: { 'idle': { ... }, 'fetching': { success: function (data) { // render the data this.changeStateTo('idle'); }, failure: function (error) { this.changeStateTo('error'); } }, ...}

    Observa cómo hemos liberado a nuestro cerebro de tener que pensar en el proceso anterior. No nos importan los clics de los usuarios ni lo que sucede con la solicitud HTTP. Sabemos que la aplicación está en un fetchingestado y solo esperamos estas dos acciones. Es un poco como escribir nueva lógica de forma aislada.

    El último bit es el errorestado. Sería bueno si proporcionemos esa lógica de reintento para que la aplicación pueda recuperarse de una falla.

    transitions: { 'error': { retry: function () { this.changeStateTo('idle'); this.dispatch('click'); } }}

    Aquí tenemos que duplicar la lógica que escribimos en el clickcontrolador. Para evitar eso, debemos definir el controlador como una función accesible para ambas acciones, o primero hacemos la transición al idleestado y luego enviamos la clickacción manualmente.

    Puede encontrar un ejemplo completo de la máquina de estado de funcionamiento en mi Codepen .

    Gestión de máquinas de estados con una biblioteca

    El patrón de máquina de estados finitos funciona independientemente de si usamos React, Vue o Angular. Como vimos en la sección anterior, podemos implementar fácilmente una máquina de estados sin muchos problemas. Sin embargo, a veces una biblioteca ofrece más flexibilidad. Algunos de los buenos son Machina.js y XState . En este artículo, sin embargo, hablaremos sobre Stent , mi biblioteca tipo Redux que se basa en el concepto de máquinas de estados finitos.

     

    Stent es una implementación de un contenedor de máquinas de estados. Sigue algunas de las ideas de los proyectos Redux y Redux-Saga , pero proporciona, en mi opinión, procesos más simples y sin repeticiones. Se desarrolla mediante desarrollo basado en Léame y, literalmente, pasé semanas solo en el diseño de la API. Como estaba escribiendo la biblioteca, tuve la oportunidad de solucionar los problemas que encontré al usar las arquitecturas Redux y Flux.

    Creando máquinas

    En la mayoría de los casos, nuestras aplicaciones cubren múltiples dominios. No podemos ir con una sola máquina. Así, el Stent permite la creación de muchas máquinas:

    import { Machine } from 'stent';const machineA = Machine.create('A', { state: ..., transitions: ...});const machineB = Machine.create('B', { state: ..., transitions: ...});

    Posteriormente, podremos acceder a estas máquinas mediante el Machine.getmétodo:

    const machineA = Machine.get('A');const machineB = Machine.get('B');

    Conexión de las máquinas a la lógica de renderizado

    El renderizado en mi caso se realiza mediante React, pero podemos usar cualquier otra biblioteca. Todo se reduce a activar una devolución de llamada en la que activamos el renderizado. Una de las primeras características en las que trabajé fue la connectfunción:

    import { connect } from 'stent/lib/helpers';Machine.create('MachineA', ...);Machine.create('MachineB', ...);connect() .with('MachineA', 'MachineB') .map((MachineA, MachineB) = { ... rendering here });

    Decimos qué máquinas son importantes para nosotros y damos sus nombres. La devolución de llamada a la que pasamos mapse activa una vez inicialmente y luego cada vez que cambia el estado de algunas de las máquinas. Aquí es donde activamos el renderizado. En este punto, tenemos acceso directo a las máquinas conectadas, por lo que podemos recuperar el estado y los métodos actuales. También existen mapOnce, para activar la devolución de llamada solo una vez y mapSilent, para omitir esa ejecución inicial.

    Para mayor comodidad, se exporta un asistente específicamente para la integración de React. Es realmente similar al de Redux connect(mapStateToProps).

    import React from 'react';import { connect } from 'stent/lib/react';class TodoList extends React.Component { render() { const { isIdle, todos } = this.props; ... }}// MachineA and MachineB are machines defined// using Machine.create functionexport default connect(TodoList) .with('MachineA', 'MachineB') .map((MachineA, MachineB) = { isIdle: MachineA.isIdle, todos: MachineB.state.todos });

    Stent ejecuta nuestra devolución de llamada de mapeo y espera recibir un objeto, un objeto que se envía a propsnuestro componente React.

    ¿Qué es el estado en el contexto del stent?

    Hasta ahora, nuestro estado ha sido simples hilos. Desafortunadamente, en el mundo real, tenemos que mantener más de una cuerda en estado. Es por eso que el estado del Stent es en realidad un objeto con propiedades en su interior. La única propiedad reservada es name. Todo lo demás son datos específicos de la aplicación. Por ejemplo:

     

    { name: 'idle' }{ name: 'fetching', todos: [] }{ name: 'forward', speed: 120, gear: 4 }

    Mi experiencia con Stent hasta ahora me muestra que si el objeto de estado se hace más grande, probablemente necesitaríamos otra máquina que maneje esas propiedades adicionales. Identificar los distintos estados lleva algún tiempo, pero creo que este es un gran paso adelante en la redacción de aplicaciones más manejables. Es un poco como predecir el futuro y trazar marcos de las posibles acciones.

    Trabajar con la máquina de estados

    De forma similar al ejemplo del principio, tenemos que definir los posibles estados (finitos) de nuestra máquina y describir las posibles entradas:

    import { Machine } from 'stent';const machine = Machine.create('sprinter', { state: { name: 'idle' }, // initial state transitions: { 'idle': { 'run please': function () { return { name: 'running' }; } }, 'running': { 'stop now': function () { return { name: 'idle' }; } } }});

    Tenemos nuestro estado inicial, idleque acepta una acción de run. Una vez que la máquina está en un runningestado, podemos ejecutar la stopacción, lo que nos devuelve al idleestado.

    Probablemente recuerdes los ayudantes dispatchy changeStateTode nuestra implementación anterior. Esta biblioteca proporciona la misma lógica, pero está oculta internamente y no tenemos que pensar en ello. Por conveniencia, según la transitionspropiedad, Stent genera lo siguiente:

    • métodos auxiliares para verificar si la máquina está en un estado particular: el idleestado produce el isIdle()método, mientras que para runningnosotros tenemos isRunning();
    • métodos auxiliares para enviar acciones: runPlease()y stopNow().

    Entonces, en el ejemplo anterior, podemos usar esto:

    machine.isIdle(); // booleanmachine.isRunning(); // booleanmachine.runPlease(); // fires actionmachine.stopNow(); // fires action

    Combinando los métodos generados automáticamente con la connectfunción de utilidad, podemos cerrar el círculo. Una interacción del usuario desencadena la entrada y la acción de la máquina, lo que actualiza el estado. Debido a esa actualización, la función de mapeo pasada connectse activa y se nos informa sobre el cambio de estado. Luego, volvemos a renderizar.

    Controladores de entrada y acción

    Probablemente la parte más importante son los controladores de acciones. Este es el lugar donde escribimos la mayor parte de la lógica de la aplicación porque respondemos a entradas y estados modificados. Algo que me gusta mucho en Redux también se integra aquí: la inmutabilidad y simplicidad de la función reductora . La esencia del controlador de acciones de Stent es la misma. Recibe el estado actual y la carga útil de la acción, y debe devolver el nuevo estado. Si el controlador no devuelve nada ( undefined), entonces el estado de la máquina sigue siendo el mismo.

     

    transitions: { 'fetching': { 'success': function (state, payload) { const todos = [ ...state.todos, payload ]; return { name: 'idle', todos }; } }}

    Supongamos que necesitamos recuperar datos de un servidor remoto. Activamos la solicitud y hacemos la transición de la máquina a un fetchingestado. Una vez que los datos provienen del back-end, activamos una successacción, como esta:

    machine.success({ label: '...' });

    Luego, volvemos a un idleestado y mantenemos algunos datos en forma de todosmatriz. Hay un par de valores más posibles para establecer como controladores de acciones. El primer caso y el más simple es cuando pasamos solo una cadena que se convierte en el nuevo estado.

    transitions: { 'idle': { 'run': 'running' }}

    Esta es una transición desde { name: 'idle' }el { name: 'running' }uso de la run()acción. Este enfoque es útil cuando tenemos transiciones de estado sincrónicas y no tenemos metadatos. Entonces, si mantenemos algo más en el estado, ese tipo de transición lo eliminará. De manera similar, podemos pasar un objeto de estado directamente:

    transitions: { 'editing': { 'delete all todos': { name: 'idle', todos: [] } }}

    Estamos pasando del editinguso idlede la deleteAllTodosacción.

    Ya vimos el controlador de funciones y la última variante del controlador de acciones es una función generadora. Está inspirado en el proyecto Redux-Saga y tiene este aspecto:

    import { call } from 'stent/lib/helpers';Machine.create('app', { 'idle': { 'fetch data': function * (state, payload) { yield { name: 'fetching' } try { const data = yield call(requestToBackend, '/api/todos/', 'POST'); return { name: 'idle', data }; } catch (error) { return { name: 'error', error }; } } }});

    Si no tiene experiencia con generadores, esto puede parecer un poco críptico. Pero los generadores de JavaScript son una herramienta poderosa. Se nos permite pausar nuestro controlador de acciones, cambiar el estado varias veces y manejar la lógica asíncrona.

    Diversión con generadores

    Cuando conocí Redux-Saga por primera vez , pensé que era una forma demasiado complicada de manejar operaciones asíncronas. De hecho, es una implementación bastante inteligente del patrón de diseño de comandos . El principal beneficio de este patrón es que separa la invocación de la lógica y su implementación real.

    En otras palabras, decimos lo que queremos pero no cómo debería suceder. La serie de blogs de Matt Hink me ayudó a comprender cómo se implementan las sagas y recomiendo encarecidamente leerla. Introduje las mismas ideas en Stent y, a los efectos de este artículo, diremos que al producir cosas, estamos dando instrucciones sobre lo que queremos sin realmente hacerlo. Una vez realizada la acción, recuperamos el control.

    Por el momento, es posible que se envíen (cedan) un par de cosas:

    • un objeto de estado (o una cadena) para cambiar el estado de la máquina;
    • una llamada al callasistente (acepta una función síncrona, que es una función que devuelve una promesa u otra función generadora); básicamente estamos diciendo: “Ejecute esto por mí y, si es asíncrono, espere. Una vez que hayas terminado, dame el resultado.”;
    • una llamada del waitayudante (acepta una cadena que representa otra acción); Si usamos esta función de utilidad, pausamos el controlador y esperamos a que se envíe otra acción.

    Aquí hay una función que ilustra las variantes:

     

    const fireHTTPRequest = function () { return new Promise((resolve, reject) = { // ... });}...transitions: { 'idle': { 'fetch data': function * () { yield 'fetching'; // sets the state to { name: 'fetching' } yield { name: 'fetching' }; // same as above // wait for getTheData and checkForErrors actions // to be dispatched const [ data, isError ] = yield wait('get the data', 'check for errors'); // wait for the promise returned by fireHTTPRequest // to be resolved const result = yield call(fireHTTPRequest, '/api/data/users'); return { name: 'finish', users: result }; } }}

    Como podemos ver, el código parece sincrónico, pero en realidad no lo es. Es simplemente Stent haciendo la parte aburrida de esperar la promesa resuelta o iterar sobre otro generador.

    Cómo el stent está resolviendo mis preocupaciones sobre Redux

    Demasiado código repetitivo

    La arquitectura Redux (y Flux) se basa en acciones que circulan en nuestro sistema. Cuando la aplicación crece, normalmente acabamos teniendo muchas constantes y creadores de acciones. Estas dos cosas suelen estar en carpetas diferentes y el seguimiento de la ejecución del código a veces lleva tiempo. Además, cuando agregamos una nueva función, siempre tenemos que lidiar con un conjunto completo de acciones, lo que significa definir más nombres de acciones y creadores de acciones.

    En Stent, no tenemos nombres de acciones y la biblioteca crea los creadores de acciones automáticamente para nosotros:

    const machine = Machine.create('todo-app', { state: { name: 'idle', todos: [] }, transitions: { 'idle': { 'add todo': function (state, todo) { ... } } }});machine.addTodo({ title: 'Fix that bug' });

    Tenemos el machine.addTodocreador de acciones definido directamente como método de la máquina. Este enfoque también resolvió otro problema al que me enfrenté: encontrar el reductor que responda a una acción particular. Por lo general, en los componentes de React, vemos nombres de creadores de acciones como addTodo; sin embargo, en los reductores trabajamos con un tipo de acción que es constante. A veces tengo que saltar al código del creador de acciones solo para poder ver el tipo exacto. Aquí no tenemos ningún tipo.

    Cambios de estado impredecibles

    En general, Redux hace un buen trabajo al gestionar el estado de forma inmutable. El problema no está en Redux en sí, sino en que el desarrollador puede realizar cualquier acción en cualquier momento. Si decimos que tenemos una acción que enciende las luces, ¿está bien realizar esa acción dos veces seguidas? Si no es así, ¿cómo se supone que vamos a resolver este problema con Redux? Bueno, probablemente pondríamos algún código en el reductor que proteja la lógica y verifique si las luces ya están encendidas; tal vez una ifcláusula que verifique el estado actual. Ahora la pregunta es, ¿no está esto más allá del alcance del reductor? ¿Debería el reductor conocer estos casos extremos?

    Lo que me falta en Redux es una forma de detener el envío de una acción basada en el estado actual de la aplicación sin contaminar el reductor con lógica condicional. Y tampoco quiero llevar esta decisión a la capa de vista, donde se despide al creador de la acción. Con Stent, esto sucede automáticamente porque la máquina no responde a acciones que no estén declaradas en el estado actual. Por ejemplo:

    const machine = Machine.create('app', { state: { name: 'idle' }, transitions: { 'idle': { 'run': 'running', 'jump': 'jumping' }, 'running': { 'stop': 'idle' } }});// this is finemachine.run();// This will do nothing because at this point// the machine is in a 'running' state and there is// only 'stop' action there.machine.jump();

    El hecho de que la máquina acepte sólo entradas específicas en un momento dado nos protege de errores extraños y hace que nuestras aplicaciones sean más predecibles.

    Estados, no transiciones

    Redux, al igual que Flux, nos hace pensar en términos de transiciones. El modelo mental de desarrollo con Redux está impulsado en gran medida por las acciones y cómo estas acciones transforman el estado de nuestros reductores. Eso no está mal, pero he descubierto que tiene más sentido pensar en términos de estados: en qué estados podría estar la aplicación y cómo estos estados representan los requisitos comerciales.

    Conclusión

    El concepto de máquinas de estados en programación, especialmente en el desarrollo de interfaces de usuario, me abrió los ojos. Empecé a ver máquinas de estados en todas partes y tengo cierto deseo de cambiar siempre a ese paradigma. Definitivamente veo los beneficios de tener estados más estrictamente definidos y transiciones entre ellos. Siempre estoy buscando formas de hacer que mis aplicaciones sean simples y legibles. Creo que las máquinas de estados son un paso en esta dirección. El concepto es simple y al mismo tiempo poderoso. Tiene el potencial de eliminar muchos errores.

    Otras lecturas

    El auge de las máquinas estatales

    El auge de las máquinas estatales

    Patrones de diseño de interfaces inteligentes, vídeo de 10h + formación UX SmashingConf Friburgo 2024 Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-el-auge-de-las-maquinas-estatales-929-0.jpg

    2024-05-20

     

    El auge de las máquinas estatales
    El auge de las máquinas estatales

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20