¿Dónde se debe hacer la solicitud ajax en la aplicación Flux?

194

Estoy creando una aplicación react.js con arquitectura de flujo y estoy tratando de averiguar dónde y cuándo se debe hacer una solicitud de datos del servidor. ¿Hay algún ejemplo para esto? (¡No TODO aplicación!)

Eniz Gülek
fuente

Respuestas:

127

Soy un gran defensor de poner operaciones de escritura asíncrona en los creadores de acción y operaciones de lectura asíncrona en la tienda. El objetivo es mantener el código de modificación del estado de la tienda en controladores de acción totalmente sincrónicos; esto los hace simples de razonar y de prueba de unidad simple. Para evitar múltiples solicitudes simultáneas al mismo punto final (por ejemplo, lectura doble), moveré el procesamiento de solicitud real a un módulo separado que usa promesas para evitar las solicitudes múltiples; por ejemplo:

class MyResourceDAO {
  get(id) {
    if (!this.promises[id]) {
      this.promises[id] = new Promise((resolve, reject) => {
        // ajax handling here...
      });
    } 
    return this.promises[id];
  }
}

Si bien las lecturas en la tienda implican funciones asincrónicas, existe una advertencia importante de que las tiendas no se actualizan en los controladores asincrónicos, sino que activan una acción y solo activan una acción cuando llega la respuesta. Los controladores de esta acción terminan haciendo la modificación de estado real.

Por ejemplo, un componente podría hacer:

getInitialState() {
  return { data: myStore.getSomeData(this.props.id) };
}

La tienda tendría un método implementado, tal vez, algo como esto:

class Store {
  getSomeData(id) {
    if (!this.cache[id]) {
      MyResurceDAO.get(id).then(this.updateFromServer);
      this.cache[id] = LOADING_TOKEN;
      // LOADING_TOKEN is a unique value of some kind
      // that the component can use to know that the
      // value is not yet available.
    }

    return this.cache[id];
  }

  updateFromServer(response) {
    fluxDispatcher.dispatch({
      type: "DATA_FROM_SERVER",
      payload: {id: response.id, data: response}
    });
  }

  // this handles the "DATA_FROM_SERVER" action
  handleDataFromServer(action) {
    this.cache[action.payload.id] = action.payload.data;
    this.emit("change"); // or whatever you do to re-render your app
  }
}
Michelle Tilley
fuente
¿Has intentado poner promesas dentro de las cargas útiles de acción? Me resulta más fácil lidiar con el envío de múltiples acciones
Sebastien Lorber
@SebastienLorber La gran atracción para mí es mantener todas las actualizaciones de estado en una ruta de código síncrono, y explícitamente solo como resultado de los despachos de acción, por lo que evito la asincronía dentro de las tiendas.
Michelle Tilley
1
@ Federico Todavía no me queda claro cuál es la "mejor" solución. He estado experimentando con esta estrategia para la carga de datos combinada con contar el número de solicitudes asíncronas pendientes. Desafortunadamente, fluxse inyecta en las tiendas después de la construcción, por lo que no hay una excelente manera de obtener acciones en el método de inicialización. Puede encontrar algunas buenas ideas de las bibliotecas de flujo isomoróficas de Yahoo; Esto es algo que Fluxxor v2 debería admitir mejor. No dude en enviarme un correo electrónico si desea hablar más sobre esto.
Michelle Tilley
1
data: resultdebería ser data : data, ¿verdad? No existe result. tal vez sea mejor cambiar el nombre del parámetro de datos a la carga útil o algo así.
oligofren
2
Este viejo hilo me pareció muy útil, en particular, los comentarios de Bill Fisher y Jing Chen. Esto está muy cerca de lo que @BinaryMuse propone con la pequeña diferencia de que el despacho ocurre en el creador de la acción.
phillipwei
37

Fluxxor tiene un ejemplo de comunicación asíncrona con una API.

Esta publicación de blog habla de ello y ha aparecido en el blog de React.


Considero que esta es una pregunta muy importante y difícil que aún no se ha respondido claramente, ya que la sincronización del software de la interfaz con el back-end sigue siendo difícil.

¿Deben realizarse solicitudes de API en componentes JSX? ¿Historias? ¿Otro lugar?

Realizar solicitudes en tiendas significa que si 2 tiendas necesitan los mismos datos para una acción determinada, emitirán 2 solicitudes similares (a menos que introduzca dependencias entre tiendas, lo que realmente no me gusta )

En mi caso, he encontrado esto muy útil para poner Q promesas como carga de acciones porque:

  • No es necesario que mis acciones sean serializables (no mantengo un registro de eventos, no necesito la función de reproducción de eventos del origen de eventos)
  • Elimina la necesidad de tener diferentes acciones / eventos (solicitud disparada / solicitud completada / solicitud fallida) y tener que hacerlos coincidir usando identificadores de correlación cuando se pueden disparar solicitudes concurrentes.
  • Permite que varias tiendas escuchen la finalización de la misma solicitud, sin introducir ninguna dependencia entre las tiendas (sin embargo, ¿puede ser mejor introducir una capa de almacenamiento en caché?)

Ajax es MAL

Creo que Ajax se usará cada vez menos en el futuro cercano porque es muy difícil razonar sobre ello. ¿La direccion correcta? Considerando los dispositivos como parte del sistema distribuido, no sé dónde encontré esta idea por primera vez (tal vez en este inspirador video de Chris Granger ).

Piénsalo. Ahora, para la escalabilidad, utilizamos sistemas distribuidos con eventual consistencia como motores de almacenamiento (porque no podemos superar el teorema de CAP y, a menudo, queremos estar disponibles). Estos sistemas no se sincronizan mediante encuestas entre sí (¿excepto quizás para operaciones de consenso?) Sino que usan estructuras como CRDT y registros de eventos para que todos los miembros del sistema distribuido sean eventualmente consistentes (los miembros convergerán a los mismos datos, con el tiempo suficiente) .

Ahora piense en lo que es un dispositivo móvil o un navegador. Es solo un miembro del sistema distribuido que puede sufrir latencia de red y particionamiento de red.(es decir, está utilizando su teléfono inteligente en el metro)

Si podemos construir particiones de red y bases de datos tolerantes a la velocidad de la red (quiero decir que aún podemos realizar operaciones de escritura en un nodo aislado), probablemente podamos construir softwares frontend (móviles o de escritorio) inspirados en estos conceptos, que funcionan bien con el modo fuera de línea admitido de la caja sin las características de la aplicación indisponibilidad.

Creo que realmente deberíamos inspirarnos sobre cómo funcionan las bases de datos para diseñar nuestras aplicaciones frontend. Una cosa a tener en cuenta es que estas aplicaciones no realizan solicitudes ajax POST y PUT y GET para enviarse datos entre sí, sino que usan registros de eventos y CRDT para garantizar la coherencia eventual.

Entonces, ¿por qué no hacer eso en la interfaz? Tenga en cuenta que el backend ya se está moviendo en esa dirección, con herramientas como Kafka masivamente adoptadas por grandes jugadores. Esto de alguna manera también está relacionado con el Abastecimiento de eventos / CQRS / DDD.

Consulte estos impresionantes artículos de autores de Kafka para convencerse:

Tal vez podamos comenzar enviando comandos al servidor y recibiendo una secuencia de eventos del servidor (a través de sockets web, por ejemplo), en lugar de disparar solicitudes de Ajax.

Nunca me he sentido muy cómodo con las solicitudes de Ajax. A medida que reaccionamos, los desarrolladores tienden a ser programadores funcionales. Creo que es difícil razonar sobre los datos locales que se supone que son su "fuente de verdad" de su aplicación frontend, mientras que la verdadera fuente de verdad está realmente en la base de datos del servidor, y su fuente de verdad "local" ya puede estar desactualizada cuando lo reciba, y nunca convergerá a la fuente real de valor de verdad a menos que presione algún botón de actualización cojo ... ¿Es esto ingeniería?

Sin embargo, todavía es un poco difícil diseñar tal cosa por algunas razones obvias:

  • Su cliente móvil / navegador tiene recursos limitados y no necesariamente puede almacenar todos los datos localmente (por lo tanto, a veces requiere sondear con un contenido pesado de solicitud ajax)
  • Su cliente no debería ver todos los datos del sistema distribuido, por lo que requiere de alguna manera filtrar los eventos que recibe por razones de seguridad
Sebastien Lorber
fuente
3
¿Puede proporcionar un ejemplo del uso de promesas Q con acciones?
Matt Foxx Duncan
@MattFoxxDun no está seguro de que sea una buena idea, ya que hace que el "registro de eventos" no sea serializable y hace que la tienda se actualice de forma asíncrona en las acciones que se disparan, por lo que tiene algunos inconvenientes. Sin embargo, si está bien para su caso de uso y comprende estos inconvenientes, es bastante útil y Reducir repetitivo. Con Fluxxor probablemente puedas hacer algo comothis.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Sebastien Lorber el
Totalmente en desacuerdo sobre su argumento AJAX. De hecho, fue muy molesto leerlo. ¿Has leído tus comentarios? Piense en las tiendas, los juegos y las aplicaciones que generan mucho dinero: todos requieren API y llamadas al servidor AJAX ... mire Firebase si quiere "sin servidor" o algo por el estilo, pero AJAX está aquí para decir que espero que al menos nadie más esté de acuerdo su lógica
TheBlackBenzKid
@TheBlackBenzKid No estoy diciendo que Ajax desaparecerá totalmente en el año (y asegúrese de que sigo construyendo sitios web además de las solicitudes de ajax actualmente como CTO de una startup), pero estoy diciendo que es probable que desaparezca porque no es un protocolo lo suficientemente bueno como para manejar la consistencia eventual que más bien requiere transmisión y no sondeo, y la consistencia eventual es lo que permite que las aplicaciones funcionen fuera de línea de manera confiable (sí, puede hackear algo con el almacenamiento local usted mismo, pero tendrá capacidades fuera de línea limitadas, o tu aplicación es muy simple). El problema no es el almacenamiento en caché, está invalidando ese caché.
Sebastien Lorber
@TheBlackBenzKid Los modelos detrás de Firebase, Meteor, etc. no son lo suficientemente buenos. ¿Sabes cómo manejan estos sistemas las escrituras concurrentes? ¿Last-write-win en lugar de estrategias de fusión / consistencia causal? ¿Puede darse el lujo de anular el trabajo de su colega en una aplicación cuando ambos trabajan en conexiones poco confiables? También tenga en cuenta que estos sistemas tienden a acoplar mucho las modelaciones locales y de servidor. ¿Conoces alguna aplicación colaborativa bien conocida que sea significativamente compleja, funcione perfectamente sin conexión y declare que eres un usuario satisfecho de Firebase? Yo no
Sebastien Lorber
20

Puede solicitar datos en los creadores de acciones o en las tiendas. Lo importante es no manejar la respuesta directamente, sino crear una acción en la devolución de llamada de error / éxito. Manejar la respuesta directamente en la tienda conduce a un diseño más frágil.

fisherwebdev
fuente
9
¿Puedes explicar esto en más detalles por favor? Digamos que necesito hacer la carga inicial de datos desde el servidor. En la vista del controlador, inicio una acción INIT, y la Tienda inicia su inicialización asíncrona reflejando esta acción. Ahora, iría con la idea de que cuando la Tienda recuperara los datos, simplemente emitiría un cambio, pero no comenzaría una acción. Entonces, emitir un cambio después de la inicialización les dice a las vistas que pueden obtener los datos de la tienda. ¿Por qué es necesario no emitir un cambio tras una carga exitosa, sino comenzar otra acción? Gracias
Jim-Y
Fisherwebdev, acerca de las tiendas que solicitan datos, al hacerlo, no rompa el paradigma de Flux, las únicas 2 formas correctas en las que puedo pensar para solicitar datos es mediante: 1. use una clase bootstrap usando Actions para cargar datos 2 Vistas, nuevamente usando Acciones para cargar datos
Yotam
44
Solicitar datos no es lo mismo que recibir datos. @ Jim-Y: solo debe emitir un cambio una vez que los datos en la tienda realmente hayan cambiado. Yotam: No, solicitar datos en la tienda no rompe el paradigma. Los datos solo deben recibirse a través de acciones, de modo que todas las tiendas puedan ser informadas por cualquier nuevo dato que ingrese a la aplicación. Por lo tanto, podemos solicitar datos en una tienda, pero cuando vuelve la respuesta, necesitamos crear una nueva acción en lugar de manejarla directamente. Esto mantiene la aplicación flexible y resistente al desarrollo de nuevas características.
fisherwebdev
2

He estado usando el ejemplo de Binary Muse del ejemplo de Fluxxor ajax . Aquí está mi ejemplo muy simple usando el mismo enfoque.

Tengo una tienda de productos simple , algunas acciones de productos y el componente de vista de controlador que tiene subcomponentes que responden a los cambios realizados en la tienda de productos . Por ejemplo producto deslizante , producto de lista y de búsqueda de productos componentes.

Cliente de producto falso

Aquí está el cliente falso que puede sustituir por llamar a un punto final real que devuelve productos.

var ProductClient = {

  load: function(success, failure) {
    setTimeout(function() {
      var ITEMS = require('../data/product-data.js');
      success(ITEMS);
    }, 1000);
  }    
};

module.exports = ProductClient;

Tienda de productos

Aquí está la Tienda de productos, obviamente, esta es una tienda muy mínima.

var Fluxxor = require("fluxxor");

var store = Fluxxor.createStore({

  initialize: function(options) {

    this.productItems = [];

    this.bindActions(
      constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
      constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
    );
  },

  onLoadSuccess: function(data) {    
    for(var i = 0; i < data.products.length; i++){
      this.productItems.push(data.products[i]);
    }    
    this.emit("change");
  },

  onLoadFail: function(error) {
    console.log(error);    
    this.emit("change");
  },    

  getState: function() {
    return {
      productItems: this.productItems
    };
  }
});

module.exports = store;

Ahora las acciones del producto, que hacen la solicitud AJAX y, en caso de éxito, activan la acción LOAD_PRODUCTS_SUCCESS que devuelve los productos a la tienda.

Acciones de producto

var ProductClient = require("../fake-clients/product-client");

var actions = {

  loadProducts: function() {

    ProductClient.load(function(products) {
      this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
    }.bind(this), function(error) {
      this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
    }.bind(this));
  }    

};

module.exports = actions;

Entonces llamando this.getFlux().actions.productActions.loadProducts() desde cualquier componente que escuche esta tienda cargaría los productos.

Sin embargo, podría imaginarse teniendo diferentes acciones que responderían a las interacciones del usuario como addProduct(id) removeProduct(id)etc. siguiendo el mismo patrón.

Espero que ese ejemplo ayude un poco, ya que me pareció un poco difícil de implementar, pero ciertamente ayudó a mantener mis tiendas 100% sincronizadas.

svnm
fuente
2

Respondí una pregunta relacionada aquí: Cómo manejar llamadas api anidadas en flujo

Se supone que las acciones no son cosas que causen un cambio. Se supone que son como un periódico que informa la aplicación de un cambio en el mundo exterior, y luego la aplicación responde a esa noticia. Las tiendas provocan cambios en sí mismas. Las acciones solo les informan.

Bill Fisher, creador de Flux https://stackoverflow.com/a/26581808/4258088

Lo que básicamente debería estar haciendo es, indicando a través de acciones qué datos necesita. Si la acción informa a la tienda, debería decidir si necesita obtener algunos datos.

La tienda debe ser responsable de acumular / recuperar todos los datos necesarios. Sin embargo, es importante tener en cuenta que después de que la tienda solicitó los datos y obtiene la respuesta, debería desencadenar una acción en sí misma con los datos recuperados, en lugar de que la tienda maneje / guarde la respuesta directamente.

Una tienda podría verse así:

class DataStore {
  constructor() {
    this.data = [];

    this.bindListeners({
      handleDataNeeded: Action.DATA_NEEDED,
      handleNewData: Action.NEW_DATA
    });
  }

  handleDataNeeded(id) {
    if(neededDataNotThereYet){
      api.data.fetch(id, (err, res) => {
        //Code
        if(success){
          Action.newData(payLoad);
        }
      }
    }
  }

  handleNewData(data) {
    //code that saves data and emit change
  }
}
MoeSattler
fuente
0

Aquí está mi opinión sobre esto: http://www.thedreaming.org/2015/03/14/react-ajax/

Espero que ayude. :)

Jason Walton
fuente
8
voto negativo según las pautas. poner respuestas en sitios externos hace que este sitio sea menos útil, y genera respuestas de menor calidad, lo que disminuye la utilidad del sitio. las URL externas probablemente también se romperán a tiempo. el voto negativo no dice nada sobre la utilidad del artículo, que por cierto es muy bueno :)
oligofren
2
Buena publicación, pero agregar un breve resumen de los pros / contras de cada enfoque te dará votos positivos. En SO, no deberíamos necesitar hacer clic en un enlace para obtener la esencia de su respuesta.
Cory House