Redux: Enfoque alternativo a las acciones asincrónicas

Creado en 27 dic. 2015  ·  44Comentarios  ·  Fuente: reduxjs/redux

He estado explorando una alternativa a la forma en que las acciones asíncronas se realizan en redux, y agradecería cualquier comentario que otros puedan tener sobre lo que he hecho.

Para ilustrar mi enfoque, cambié el ejemplo asíncrono en mi clon de redux: https://github.com/winstonewert/redux/tree/master/examples/async

Normalmente, las acciones externas se realizan haciendo que los creadores de acciones sean asincrónicos. En el caso del ejemplo asíncrono, el creador de la acción fetchPosts envía una acción REQUEST_POSTS para indicar el inicio de la solicitud, seguida de RECEIVE_POSTS una vez que las publicaciones han regresado de la API.

En mi ejemplo, todos los creadores de acciones son sincrónicos. En cambio, hay una función que devuelve la lista de acciones asincrónicas que deberían estar teniendo lugar actualmente según el estado. Vea mi ejemplo aquí: https://github.com/rackt/redux/compare/master...winstonewert : master # diff-8a94dc7aa7bdc6e5390c9216a69761f8R12

La función doReactions se suscribe a la tienda y garantiza que el estado real de las solicitudes que se están realizando en ese momento coincida con el estado devuelto por el estado doReactions al iniciar o cancelar solicitudes.

Entonces, ¿cuál es la diferencia?

1) La función de reacciones es una función pura del estado. Esto facilita la prueba.
2) La lógica real de qué solicitudes realizar es más simple. Vea la función de pocas líneas en mi ejemplo, frente a las diversas piezas de lógica distribuidas a través de contenedores y creadores de acciones antes.
3) Facilita la cancelación de solicitudes.

¿Alguna idea?

discussion feedback wanted

Comentario más útil

Yo también he estado pensando mucho en formas alternativas de manejar los efectos secundarios en redux y espero no secuestrar su hilo mientras me olvido de algunos de los problemas que veo con los enfoques actuales y por qué y cómo creo que esto es gran paso en la dirección correcta a pesar de su aparente simplicidad.

El problema de los efectos secundarios en los creadores de acciones

En los lenguajes funcionales puros, los efectos secundarios siempre se llevan al borde de la aplicación y se devuelven al tiempo de ejecución para su ejecución. En Elm, los reductores devuelven una tupla que contiene el nuevo estado y cualquier efecto que deba ejecutarse. Sin embargo, los métodos con esta firma aún no se pueden combinar con otros reductores redux.

El lugar obvio (pero posiblemente no el mejor) para realizar efectos secundarios en redux se ha convertido en los creadores de acciones y se han desarrollado varias opciones de middleware diferentes para respaldar este patrón. Sin embargo, creo que los enfoques actuales de middleware son más una solución para no poder devolver los efectos secundarios como un concepto de primera clase de los reductores.

Si bien la gente todavía está construyendo cosas increíbles con redux y es un gran paso adelante y mucho más simple y pragmático que la mayoría de las alternativas, hay algunos problemas que veo con tener efectos secundarios dentro de los creadores de acciones:

  • El estado implícito está oculto
  • Duplicación de lógica empresarial
  • Las suposiciones y / o dependencias del contexto reducen la reutilización
  • Los creadores de acciones con efectos secundarios son difíciles de probar
  • No se puede optimizar ni procesar por lotes

El estado implícito está oculto

En la aplicación de contador, incrementAsync crea un tiempo de espera y solo cuando se completa se actualiza el estado de la aplicación. Si quisiera, por ejemplo, mostrar un indicador visual de que una operación de incremento está en progreso, la vista no puede inferir esto del estado de la aplicación. Este estado está implícito y oculto.

Aunque a veces elegante, no estoy tan seguro de la propuesta de utilizar generadores como orquestadores de creadores de acciones, ya que el estado implícito está oculto y no se puede serializar fácilmente.

Usando redux-thunk o similar, podría enviar múltiples mensajes al reductor informándole cuando la operación de incremento ha comenzado y cuando se ha completado, pero esto crea un conjunto diferente de problemas.

Rebobinar el estado hasta un punto en el que la operación de incremento se marque como en progreso después de que el efecto se haya completado no regenerará realmente el efecto secundario y, por lo tanto, permanecerá en progreso indefinidamente.

Su propuesta parece resolver este problema. Dado que los efectos secundarios se crean a partir del estado, la intención debe expresarse con el estado resultante de una forma u otra, por lo tanto, si uno revierte el almacenamiento a un estado anterior donde se inició la acción, las reacciones iniciarán el efecto nuevamente en lugar de dejar el estado en el limbo.

Duplicación de lógica empresarial

Es natural que las acciones generen un efecto secundario solo cuando la aplicación se encuentra en un estado específico. En redux, si el creador de una acción requiere un estado, debe ser una función simple y pura o estar provista explícitamente del estado.

Como ejemplo simple, digamos que comenzamos con la aplicación de contador de ejemplo y queremos cambiar el contador a un color de fuente aleatorio cada vez que el contador sea múltiplo de 5.

Dado que la generación de números aleatorios es impura, el lugar sugerido para colocar este comportamiento es en el creador de acciones. Sin embargo, hay varias acciones diferentes que pueden cambiar el valor del contador, incrementar, decrementar, incrementarAsync, incrementIfOdd (que no necesita ser modificado en este caso).

El incremento y la disminución anteriormente no requerían ningún estado, ya que anteriormente se manejaban en el reductor y, por lo tanto, tenían acceso al valor actual, pero dado que un reductor no puede tener o devolver efectos secundarios (generación de números aleatorios), estas funciones ahora se convierten en creadores de acciones impuras que necesitan conocer el valor actual del contador para determinar si es necesario seleccionar un nuevo color de fuente aleatorio y esta lógica debe duplicarse en todos los creadores de acciones de contador.

Una posible alternativa a proporcionar explícitamente el estado actual sería usar redux-thunk y devolver una devolución de llamada para acceder al estado actual. Esto le permite evitar modificar todos los lugares donde se crean las acciones para proporcionar el valor actual, pero ahora requiere que el creador de la acción sepa en qué parte del estado global de la aplicación se almacena el valor y esto limita la capacidad de reutilizar el mismo contador varias veces dentro de la misma aplicación o en diferentes aplicaciones donde el estado puede estar estructurado de manera diferente.

Las suposiciones y / o dependencias del contexto reducen la reutilización

Una vez más, revisando el ejemplo de contador, notará que solo hay una instancia de contador. Si bien es trivial tener muchos contadores en la página que visualizan / actualizan el mismo estado, se requieren modificaciones adicionales al contador si desea que cada contador use un estado diferente.

Esto se ha discutido antes. ¿Cómo crear una lista genérica como reductor y potenciador de componentes?

Si el contador solo usara tipos de acción simples, sería relativamente trivial aplicar la arquitectura elm .

En este caso, el padre simplemente envuelve a los creadores de acciones o al despachador para aumentar el mensaje con cualquier contexto necesario, luego puede llamar al reductor directamente con el estado localizado.

Si bien el ejemplo de React Elmish parece impresionante, en el ejemplo faltan notablemente los dos creadores de acciones problemáticos, incrementIfOdd e incrementAsync.

incrementIfOdd depende del middleware para determinar el estado actual y, por lo tanto, necesita saber su ubicación dentro del estado de la aplicación.

incrementAsync finalmente envía directamente una acción de incremento que no está expuesta al componente principal y, por lo tanto, no se puede envolver con contexto adicional.

Si bien su propuesta no aborda directamente este problema, si incrementAsync se implementó como una acción simple que cambió el estado a {counter: 0, incrementAfterDelay: 1000} para activar el efecto secundario en un oyente de la tienda, entonces incrementAsync se convierte en un mensaje simple. incrementIfOdd es puro, por lo que podría implementarse en el reductor o podría proporcionarse el estado explícitamente ... Por lo tanto, es posible aplicar la arquitectura elm nuevamente si se desea.

Los creadores de acciones con efectos secundarios son difíciles de probar

Creo que es bastante obvio que los efectos secundarios serán más difíciles de probar. Una vez que sus efectos secundarios están condicionados al estado actual y la lógica empresarial, no solo se vuelven más difíciles sino también más importantes de probar.

Su propuesta le permite a uno crear fácilmente una prueba de que una transición de estado creará un estado que contiene las reacciones deseadas sin ejecutar ninguna de ellas. Las reacciones también son más fáciles de probar, ya que no necesitan ningún estado condicional o lógica empresarial.

No se puede optimizar ni procesar por lotes

Una publicación de blog reciente de John A De Goes discutió el problema con los tipos de datos opacos como IO o Task para expresar efectos. Al usar descripciones declarativas de efectos secundarios en lugar de tipos opacos, tiene el potencial de optimizar o combinar efectos más adelante.

Una arquitectura moderna para FP

Los thunks, promesas y generadores son opacos y, por lo tanto, las optimizaciones como el procesamiento por lotes y / o la supresión de llamadas api duplicadas deben manejarse explícitamente con funciones similares a fetchPostsIfNeeded .

Su propuesta elimina fetchPostsIfNeeded y parece totalmente factible implementar una función reactions que podría optimizar múltiples solicitudes y / o usar diferentes conjuntos de apis según sea necesario cuando se han solicitado más o menos datos.

Mi implementación

Recientemente creé una bifurcación de redux que permite crear reductores que devuelvan solo el nuevo estado como lo hacen ahora o un objeto especial con efectos que contiene el nuevo estado y una descripción de los efectos que se ejecutarán después del reductor.

No estaba seguro de cómo hacer esto sin bifurcar redux, ya que era necesario modificar compose y combineReducers para levantar los efectos sobre los reductores existentes a fin de mantener la compatibilidad con el código de reductor existente.

Sin embargo, su propuesta es bastante buena en el sentido de que no requiere modificar redux. Además, creo que su solución hace un mejor trabajo al resolver el problema del estado oculto implícito y probablemente sea más fácil de combinar u optimizar las reacciones resultantes.

Resumen

Al igual que React es "solo la interfaz de usuario", y no es muy prescriptivo cómo uno realmente almacena o actualiza el estado de la aplicación, Redux es principalmente "solo la tienda" y no es muy prescriptivo sobre cómo uno maneja los efectos secundarios.

Nunca culpo a nadie por ser pragmático y hacer las cosas, y los muchos contribuyentes a redux y el middleware han permitido a las personas crear cosas realmente interesantes más rápido y mejor de lo que era posible anteriormente. Solo gracias a sus contribuciones hemos llegado tan lejos. Un agradecimiento especial a todos los que han contribuido.

Redux es asombroso. Estos no son problemas necesarios con Redux en sí, sino, con suerte, críticas constructivas de los patrones arquitectónicos actuales y las motivaciones y las ventajas potenciales de ejecutar los efectos después de las modificaciones estatales en lugar de antes.

Todos 44 comentarios

¡Enfoque interesante! Parece que idealmente debería estar desacoplado de la naturaleza de la asincronía, como el método utilizado para ejecutar el XHR, o incluso que una solicitud web es la fuente de la asincronía en primer lugar.

Yo también he estado pensando mucho en formas alternativas de manejar los efectos secundarios en redux y espero no secuestrar su hilo mientras me olvido de algunos de los problemas que veo con los enfoques actuales y por qué y cómo creo que esto es gran paso en la dirección correcta a pesar de su aparente simplicidad.

El problema de los efectos secundarios en los creadores de acciones

En los lenguajes funcionales puros, los efectos secundarios siempre se llevan al borde de la aplicación y se devuelven al tiempo de ejecución para su ejecución. En Elm, los reductores devuelven una tupla que contiene el nuevo estado y cualquier efecto que deba ejecutarse. Sin embargo, los métodos con esta firma aún no se pueden combinar con otros reductores redux.

El lugar obvio (pero posiblemente no el mejor) para realizar efectos secundarios en redux se ha convertido en los creadores de acciones y se han desarrollado varias opciones de middleware diferentes para respaldar este patrón. Sin embargo, creo que los enfoques actuales de middleware son más una solución para no poder devolver los efectos secundarios como un concepto de primera clase de los reductores.

Si bien la gente todavía está construyendo cosas increíbles con redux y es un gran paso adelante y mucho más simple y pragmático que la mayoría de las alternativas, hay algunos problemas que veo con tener efectos secundarios dentro de los creadores de acciones:

  • El estado implícito está oculto
  • Duplicación de lógica empresarial
  • Las suposiciones y / o dependencias del contexto reducen la reutilización
  • Los creadores de acciones con efectos secundarios son difíciles de probar
  • No se puede optimizar ni procesar por lotes

El estado implícito está oculto

En la aplicación de contador, incrementAsync crea un tiempo de espera y solo cuando se completa se actualiza el estado de la aplicación. Si quisiera, por ejemplo, mostrar un indicador visual de que una operación de incremento está en progreso, la vista no puede inferir esto del estado de la aplicación. Este estado está implícito y oculto.

Aunque a veces elegante, no estoy tan seguro de la propuesta de utilizar generadores como orquestadores de creadores de acciones, ya que el estado implícito está oculto y no se puede serializar fácilmente.

Usando redux-thunk o similar, podría enviar múltiples mensajes al reductor informándole cuando la operación de incremento ha comenzado y cuando se ha completado, pero esto crea un conjunto diferente de problemas.

Rebobinar el estado hasta un punto en el que la operación de incremento se marque como en progreso después de que el efecto se haya completado no regenerará realmente el efecto secundario y, por lo tanto, permanecerá en progreso indefinidamente.

Su propuesta parece resolver este problema. Dado que los efectos secundarios se crean a partir del estado, la intención debe expresarse con el estado resultante de una forma u otra, por lo tanto, si uno revierte el almacenamiento a un estado anterior donde se inició la acción, las reacciones iniciarán el efecto nuevamente en lugar de dejar el estado en el limbo.

Duplicación de lógica empresarial

Es natural que las acciones generen un efecto secundario solo cuando la aplicación se encuentra en un estado específico. En redux, si el creador de una acción requiere un estado, debe ser una función simple y pura o estar provista explícitamente del estado.

Como ejemplo simple, digamos que comenzamos con la aplicación de contador de ejemplo y queremos cambiar el contador a un color de fuente aleatorio cada vez que el contador sea múltiplo de 5.

Dado que la generación de números aleatorios es impura, el lugar sugerido para colocar este comportamiento es en el creador de acciones. Sin embargo, hay varias acciones diferentes que pueden cambiar el valor del contador, incrementar, decrementar, incrementarAsync, incrementIfOdd (que no necesita ser modificado en este caso).

El incremento y la disminución anteriormente no requerían ningún estado, ya que anteriormente se manejaban en el reductor y, por lo tanto, tenían acceso al valor actual, pero dado que un reductor no puede tener o devolver efectos secundarios (generación de números aleatorios), estas funciones ahora se convierten en creadores de acciones impuras que necesitan conocer el valor actual del contador para determinar si es necesario seleccionar un nuevo color de fuente aleatorio y esta lógica debe duplicarse en todos los creadores de acciones de contador.

Una posible alternativa a proporcionar explícitamente el estado actual sería usar redux-thunk y devolver una devolución de llamada para acceder al estado actual. Esto le permite evitar modificar todos los lugares donde se crean las acciones para proporcionar el valor actual, pero ahora requiere que el creador de la acción sepa en qué parte del estado global de la aplicación se almacena el valor y esto limita la capacidad de reutilizar el mismo contador varias veces dentro de la misma aplicación o en diferentes aplicaciones donde el estado puede estar estructurado de manera diferente.

Las suposiciones y / o dependencias del contexto reducen la reutilización

Una vez más, revisando el ejemplo de contador, notará que solo hay una instancia de contador. Si bien es trivial tener muchos contadores en la página que visualizan / actualizan el mismo estado, se requieren modificaciones adicionales al contador si desea que cada contador use un estado diferente.

Esto se ha discutido antes. ¿Cómo crear una lista genérica como reductor y potenciador de componentes?

Si el contador solo usara tipos de acción simples, sería relativamente trivial aplicar la arquitectura elm .

En este caso, el padre simplemente envuelve a los creadores de acciones o al despachador para aumentar el mensaje con cualquier contexto necesario, luego puede llamar al reductor directamente con el estado localizado.

Si bien el ejemplo de React Elmish parece impresionante, en el ejemplo faltan notablemente los dos creadores de acciones problemáticos, incrementIfOdd e incrementAsync.

incrementIfOdd depende del middleware para determinar el estado actual y, por lo tanto, necesita saber su ubicación dentro del estado de la aplicación.

incrementAsync finalmente envía directamente una acción de incremento que no está expuesta al componente principal y, por lo tanto, no se puede envolver con contexto adicional.

Si bien su propuesta no aborda directamente este problema, si incrementAsync se implementó como una acción simple que cambió el estado a {counter: 0, incrementAfterDelay: 1000} para activar el efecto secundario en un oyente de la tienda, entonces incrementAsync se convierte en un mensaje simple. incrementIfOdd es puro, por lo que podría implementarse en el reductor o podría proporcionarse el estado explícitamente ... Por lo tanto, es posible aplicar la arquitectura elm nuevamente si se desea.

Los creadores de acciones con efectos secundarios son difíciles de probar

Creo que es bastante obvio que los efectos secundarios serán más difíciles de probar. Una vez que sus efectos secundarios están condicionados al estado actual y la lógica empresarial, no solo se vuelven más difíciles sino también más importantes de probar.

Su propuesta le permite a uno crear fácilmente una prueba de que una transición de estado creará un estado que contiene las reacciones deseadas sin ejecutar ninguna de ellas. Las reacciones también son más fáciles de probar, ya que no necesitan ningún estado condicional o lógica empresarial.

No se puede optimizar ni procesar por lotes

Una publicación de blog reciente de John A De Goes discutió el problema con los tipos de datos opacos como IO o Task para expresar efectos. Al usar descripciones declarativas de efectos secundarios en lugar de tipos opacos, tiene el potencial de optimizar o combinar efectos más adelante.

Una arquitectura moderna para FP

Los thunks, promesas y generadores son opacos y, por lo tanto, las optimizaciones como el procesamiento por lotes y / o la supresión de llamadas api duplicadas deben manejarse explícitamente con funciones similares a fetchPostsIfNeeded .

Su propuesta elimina fetchPostsIfNeeded y parece totalmente factible implementar una función reactions que podría optimizar múltiples solicitudes y / o usar diferentes conjuntos de apis según sea necesario cuando se han solicitado más o menos datos.

Mi implementación

Recientemente creé una bifurcación de redux que permite crear reductores que devuelvan solo el nuevo estado como lo hacen ahora o un objeto especial con efectos que contiene el nuevo estado y una descripción de los efectos que se ejecutarán después del reductor.

No estaba seguro de cómo hacer esto sin bifurcar redux, ya que era necesario modificar compose y combineReducers para levantar los efectos sobre los reductores existentes a fin de mantener la compatibilidad con el código de reductor existente.

Sin embargo, su propuesta es bastante buena en el sentido de que no requiere modificar redux. Además, creo que su solución hace un mejor trabajo al resolver el problema del estado oculto implícito y probablemente sea más fácil de combinar u optimizar las reacciones resultantes.

Resumen

Al igual que React es "solo la interfaz de usuario", y no es muy prescriptivo cómo uno realmente almacena o actualiza el estado de la aplicación, Redux es principalmente "solo la tienda" y no es muy prescriptivo sobre cómo uno maneja los efectos secundarios.

Nunca culpo a nadie por ser pragmático y hacer las cosas, y los muchos contribuyentes a redux y el middleware han permitido a las personas crear cosas realmente interesantes más rápido y mejor de lo que era posible anteriormente. Solo gracias a sus contribuciones hemos llegado tan lejos. Un agradecimiento especial a todos los que han contribuido.

Redux es asombroso. Estos no son problemas necesarios con Redux en sí, sino, con suerte, críticas constructivas de los patrones arquitectónicos actuales y las motivaciones y las ventajas potenciales de ejecutar los efectos después de las modificaciones estatales en lugar de antes.

Estoy tratando de entender la diferencia entre este enfoque y redux-saga. Me interesa la afirmación de que oculta implícitamente el estado en los generadores, porque al principio, parece que está haciendo lo mismo. Pero supongo que eso podría depender de cómo se implemente io.take . Si la saga solo procesará una acción si actualmente está bloqueada en ese yield , entonces definitivamente veo lo que quieres decir. Pero si redux-saga pone en cola acciones de manera que io.take devolverá acciones pasadas, parece que está haciendo lo mismo. De cualquier manera, tienes algo de lógica que puede dispatch acciones de forma asincrónica, activadas al escuchar el flujo de acciones.

Sin embargo, es un concepto interesante. Conceptualizando Redux como un flujo de acción, a partir del cual se desencadenan transiciones de estado y efectos. Me parece que es una visión alternativa a considerarlo únicamente un procesador de estado.

En el modelo de abastecimiento de eventos, creo que se reduce a si las acciones de Redux son "comandos" (solicitudes contingentes para realizar una acción) o "eventos" (transiciones atómicas de estado, reflejadas en una vista plana). Supongo que tenemos una herramienta que es lo suficientemente flexible como para pensar en cualquier forma.

Yo también estoy un poco insatisfecho con el status quo de los "creadores de acciones inteligentes", pero lo he abordado de una manera diferente, en la que Redux es más la tienda de eventos, donde las acciones son uno de los muchos efectos posibles que podría ser activado por alguna capa de "controlador" externo. Factoricé el código que siguió este enfoque en react-redux-controller , aunque tengo una idea a medias en mente sobre una forma potencialmente más liviana de lograr esto. Sin embargo, requeriría que react-redux tuviera un gancho que no tiene actualmente, y algunas travesuras de envoltura de la tienda que no he resuelto del todo.

Tienda de travesuras descritas https://github.com/rackt/redux/issues/1200

Estoy tratando de entender la diferencia entre este enfoque y redux-saga.

No vi redux-saga hasta después de que se me ocurrió mi enfoque, pero definitivamente hay algunas similitudes. Pero sigo teniendo algunas diferencias:

  1. Mi enfoque no tiene acceso al flujo de acciones, solo al estado. redux-saga puede iniciar el proceso simplemente porque hubo una acción. Mi enfoque requiere que un reductor haga un cambio en el estado que activa la función de reacción para solicitar la acción.
  2. Mi enfoque requiere que todos los estados existan en el estado de redux. Redux-saga tiene el estado adicional que vive en el generador de saga (en qué línea está, los valores de las variables locales).
  3. Mi enfoque aísla la parte asincrónica. La lógica real de la reacción se puede probar sin tener que lidiar con la funcionalidad asincrónica. La saga los une.
  4. La saga reúne diferentes piezas de la misma lógica. Mi enfoque te obliga a dividir una saga en partes que pertenecen a la implementación del reductor, reacciones y tipo de reacción.

Básicamente, mi enfoque enfatiza las funciones puras y mantener todo en el estado redux. El enfoque de redux-saga enfatiza ser más expresivo. Creo que hay pros y contras, pero me gusta más el mío. Pero soy parcial.

Eso suena realmente prometedor. Creo que sería más convincente ver un ejemplo que separa la maquinaria de reacción de la lógica del dominio.

Su propuesta elimina fetchPostsIfNeeded y parece totalmente factible implementar una función de reacciones que podría optimizar múltiples solicitudes y / o usar diferentes conjuntos de apis según sea necesario cuando se han solicitado más o menos datos.

Tal como está, realmente no se puede hacer eso en la función de reacciones. La lógica allí necesitaría saber qué acciones ya están iniciadas (no podemos agregar nada más en ellas), pero la función de reacciones no tiene la información. La maquinaria de reacciones que consume la función de reacciones () ciertamente podría hacer esas cosas.

Creo que sería más convincente ver un ejemplo que separa la maquinaria de reacción de la lógica del dominio.

Supongo que te refieres a la forma en que la función doReactions () maneja el inicio / detención de XMLHttpRequest. He estado explorando diferentes formas de hacerlo. El problema es que es difícil encontrar una forma genérica de detectar si dos reacciones son en realidad la misma reacción. IsEqual de Lodash casi funciona, pero falla para los cierres.

Supongo que te refieres a la forma en que la función doReactions () maneja el inicio / detención de XMLHttpRequest.

No, solo quiero decir que en su ejemplo, toda la configuración para establecer el concepto de reacción se mezcla con la lógica de dominio de qué datos se están recuperando, así como con los detalles de cómo se obtienen esos datos. Me parece que los aspectos genéricos deberían tenerse en cuenta en algo que esté menos acoplado a los detalles específicos del ejemplo.

No, solo quiero decir que en su ejemplo, toda la configuración para establecer el concepto de reacción se mezcla con la lógica de dominio de qué datos se están recuperando, así como con los detalles de cómo se obtienen esos datos. Me parece que los aspectos genéricos deberían tenerse en cuenta en algo que esté menos acoplado a los detalles específicos del ejemplo.

Hmm ... Creo que no queremos decir lo mismo por lógica de dominio.

A mi modo de ver, la función reacciones () encapsula la lógica del dominio y está separada de la función doReactions () que maneja la lógica de cómo se aplican las reacciones. Pero parece que quieres decir algo diferente ...

Tal como está, realmente no se puede hacer eso en la función de reacciones. La lógica allí necesitaría saber qué acciones ya están iniciadas (no podemos agregar nada más en ellas), pero la función de reacciones no tiene la información. La maquinaria de reacciones que consume la función de reacciones () ciertamente podría hacer esas cosas.

Principalmente quise decir que si un solo evento desencadenaba un cambio de estado en el que varios componentes solicitaban la misma información, entonces podría optimizarlos. Sin embargo, tiene razón en que no es suficiente en sí mismo para determinar si un efecto secundario de un cambio de estado anterior aún está pendiente y, por lo tanto, la solicitud adicional es innecesaria.

Inicialmente pensé que tal vez uno podría mantener todos los estados dentro del estado de la aplicación, pero cuando comencé a pensar en el problema reciente del isOn debería almacenarse en el estado de la aplicación, el interval real isOn debería estar en el estado de la aplicación, pero no está solo no es un estado suficiente en este caso.

Principalmente quise decir que si un solo evento desencadenaba un cambio de estado en el que varios componentes solicitaban la misma información, entonces podría optimizarlos. Sin embargo, tiene razón en que no es suficiente en sí mismo para determinar si un efecto secundario de un cambio de estado anterior aún está pendiente y, por lo tanto, la solicitud adicional es innecesaria.

Estaba pensando en fusionar o agrupar solicitudes. La eliminación de duplicados debería funcionar bien. En realidad, también debería manejar bien el caso de cambios de estado pendientes, ya que seguirán siendo devueltos desde la función de reacciones (y por lo tanto desduplicados) hasta que vuelva la respuesta del servidor.

Inicialmente pensé que tal vez uno podría mantener todos los estados dentro del estado de la aplicación, pero cuando comencé a pensar en el problema reciente del cronómetro, me di cuenta de que, si bien el hecho de que el cronómetro está encendido, debe almacenarse en el estado de la aplicación, el objeto de intervalo real asociado con este cronómetro. debe almacenarse en otro lugar. isOn debe estar en el estado de la aplicación, pero no está solo no es un estado suficiente en este caso.

De la forma en que lo pienso, las reacciones pendientes actuales son como sus componentes de reacción. Técnicamente tienen algún estado interno, pero los modelamos en función del estado actual.

Hmm ... Creo que no queremos decir lo mismo por lógica de dominio.

A mi modo de ver, la función reacciones () encapsula la lógica del dominio y está separada de la función doReactions () que maneja la lógica de cómo se aplican las reacciones. Pero parece que quieres decir algo diferente ...

En cierto modo, tomé todo el módulo /reactions/index como un todo, pero sí, estaría de acuerdo en que la función reactions es puramente lógica de dominio. Pero en lugar de estar en un módulo específico de dominio, está empaquetado junto con el texto estándar de doReactions . Eso no es para cambiar su metodología, solo hace que sea más difícil entender de un vistazo la separación entre el código de la biblioteca y el código de la aplicación.

Entonces, doReactions sí mismo me parece que está estrechamente acoplado a un método particular del acto particular que obtiene datos de una API. Supongo que una biblioteca de reacciones desarrollada podría ser una forma de registrar controladores para diferentes tipos de efectos.

Eso no es para cambiar tu método; Encuentro este enfoque realmente atractivo.

No estoy seguro de que el estado del componente de reacción sea una buena analogía, ya que la mayoría del estado de reacción
debería estar en el estado de la aplicación, pero aparentemente es necesario que haya alguna forma
para mantener el estado entre los eventos de despacho que no se pueden colocar en el
Tienda.

Creo que este tipo de estado es a lo que @yelouafi se refiere como estado de control y
tal vez las sagas sean una buena forma de modelar el estado no serializable del
sistema como un observador / actor independiente.

Creo que estaría menos preocupado por el estado de la saga oculta si las sagas
respondió solo a eventos generados por la aplicación (reacciones) en lugar de a los usuarios
eventos iniciados (acciones) ya que esto permitiría al reductor de aplicaciones usar el
estado actual y cualquier lógica de negocio condicional para determinar si el
La aplicación debe permitir el efecto secundario deseado sin duplicar
lógica de negocios.
El lunes 4 de enero de 2016 a las 5:56 p. M. Winston Ewert [email protected]
escribió:

Sobre todo quise decir que si un solo evento desencadenaba un cambio de estado en el que
varios componentes solicitaron la misma información, entonces podría ser capaz de
optimizarlos. Sin embargo, tiene razón en que no es suficiente en sí mismo para
determinar si un efecto secundario de un cambio de estado anterior aún está pendiente
y por lo tanto la solicitud adicional es innecesaria.

Estaba pensando en fusionar o agrupar solicitudes. Eliminando duplicados
debería funcionar bien. En realidad, debería manejar el caso de estado pendiente
los cambios también están bien, ya que todavía se devolverán desde el
Las reacciones funcionan (y por lo tanto se eliminan de la duplicación) hasta que la respuesta del servidor
Vuelve.

Inicialmente estaba pensando que tal vez uno podría mantener todo el estado dentro de la aplicación.
estado, pero cuando comencé a pensar en el problema reciente del cronómetro,
Se dio cuenta de que, si bien el hecho de que el cronómetro esté encendido debe almacenarse en
la aplicación indica el objeto de intervalo real asociado con este
El cronómetro debe almacenarse en otro lugar. isOn debería estar en la aplicación
estado pero no está solo no es estado suficiente en este caso.

De la forma en que lo pienso, las reacciones pendientes actuales son como tu
reaccionar componentes. Técnicamente tienen algún estado interno, pero modelamos
ellos en función del estado actual.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/rackt/redux/issues/1182#issuecomment -168858051.

Eso no es para cambiar su metodología, solo hace que sea más difícil entender de un vistazo la separación entre el código de la biblioteca y el código de la aplicación.

Eso es totalmente justo.

Entonces, doReactions en sí me parece estar estrechamente acoplado a un método particular del acto particular que obtiene datos de una API. Supongo que una biblioteca de reacciones desarrollada podría ser una forma de registrar controladores para diferentes tipos de efectos.

Si. Todavía estoy tratando de encontrar la mejor manera de dividirlo. Es complicado por el problema de la verificación de la igualdad.

No estoy seguro de que el estado del componente de reacción sea una buena analogía, ya que la mayoría del estado de reacción
debería estar en el estado de la aplicación, pero aparentemente es necesario que haya alguna forma
para mantener el estado entre los eventos de despacho que no se pueden colocar en el
Tienda.

Lo siento, creo que arruiné la analogía. Mi punto no es comparar el estado de la acción externa con el estado del componente de reacción tanto como el estado del DOM. El intervalo o XMLHttpRequest son más bien como los elementos DOM que reaccionan, crean y destruyen. Simplemente le dices a react cuál debería ser el DOM actual y haz que suceda. Del mismo modo, simplemente devuelve el conjunto de reacciones externas actuales y el marco cancela o inicia la acción para hacerlo realidad.

Encuentro ese enfoque realmente interesante también. ¿Ha considerado usar múltiples doReactions , que toman diferentes asignaciones de estado? Creo que sería similar a cyclejs, donde puedes crear controladores reutilizables:

function main(action$) {
  const state$ = action$.startWith(INITIAL_STATE).scan(reducer);

  return { 
    DOM: state$.map(describeDOM),
    HTTP: state$.map(describeRequests),
    ...
  };
}

Una diferencia es que no consulta los controladores por eventos para obtener el flujo de acción ( const someEvent$ = sources.DOM.select('.class').events('click') ), sino que especifica las acciones en el receptor directamente ( <button onClick={() => dispatch(action())} /> ) como lo ha hecho para las solicitudes HTTP también.

Creo que la analogía de React funciona bastante bien. Sin embargo, no consideraría que el DOM sea el estado interno, sino la API con la que funciona, mientras que el estado interno se compone de las instancias de componentes y el dom virtual.

Aquí hay una idea para la API (usando React; HTTP también podría construirse así):

// usage
const describe = (state, dispatch) => <MyComponent state={state} dispatch={dispatch} />;
const driver = createReactDOMDriver({ container } /* opts */);
store.subscribe(() => driver.update(describe(store.getState(), store.dispatch)); 
// (could be simplified further to eg. `store.use(driver, describe)` )

// implementation
const createReactDOMDriver = ({ container }) => {
  return {
    update: (element) => ReactDOM.render(element, container),
    destroy: () => ReactDOM.unmountComponentAtNode(container),
  };
};

Haría que describe tomara getState (en lugar de una instantánea del estado) y dispatch . De esa forma, podría ser tan asincrónico como quisiera.

¿Ha considerado usar múltiples doReactions, que toman diferentes asignaciones de estado?

Lo había pensado brevemente y ahora mismo voy y vengo. Hace que sea natural tener diferentes bibliotecas de reacciones que hacen cosas diferentes, una para el DOM, una para http, una para temporizadores, una para audio web, etc. Cada una puede hacer las optimizaciones / comportamientos apropiados a su propio caso. Pero parece menos útil si tiene una aplicación que realiza un montón de acciones externas únicas.

Tendría que la descripción tome getState (en lugar de una instantánea del estado) y envíe. De esa forma, podría ser tan asincrónico como quisiera.

Yo no lo haría. En mi opinión, queremos restringir el async siempre que sea posible, no proporcionar formas adicionales de usarlo. Cualquier cosa para la que desee llamar a getState () debe hacerse en la función reductor o reacciones. (Pero esa es mi mentalidad purista, y quizás exista un caso pragmático para no seguirla).

Punto justo. No he pensado bien en el mapeo entre tu idea y el ejemplo de @taurose . Asumí apresuradamente que describe era la función reactions , pero puede que eso no sea cierto.

Pero sí, estoy de acuerdo en que limitar async es ideal, porque si entiendo el impulso de su idea, queremos que las continuaciones sean puras y mapeen 1: 1 con aspectos específicos en el estado, como la presencia de un miembro de la matriz que describe la intención que un efecto determinado está en curso. De esa manera, realmente no importa si se ejecutan varias veces, y no hay ningún aspecto oculto de un proceso que se detenga en algún lugar en medio del flujo del que otros procesos puedan depender implícitamente.

Tendría que la descripción tome getState (en lugar de una instantánea del estado) y envíe. De esa forma, podría ser tan asincrónico como quisiera.

describe recibe llamadas en cada cambio de estado, por lo que no veo la necesidad de eso. No significa que no pueda realizar una sincronización. Considere los componentes de reacción: no llamaría getState dentro de sus métodos de renderizado o controladores de eventos para obtener el estado actual, sino leerlo desde los accesorios.

Pero tiene razón en que no puede (no debería) hacer nada asincrónico por sí mismo; debe dejar eso al controlador y simplemente pasarle algún estado mapeado y / o devoluciones de llamada.

se suponía que describir era la función de reacciones, pero eso puede no ser cierto.

Por lo que puedo decir, es más o menos lo mismo. Una diferencia sería que reactions no obtiene dispatch . Entonces, mientras describe devuelve devoluciones de llamada que crean y envían acciones, reactions devuelve creadores de acciones.

@winstonewert es un hilo largo y no tengo tiempo para leer en este momento o revisar tu código, pero tal vez @yelouafi pueda responderte.

El proyecto redux-saga se originó a partir de largas discusiones aquí.

También estoy usando el concepto de saga durante más de un año en una aplicación de producción, y la implementación es menos expresiva pero no se basa en generadores. Aquí hay algunos pseudo ejemplos que di del concepto de redux:

La implementación aquí está lejos de ser perfecta, pero solo da una idea.

@yelouafi es consciente de los problemas inherentes al uso de generadores que ocultan el estado fuera de redux, y que es complicado comenzar una saga en un backend y transmitir ese estado oculto al frontend para aplicaciones universales (¿si es realmente necesario?)

La saga de redux es redux-thunk como Free es para IO mónada. Los efectos son declarativos y no se ejecutan en este momento, se pueden introspectar y se ejecutan en un intérprete (que puede personalizar en el futuro)

Entiendo su punto sobre el estado oculto dentro de los generadores. Pero, ¿es realmente la tienda Redux la verdadera fuente de verdad de una aplicación Redux? No lo creo. Redux registra las acciones y las reproduce. Siempre puede reproducir estas acciones para recrear la tienda. La tienda redux es como una vista de consulta CQRS del registro de eventos. No significa que tenga que ser la única proyección de ese registro de eventos. Puede proyectar el mismo registro de eventos en diferentes vistas de consulta y escucharlos en sagas que pueden administrar su estado con generadores, objetos mutables globales o reductores, sea cual sea la tecnología.

En mi opinión, crear el concepto de saga con reductor no es una mala idea conceptualmente, y estoy de acuerdo con usted que es una decisión comercial.
Personalmente después de más de 1 año de usar sagas en producción no recuerdo ningún caso de uso donde hubiera sido útil poder tomar una instantánea del estado de una saga y restaurarla más tarde, por lo que prefiero la expresividad de los generadores aunque pierda esta. característica.

Espero que nada de lo que digo se haya convertido en un ataque a la saga redux. Solo estaba hablando de cómo se diferenciaba del enfoque que se me ocurrió.

Entiendo su punto sobre el estado oculto dentro de los generadores. Pero, ¿es realmente la tienda Redux la verdadera fuente de verdad de una aplicación Redux? No lo creo. Redux registra las acciones y las reproduce. Siempre puede reproducir estas acciones para recrear la tienda. La tienda redux es como una vista de consulta CQRS del registro de eventos. No significa que tenga que ser la única proyección de ese registro de eventos. Puede proyectar el mismo registro de eventos en diferentes vistas de consulta y escucharlos en sagas que pueden administrar su estado con generadores, objetos mutables globales o reductores, sea cual sea la tecnología.

Realmente no entiendo tu punto aquí. ¿Parece estar argumentando que una saga es una proyección del registro de eventos? Pero no lo es. Si repito las acciones, no llegaré al mismo lugar en las sagas si la saga depende de eventos asincrónicos. Me parece ineludible que las sagas produzcan un estado que no está en la tienda de estado de redux ni una proyección del registro de eventos.

Por lo que puedo decir, es más o menos lo mismo. Una diferencia sería que las reacciones no son rápidas. Entonces, mientras describe devuelve devoluciones de llamada que crean y envían acciones, las reacciones devuelven creadores de acciones.

Acordado. En principio, react podría usar la misma interfaz, todos los controladores de eventos tomarían un creador de acciones que se enviaría cuando se disparara el evento.

Cuanto más pienso en esto, creo que podría haber mucha sinergia entre este enfoque y las sagas. Estoy completamente de acuerdo con los cuatro puntos descritos por @winstonewert. Creo que es bueno que las reacciones no puedan ver las acciones iniciadas por el usuario, ya que esto evita el estado oculto y garantiza que la lógica empresarial en los reductores no tenga que duplicarse en los creadores de acciones o las sagas. Sin embargo, me di cuenta de que los efectos secundarios a menudo crean un estado no serializable que no se puede almacenar en el almacén de reacción, intervalos, objetos dom, solicitudes http, etc. sagas, rxjs, baconjs, etc.son perfectos para este estado de control externo no serializable.

doReactions podría reemplazarse con una saga y la fuente del evento para las sagas deberían ser reacciones, no acciones.

Espero que nada de lo que estoy diciendo haya sido un ataque a la saga redux.

Para nada. He estado siguiendo la discusión pero no quería comentar sin mirar más de cerca su código.

A primera vista. Parece que solo reaccionas a los cambios de estado. Como dije, fue un vistazo rápido. Pero parece que la implementación de flujos complejos será aún más difícil que el enfoque elm (donde se toma tanto el estado como la acción). esto significa que tendrá que almacenar aún más estados de control en la tienda (donde los cambios en el estado de la aplicación por sí solos son insuficientes para inferir las reacciones relevantes)

Claro, nada puede vencer a las funciones puras. Creo que los reductores son excelentes para expresar transiciones de estado, pero se vuelven realmente extraños cuando los conviertes en máquinas de estado.

esto significa que tendrá que almacenar aún más estados de control en la tienda (donde los cambios en el estado de la aplicación por sí solos son insuficientes para inferir las reacciones relevantes)

Sí. Este me parece ser el aspecto diferenciador clave de este enfoque. Pero me pregunto si este tema podría hacerse transparente, en la práctica, si diferentes tipos de efectos se pueden envolver en diferentes "impulsores". Me imagino que será bastante fácil para las personas elegir los controladores que quieran o escribir los suyos propios para obtener efectos novedosos.

Sin embargo, me di cuenta de que los efectos secundarios a menudo crean un estado no serializable que no se puede almacenar en el almacén de reacción, intervalos, objetos dom, solicitudes http, etc. sagas, rxjs, baconjs, etc.son perfectos para este estado de control externo no serializable.

Todavía no veo lo que eres.

Creo que los reductores son excelentes para expresar transiciones de estado, pero se vuelven realmente extraños cuando los conviertes en máquinas de estado.

Estoy de acuerdo. Si está escribiendo a mano una máquina de estado compleja, tenemos un problema. (En realidad, sería genial si pudiéramos convertir un generador en un reductor).

Pero me pregunto si este tema podría hacerse transparente, en la práctica, si diferentes tipos de efectos se pueden envolver en diferentes "impulsores". Me imagino que será bastante fácil para las personas elegir los controladores que quieran o escribir los suyos propios para obtener efectos novedosos.

No estoy seguro de lo que estás pensando aquí. Puedo ver diferentes controladores haciendo diferentes cosas útiles, pero ¿eliminan el estado de control?

@winstonewert no, no estoy tomando nada como un ataque. Ni siquiera tuve tiempo de mirar realmente tu código :)

Realmente no entiendo tu punto aquí. ¿Parece estar argumentando que una saga es una proyección del registro de eventos? Pero no lo es. Si repito las acciones, no llegaré al mismo lugar en las sagas si la saga depende de eventos asincrónicos. Me parece ineludible que las sagas produzcan un estado que no está en la tienda de estado de redux ni una proyección del registro de eventos.

No, no lo soy, la tienda redux es una proyección, pero la saga es un simple oyente simple.

La saga (también llamada administrador de procesos) no es un concepto nuevo, se origina en el mundo CQRS y se ha utilizado ampliamente en sistemas backend en el pasado.

La saga no es la proyección de un registro de eventos a una estructura de datos, es una pieza de orquestación que puede escuchar lo que está sucediendo en su sistema y emitir reacciones, el resto son detalles de implementación. Generalmente, las sagas están escuchando un registro de eventos (y tal vez otras cosas externas, como el tiempo ...) y pueden producir nuevos comandos / eventos. Además, cuando reproduce eventos en sistemas backend, generalmente deshabilita los efectos secundarios activados por sagas.

Una diferencia es que en los sistemas backend, la saga es a menudo una proyección del registro de eventos: para cambiar su estado, tiene que emitir eventos y escucharlos él mismo. En redux-saga, tal como está implementado actualmente, sería más difícil reproducir el registro de eventos para restaurar el estado de la saga.

No estoy seguro de lo que estás pensando aquí. Puedo ver diferentes controladores haciendo diferentes cosas útiles, pero ¿eliminan el estado de control?

No, no eliminarlo, simplemente convertirlo en una preocupación de implementación oculta, para la mayoría de los propósitos.

Me parece que hay un consenso muy fuerte en la comunidad de Redux de que almacenar el estado del dominio en la tienda es una gran ganancia (de lo contrario, ¿por qué usaría Redux?). Algo menor es el consenso de que almacenar el estado de la interfaz de usuario es una ventaja, en lugar de tenerlo encapsulado en componentes. Luego está la idea de sincronizar el estado del navegador en la tienda, como la URL (redux-simple-router) o los datos del formulario. Pero esta parece ser la frontera final, de almacenar el estado / etapa de un proceso de larga duración en la tienda.

Lo siento si esto es una tangente, pero creo que un enfoque muy general con buena usabilidad del desarrollador debería tener las siguientes características:

  • Haga que el usuario típico no tenga que preocuparse por la forma en que se representan los efectos en la tienda. Deben interactuar con API simples, que resuman esos detalles.
  • Hágalo de modo que los efectos se puedan componer fácilmente. Debe sentirse natural hacer cosas como controlar el flujo y los efectos que dependen de otros efectos. Aquí, por supuesto, es donde realmente brilla una abstracción de generador. Funciona bien con la mayoría de los controles de flujo, siendo los cierres una excepción notable. Pero es fácil ver cómo los flujos asíncronos complicados se pueden expresar en redux-saga o react-redux-controller.
  • Hágalo de modo que el estado del efecto pueda revelarse fácilmente a otros consumidores de la tienda, cuando lo desee. Esto le permitiría hacer cosas como presentar el estado de un proceso de efectos múltiples al usuario.
  • Quizás esto sea obvio, pero cualquier subsistema que encapsule el estado sincroniza ese estado con Redux, enviando acciones.

Para ese segundo punto, creo que tendría que haber algo bastante similar a redux-saga. Puede acercarse bastante a lo que tengo en mente con sus envoltorios call . Pero una saga tendría que ser "de reenvío rápido", en cierto sentido, para permitirte deserializarla en un estado intermedio.

Todo esto es una tarea difícil, pero en términos prácticos, creo que si se pueden obtener grandes ganancias al tener un registro de acción central y serializable, rastreando el estado de una aplicación completa a un nivel muy granular, este sería el forma de aprovecharlo. Y creo que de hecho puede haber grandes triunfos ahí fuera. Me estoy imaginando una forma mucho más sencilla de instrumentar aplicaciones con análisis de usuario y rendimiento. Me estoy imaginando una capacidad de prueba realmente asombrosa, donde diferentes subsistemas están acoplados solo a través del estado.

Es posible que me haya salido del rumbo ahora, así que lo dejaré así :)

@acjay creo que estamos de acuerdo contigo en estos puntos, el problema es encontrar esta implementación que resuelva todos esos correctamente :)

Pero parece difícil tener una api expresiva con generadores y la posibilidad de viajar en el tiempo y de instantáneas / restaurar el estado ... Tal vez sería posible memorizar la ejecución del efecto para que podamos restaurar fácilmente el estado de los generadores ...

No estoy seguro, pero esto podría excluir las sagas de estilo while(true) { ... } . ¿El bucle sería solo una consecuencia de la progresión del estado?

@acjay @slorber

Como expliqué en (https://github.com/yelouafi/redux-saga/issues/22#issuecomment-168872101) Viajar en el tiempo solo (es decir, sin recarga en caliente) es posible para las sagas. Todo lo que necesitas para llevar una saga a un punto específico es la secuencia de efectos producidos desde el principio hasta ese punto, así como su resultado (resolver o rechazar). Entonces solo conducirás el generador con esa secuencia

En la rama maestra real (aún no publicada en npm). Sagas apoyan el seguimiento, despachan todos los efectos producidos, así como su resultado como acciones a la tienda; también proporcionan información de jerarquía para rastrear el diagrama de flujo de control.

Ese registro de efectos se puede aprovechar para reproducir una Saga hasta un punto determinado: no es necesario realizar las llamadas a la API reales, ya que el registro ya contiene las respuestas pasadas.

En los ejemplos de repositorios, hay un ejemplo de un monitor de saga (implementado como un middleware de Redux). Escucha el registro de efectos y mantiene una estructura de árbol interna (bien construida de forma perezosa). Puede imprimir un rastro del flujo enviando una acción {type: 'LOG_EFFECT'} a la tienda

Aquí hay una captura de un registro de efectos del ejemplo asincrónico

saga-log-async

Editar: lo siento enlace de imagen fija

¡Intrigante! Y esa imagen de las herramientas de desarrollo es _increíble_.

Eso es genial :)

De hecho, ese monitor de saga es bastante bueno.

Pensando en ello, me parece que saga está resolviendo dos cuestiones. En primer lugar, maneja los efectos asincrónicos. En segundo lugar, maneja interacciones de estado complejas que de otro modo habrían requerido una desagradable máquina de estados escrita a mano en un reductor.

Mi enfoque solo aborda el primer tema. No he encontrado una necesidad para el segundo número. Probablemente todavía no he escrito suficiente código redux para encontrarlo.

Sí, pero me pregunto si hay una manera de fusionar las dos ideas. El contenedor call redux-saga es un nivel bastante simple de direccionamiento indirecto sobre un efecto, pero suponiendo que pudiera inicializar el middleware con controladores para diferentes tipos de efectos, podría representarlos como datos JSONable, desacoplados de la función que en realidad se llama. El conductor manejaría el detalle de enviar cambios de estado subyacentes a la tienda.

Eso podría suponer una gran complejidad adicional con pocos beneficios prácticos. Pero solo estoy tratando de seguir esta línea de pensamiento hasta el final.

Ok, armé más de una biblioteca y porté el ejemplo del mundo real para usarlo:

En primer lugar, tenemos la implementación de reacciones:
https://github.com/winstonewert/redux-reactions/blob/master/src/index.js
La interfaz consta de tres funciones: startReactions toma la tienda, una función de reacciones y un mapeo de los nombres a los controladores. fromEmitter y fromPromiseFactory crean controladores.

Aquí el ejemplo llama a startReactions para habilitar el sistema:
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/store/configureStore.dev.js#L28

La configuración básica de reacciones está aquí:
https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/index.js.
La función de reacciones en realidad solo itera a través de los componentes que reaccionan instancias del enrutador en busca de aquellos con una función de reacciones () para descubrir las reacciones necesarias reales para esa página.

La implementación del tipo de reacción de github api está aquí: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js. Esto es principalmente copiar / pegar del middleware usado en el ejemplo original. El punto crítico está aquí: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/api.js#L79 , donde usa fromPromiseFactory para crear el controlador a partir de una función que devuelve promesas.

Vea la función de reacciones específicas de un componente aquí: https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/containers/RepoPage.js#L80.

Los creadores de reacciones y la lógica común se encuentran en https://github.com/winstonewert/redux-reactions/blob/master/examples/real-world/reactions/data.js

¡Hola amigos! ¡Raise acaba de publicar un potenciador de tienda que también te permite usar un sistema de efectos similar a la arquitectura Elm! Espero que podamos aprender y mejorar todos estos enfoques en el futuro para satisfacer todas las necesidades de la comunidad: sonrisa:

https://github.com/raisemarketplace/redux-loop

Cualquiera que esté interesado en la discusión puede querer ver más discusiones sobre mi idea aquí: https://github.com/winstonewert/redux-reactions/issues/7

También puede ver una rama aquí, donde modifico la aplicación del contador para que sea más olmo usando mi patrón:
https://github.com/winstonewert/redux-reactions/tree/elmish/examples/counter

También descubrí que estoy reinventando el enfoque que se usa aquí: https://github.com/ccorcos/elmish

Hola @yelouafi , ¿podrías volver a publicar el enlace a la idea del monitor de saga? ¡Esas son cosas realmente geniales! El vínculo parece estar muerto (404). ¡Me encantaría ver más!

Nueva discusión relevante: https://github.com/reactjs/redux/issues/1528

(Creo que esto está relacionado. Lo siento si ese es un lugar equivocado)

¿Podríamos tratar todos los efectos de la misma manera que la renderización DOM?

  1. jQuery es un controlador DOM con una interfaz imperativa. React es un controlador DOM con interfaz declarativa. Entonces, en lugar de ordenar: "deshabilite ese botón", declaramos: "necesitamos ese botón deshabilitado" y el controlador decide qué manipulaciones DOM hacer. En lugar de ordenar: " GET \product\123 ", declaramos: "necesitamos esos datos" y el conductor decide qué solicitudes enviar / cancelar.
  2. Usamos componentes de React como controlador de API a DOM. Usémoslos también para interactuar con otros controladores.

    • <button ...> - construimos nuestra capa de vista a partir de componentes de React "normales"

    • <Map ...> - usamos componentes "envoltorios" para convertir la interfaz imperativa de alguna biblioteca en una declarativa. Los usamos de la misma manera que los componentes "normales", pero internamente son controladores.

    • <Chart ...> - esto podría ser cualquiera de los anteriores dependiendo de la implementación. Entonces, la línea entre los componentes "normales" y los controladores ya está borrosa.

    • <Http url={'/product/'+props.selectedProductId} onSuccess={props.PRODUCT_LOADED} /> (o "inteligente" <Service...> ): construimos nuestra capa de servicio a partir de componentes de controlador (sin interfaz de usuario)

Tanto las capas de Vista como las de Servicio se describen a través de los componentes de React. Y nuestros componentes de nivel superior (conectados) los pegan.
De esta manera, nuestros reductores permanecen puros y no introducimos ningún medio nuevo para manejar los efectos.

No estoy seguro de cómo encajan aquí new Date o Math.random .

¿Es siempre posible convertir una API imperativa en declarativa?
¿Crees que esta es una vista viable?

Gracias

Dado que tenemos sagas y otras herramientas increíbles para acciones asíncronas, creo que podemos cerrar esto de manera segura ahora. Consulte el n. ° 1528 para conocer algunas direcciones nuevas e interesantes (más allá de lo asincrónico también).

¿Fue útil esta página
0 / 5 - 0 calificaciones