¿Deben las tiendas de flujo o acciones (o ambas) tocar servicios externos?

122

Si las tiendas mantienen su propio estado y tienen la capacidad de llamar a la red y a los servicios de almacenamiento de datos al hacerlo ... en cuyo caso las acciones son solo mensajes tontos,

-O-

... ¿las tiendas deben ser tontas receptoras de datos inmutables de las acciones (y las acciones son las que obtienen / envían datos entre fuentes externas? La tienda en este caso actuaría como modelos de vista y podría agregar / filtrar sus datos antes de establecer su propia base de estado en los datos inmutables que fueron alimentados por la acción.

Me parece que debería ser uno u otro (en lugar de una mezcla de ambos). Si es así, ¿por qué uno es preferido / recomendado sobre el otro?

plaxdan
fuente
2
Esta publicación podría ayudar code-experience.com/…
Markus-ipse
Para aquellos que evalúan las diversas implementaciones del patrón de flujo, les recomiendo que echen un vistazo a Redux github.com/rackt/redux Las tiendas se implementan como funciones puras que toman el estado actual y emiten una nueva versión de ese estado. Como son funciones puras, la cuestión de si pueden o no llamar a los servicios de red y almacenamiento se les quita las manos: no pueden.
plaxdan

Respuestas:

151

He visto que el patrón de flujo se implementa en ambos sentidos, y después de haberlo hecho yo mismo (inicialmente siguiendo el enfoque anterior), creo que las tiendas deberían ser destinatarios tontos de datos de las acciones, y que el procesamiento asincrónico de las escrituras debería vivir en el creadores de acción. (Las lecturas asíncronas se pueden manejar de manera diferente ). En mi experiencia, esto tiene algunos beneficios, en orden de importancia:

  1. Sus tiendas se vuelven completamente sincrónicas. Esto hace que la lógica de su tienda sea mucho más fácil de seguir y muy fácil de probar: simplemente cree una instancia de una tienda con un estado determinado, envíele una acción y verifique si el estado cambió como se esperaba. Además, uno de los conceptos centrales de flujo es evitar los despachos en cascada y evitar múltiples despachos a la vez; Esto es muy difícil de hacer cuando sus tiendas realizan un procesamiento asincrónico.

  2. Todos los despachos de acción suceden de los creadores de la acción. Si maneja operaciones asincrónicas en sus tiendas y desea mantener sincronizados los manejadores de acciones de sus tiendas (y debe hacerlo para obtener las garantías de despacho único de flujo), sus tiendas necesitarán disparar acciones de ÉXITO y FALLO adicionales en respuesta a asincrónicas Procesando. En cambio, poner estos despachos en los creadores de acciones ayuda a separar los trabajos de los creadores de acciones y las tiendas; Además, no tiene que buscar en la lógica de su tienda para averiguar desde dónde se envían las acciones. Una acción asincrónica típica en este caso podría verse más o menos así (cambie la sintaxis de las dispatchllamadas según el sabor del flujo que esté usando):

    someActionCreator: function(userId) {
      // Dispatch an action now so that stores that want
      // to optimistically update their state can do so.
      dispatch("SOME_ACTION", {userId: userId});
    
      // This example uses promises, but you can use Node-style
      // callbacks or whatever you want for error handling.
      SomeDataAccessLayer.doSomething(userId)
      .then(function(newData) {
        // Stores that optimistically updated may not do anything
        // with a "SUCCESS" action, but you might e.g. stop showing
        // a loading indicator, etc.
        dispatch("SOME_ACTION_SUCCESS", {userId: userId, newData: newData});
      }, function(error) {
        // Stores can roll back by watching for the error case.
        dispatch("SOME_ACTION_FAIL", {userId: userId, error: error});
      });
    }

    La lógica que de otro modo podría duplicarse en varias acciones debería extraerse en un módulo separado; en este ejemplo, ese módulo sería SomeDataAccessLayer, que se encarga de hacer la solicitud real de Ajax.

  3. Necesitas menos creadores de acción. Esto es menos importante, pero es bueno tenerlo. Como se menciona en el n. ° 2, si sus tiendas tienen manejo de despacho de acciones síncronas (y deberían hacerlo), necesitará disparar acciones adicionales para manejar los resultados de las operaciones asíncronas. Hacer los despachos en los creadores de acciones significa que un solo creador de acciones puede despachar los tres tipos de acciones manejando el resultado del acceso asíncrono a los datos.

Michelle Tilley
fuente
15
Creo que lo que origina la llamada API web (creador de acción frente a tienda) es menos importante que el hecho de que la devolución de llamada de éxito / error debería crear una acción. Entonces el flujo de datos es siempre: acción -> despachador -> tiendas -> vistas.
fisherwebdev
1
¿Poner la lógica de solicitud real dentro de un módulo API sería mejor / más fácil de probar? Por lo tanto, su módulo API podría devolverle una promesa desde la que envía. El creador de la acción solo despacha en función de resolver / fallar después de enviar una acción 'pendiente' inicial. La pregunta que queda es cómo el componente escucha estos 'eventos', ya que no estoy seguro de que el estado de la solicitud se asigne al estado de almacenamiento.
backdesk
@backdesk Eso es exactamente lo que hago en el ejemplo anterior: despachar una acción pendiente inicial ( "SOME_ACTION"), usar una API para hacer una solicitud ( SomeDataAccessLayer.doSomething(userId)) que devuelve una promesa, y en las dos .thenfunciones, despachar acciones adicionales. El estado de solicitud puede (más o menos) correlacionarse con el estado de almacenamiento si la aplicación necesita saber sobre el estado del estado. Cómo este mapa depende de la aplicación (por ejemplo, tal vez cada comentario tiene un estado de error individual, a la Facebook, o tal vez hay un componente de error global)
Michelle Tilley
@MichelleTilley "uno de los conceptos centrales en el flujo es evitar los despachos en cascada y evitar múltiples despachos a la vez; esto es muy difícil de hacer cuando las tiendas realizan un procesamiento asincrónico". Ese es un punto clave para mí. Bien dicho.
51

Tuiteé esta pregunta a los desarrolladores en Facebook y la respuesta que recibí de Bill Fisher fue:

Al responder a la interacción de un usuario con la interfaz de usuario, haría la llamada asincrónica en los métodos de creación de acciones.

Pero cuando tiene un ticker u otro controlador no humano, una llamada de la tienda funciona mejor.

Lo importante es crear una acción en la devolución de llamada de error / éxito para que los datos siempre se originen con acciones

Markus-ipse
fuente
Si bien esto tiene sentido, ¿alguna idea de por qué a call from store works better when action triggers from non-human driver ?
SharpCoder
@SharpCoder Supongo que si tiene un ticker en vivo o algo similar, realmente no necesita disparar una acción y cuando lo hace desde la tienda, probablemente tenga que escribir menos código, ya que la tienda puede acceder instantáneamente al estado y emitir un cambio.
Florian Wendelborn
8

Las tiendas deben hacer todo, incluida la obtención de datos y la señalización a los componentes de que los datos de la tienda se han actualizado. ¿Por qué? Porque las acciones pueden ser ligeras, desechables y reemplazables sin influir en el comportamiento importante. Todos los comportamientos y funcionalidades importantes suceden en la tienda. Esto también evita la duplicación de comportamientos que de otro modo se copiarían en dos acciones muy similares pero diferentes. Las tiendas son su única fuente de (manejo de) la verdad.

En cada implementación de Flux que he visto, las acciones son básicamente cadenas de eventos convertidas en objetos, como tradicionalmente tendría un evento llamado "ancla: cliqueado", pero en Flux se definiría como AnchorActions.Clicked. Incluso son tan "tontos" que la mayoría de las implementaciones tienen objetos Dispatcher separados para enviar los eventos a las tiendas que están escuchando.

Personalmente, me gusta la implementación de Reflux de Flux donde no hay objetos Dispatcher separados y los objetos Action hacen el despacho ellos mismos.


editar: Flux de Facebook en realidad busca a los "creadores de acciones" para que usen acciones inteligentes. También preparan la carga útil utilizando las tiendas:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/ChatMessageActionCreators.js#L27 (líneas 27 y 28)

La devolución de llamada al finalizar desencadenaría una nueva acción esta vez con los datos obtenidos como carga útil:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51

Así que supongo que esa es la mejor solución.

Rygu
fuente
¿Qué es esta implementación de Reflujo? No he oído hablar de eso. Tu respuesta es interesante. ¿Quiere decir que la implementación de su tienda debe tener la lógica para hacer llamadas a la API, etc.? Pensé que las tiendas deberían recibir datos y actualizar sus valores. Filtran en acciones específicas y actualizan algunos atributos de sus tiendas.
Jeremy D
El reflujo es una ligera variación del flujo de Facebook: github.com/spoike/refluxjs Stores administra todo el dominio "Modelo" de su aplicación, frente a Actions / Dispatchers que solo unen y unen cosas.
Rygu
1
Así que he estado pensando en esto un poco más y he (casi) respondido a mi propia pregunta. Lo habría agregado como respuesta aquí (para que otros voten) pero aparentemente soy demasiado pobre en karma en stackoverflow para poder publicar una respuesta todavía. Así que aquí hay un enlace: groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan
Gracias por el enlace del grupo de google, parece realmente informativo. También soy más fanático de todo lo que pasa por el despachador, y una lógica realmente simple en la tienda, básicamente, actualizar sus datos, eso es todo. @ Rygu Comprobaré el reflujo.
Jeremy D
Edité mi respuesta con una vista alternativa. Parece que ambas soluciones son posibles. Seguramente elegiría la solución de Facebook sobre otras.
Rygu
3

Proporcionaré un argumento a favor de las acciones "tontas".

Al asignar la responsabilidad de recopilar datos de vistas en sus Acciones, las une a los requisitos de datos de sus vistas.

Por el contrario, las Acciones genéricas, que describen de manera declarativa la intención del usuario, o alguna transición de estado en su aplicación, permiten que cualquier Tienda que responda a esa Acción transforme la intención, en un estado diseñado específicamente para las vistas suscritas.

Esto se presta a tiendas más numerosas, pero más pequeñas y más especializadas. Defiendo este estilo porque

  • esto le da más flexibilidad en cómo las vistas consumen los datos de la Tienda
  • Las tiendas "inteligentes", especializadas para las vistas que las consumen, serán más pequeñas y menos acopladas para aplicaciones complejas que las acciones "inteligentes", de las cuales dependen potencialmente muchas vistas.

El propósito de una tienda es proporcionar datos a las vistas. El nombre "Acción" me sugiere que su propósito es describir un cambio en mi Aplicación.

Suponga que tiene que agregar un widget a una vista de Panel existente, que muestra algunos datos agregados nuevos y sofisticados que su equipo de back-end acaba de implementar.

Con las acciones "inteligentes", es posible que deba cambiar su acción "Actualizar panel" para consumir la nueva API. Sin embargo, "Actualizar el tablero" en sentido abstracto no ha cambiado. Los requisitos de datos de sus vistas es lo que ha cambiado.

Con las acciones "tontas", puede agregar una nueva Tienda para que el nuevo widget la consuma y configurarla de modo que cuando reciba el tipo de Acción "actualizar panel", envíe una solicitud de los nuevos datos y la exponga a el nuevo widget una vez que esté listo. Tiene sentido para mí que cuando la capa de vista necesita más o diferentes datos, las cosas que cambio son las fuentes de esos datos: las tiendas.

Carlos Lalimarmo
fuente
2

La demostración de enrutador-flujo-reacción de Gaeron tiene una buena variación de utilidad del enfoque 'correcto'.

Un ActionCreator genera una promesa desde un servicio API externo y luego pasa la promesa y tres constantes de acción a una dispatchAsyncfunción en un proxy / Dispatcher extendido. dispatchAsyncsiempre enviará la primera acción, por ejemplo, 'GET_EXTERNAL_DATA' y una vez que la promesa regrese, enviará 'GET_EXTERNAL_DATA_SUCCESS' o 'GET_EXTERNAL_DATA_ERROR'.

William Myers
fuente
1

Si desea que un día tenga un entorno de desarrollo comparable al que ve en el famoso video de Bret Victor Inventing on Principle , debería usar tiendas tontas que son solo una proyección de acciones / eventos dentro de una estructura de datos, sin ningún efecto secundario. También sería útil que sus tiendas fueran realmente miembros de la misma estructura de datos inmutable global, como en Redux .

Más explicaciones aquí: https://stackoverflow.com/a/31388262/82609

Sebastien Lorber
fuente