Где должен быть сделан запрос ajax в приложении Flux?

174

Я создаю приложение response.js с архитектурой потока, и я пытаюсь выяснить, где и когда должен запрашиваться запрос данных с сервера. Есть ли какой-нибудь пример для этого. (Не приложение TODO!)

Теги:
reactjs-flux

6 ответов

115
Лучший ответ

Я большой сторонник размещения асинхронных операций записи в создателях действий и асинхронных операций чтения в хранилище. Цель состоит в том, чтобы сохранить код модификации состояния хранилища в полностью синхронных обработчиках действий; это делает их простыми рассуждать и просты до unit test. Чтобы предотвратить одновременное одновременное обращение к одной и той же конечной точке (например, двойное чтение), я переведу фактическую обработку запроса в отдельный модуль, который использует promises для предотвращения множественных запросов; например:

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

Во время чтения в магазине задействованы асинхронные функции, существует важное предостережение о том, что хранилища не обновляются в обработчиках async, а вместо этого запускают действие и только запускают действие, когда приходит ответ. Обработчики для этого действия в конечном итоге выполняют фактическую модификацию состояния.

Например, компонент может сделать:

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

В магазине будет реализован метод, возможно, что-то вроде этого:

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
  }
}
  • 0
    Вы пытались поместить обещания в полезную нагрузку действий? Мне легче иметь дело, чем отправлять несколько действий
  • 0
    @SebastienLorber Самым большим преимуществом для меня является то, что все обновления состояний хранятся в синхронном пути кода и явно только в результате отправки действий, поэтому я избегаю асинхронности внутри хранилищ.
Показать ещё 8 комментариев
38

Fluxxor имеет пример асинхронной связи с API.

Этот пост в блоге рассказывает об этом и был включен в блог React.


Я нахожу этот очень важный и трудный вопрос, на который пока еще не ответил однозначно, поскольку синхронизация программного обеспечения интерфейса с бэкэндом по-прежнему является болью.

Должны ли выполняться запросы API в компонентах JSX? Магазины? Другое место?

Выполнение запросов в магазинах означает, что если 2 магазина нуждаются в одних и тех же данных для данного действия, они выдадут 2 похожих запроса (если вы не введете зависимости между хранилищами, которые я действительно надену, t нравится)

В моем случае, я нашел это очень удобно, чтобы положить Q promises в качестве полезной нагрузки, потому что:

  • Мои действия не обязательно должны быть сериализуемыми (я не храню журнал событий, мне не нужна функция повторения событий для источника событий)
  • Он устраняет необходимость иметь разные действия/события (запрос уволен/запрос завершен/запрос не выполнен) и должен сопоставлять их с помощью идентификаторов корреляции при одновременном запуске параллельных запросов.
  • Это позволяет нескольким хранилищам прослушивать завершение одного и того же запроса без каких-либо зависимостей между хранилищами (однако может быть лучше ввести слой кеширования?)

Ajax - это EVIL

Я думаю, что Ajax будет в меньшей степени использоваться в ближайшем будущем, потому что это очень сложно рассуждать. Правильный путь? Рассмотрение устройств как части распределенной системы Я не знаю, где я впервые встретил эту идею (возможно, в этом вдохновляющем видео Криса Грейнджера).

Подумайте об этом. Теперь для масштабируемости мы используем распределенные системы с возможной согласованностью как механизмы хранения (потому что мы не можем победить теорему CAP, и часто мы хотим быть доступными). Эти системы не синхронизируются путем опроса друг друга (за исключением, может быть, для консенсусных операций?), А используют такие структуры, как CRDT и журналы событий, чтобы сделать все члены распределенной системы в конечном итоге последовательными (члены будут сходиться к тем же данным, учитывая достаточное время).

Теперь подумайте о том, что такое мобильное устройство или браузер. Это просто член распределенной системы, который может страдать от сетевой задержки и сетевой разбивки. (т.е. Вы используете свой смартфон в метро)

Если мы сможем построить сетевые разделы и сетевые толерантные по скорости базы данных (я имею в виду, что мы все еще можем выполнять операции записи с изолированным node), мы, вероятно, можем создавать внешние интерфейсы (мобильные или настольные), вдохновленные этими концепциями, которые хорошо работают с автономным режимом, поддерживаемым из коробки без недоступности функций приложения.

Думаю, нам нужно действительно вдохновлять нас на то, как базы данных работают над архитектурой наших приложений frontend. Одно замечание состоит в том, что эти приложения не выполняют POST и PUT и GET ajax-запросы для отправки данных друг другу, а используют журналы событий и CRDT для обеспечения конечной согласованности.

Так почему бы не сделать это на фронте? Обратите внимание, что бэкэнд уже движется в этом направлении, с инструментами, такими как Kafka, широко принятыми крупными игроками. Это так или иначе связано с Event Sourcing/CQRS/DDD.

Проверьте эти удивительные статьи от авторов Kafka, чтобы убедить себя:

Возможно, мы можем начать с отправки команд на сервер и получения потока событий сервера (через веб-сайты для примера) вместо того, чтобы запускать запросы Ajax.

Мне никогда не нравились запросы Ajax. Как мы реагируем разработчиков, как правило, являются функциональными программистами. Я думаю, что трудно рассуждать о локальных данных, которые, как предполагается, являются вашим "источником истины" вашего внешнего приложения, в то время как реальный источник истины на самом деле находится в базе данных сервера, а ваш "локальный" источник правды может быть уже устаревшим когда вы его получите, и никогда не сходитесь к истинному источнику истины, если вы не нажмете какую-нибудь хромовую кнопку обновления... Это инженерное дело?

Однако по некоторым очевидным причинам еще немного сложно спроектировать:

  • Ваш клиент для мобильных/браузеров имеет ограниченные ресурсы и не может хранить все данные локально (поэтому иногда требуется опрос с тяжелым контентом ajax)
  • Ваш клиент не должен видеть все данные распределенной системы, поэтому он должен каким-то образом фильтровать события, которые он получает по соображениям безопасности.
  • 3
    Можете ли вы привести пример использования Q-обещаний с действиями?
  • 0
    @MattFoxxDuncan не уверен, что это такая хорошая идея, так как делает «журнал событий» неосериализуемым и асинхронно обновляет хранилище при запускаемых действиях, поэтому у него есть некоторые недостатки. Однако, если это нормально для вашего варианта использования, и вы понимаете эти недостатки, это довольно удобно и уменьшить шаблон. С Fluxxor вы, вероятно, можете сделать что-то вроде этого this.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere});
Показать ещё 5 комментариев
20

Вы можете вызвать данные как в создателях действий, так и в магазинах. Важно не обрабатывать ответ напрямую, а создавать действие в обратном вызове ошибки/успеха. Обработка ответа непосредственно в магазине приводит к более хрупкому дизайну.

  • 9
    Можете ли вы объяснить это более подробно, пожалуйста? Скажем, мне нужно сделать начальную загрузку данных с сервера. В представлении контроллера я запускаю действие INIT, и Store начинает асинхронную инициализацию, отражающую это действие. Теперь я согласен с идеей, что когда Store получает данные, он просто генерирует изменения, но не запускает действие. Таким образом, передача изменений после инициализации говорит представлениям, что они могут получить данные из хранилища. Почему существует необходимость не испускают изменения при успешной загрузке, но начать другое действие ?! Спасибо
  • 0
    Fisherwebdev, о магазинах, требующих данных, тем самым не нарушайте парадигму Flux, единственные 2 правильных способа вызова данных, которые я могу придумать, это использовать: 1. использовать класс начальной загрузки, используя Actions для загрузки данных 2 Представления, снова используя Действия для загрузки данных
Показать ещё 1 комментарий
2

Я ответил на соответствующий вопрос здесь: Как обрабатывать вложенные вызовы api в потоке

Действия не должны быть вещами, которые вызывают изменение. Они должны быть похожи на газету, которая информирует о применении изменений во внешнем мире, а затем приложение реагирует на эти новости. Магазины создают изменения сами по себе. Действия просто информируют их.

Билл Фишер, создатель Flux https://stackoverflow.com/questions/26581587/flux-dispatch-dispatch-cannot-dispatch-in-the-middle-of-a-dispatch

То, что вы в основном должны делать, заключается в том, чтобы указать, какие данные вам нужны. Если магазин получает информацию о действии, он должен решить, нужно ли ему извлекать некоторые данные.

Магазин должен нести ответственность за накопление/выборку всех необходимых данных. Важно отметить, однако, что после того, как магазин запросил данные и получил ответ, он должен инициировать действие непосредственно с извлеченными данными, в противоположность обработке хранилища/сохранению ответа напрямую.

Магазины могут выглядеть примерно так:

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
  }
}
2

Я использовал пример Binary Muse в примере Fluxxor ajax. Вот мой очень простой пример с использованием того же подхода.

У меня есть простой товарный магазин некоторые действия продукта и компонент controller-view, который имеет подкомпоненты, которые все реагируют на сделанные изменения в магазин продуктов. Например, компоненты product-slider, product-list и product-search.

Поддельный клиент продукта

Вот поддельный клиент, который вы можете заменить для вызова фактической конечной точки, возвращающей продукты.

var ProductClient = {

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

module.exports = ProductClient;

Магазин продуктов

Вот Магазин товаров, очевидно, это очень минимальный магазин.

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;

Теперь действия продукта, которые делают запрос AJAX и при успешном завершении действия LOAD_PRODUCTS_SUCCESS возвращают продукты в хранилище.

Действия продукта

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;

Таким образом, вызов this.getFlux().actions.productActions.loadProducts() из любого компонента, прослушивающего этот магазин, будет загружать продукты.

Вы можете представить себе разные действия, которые могли бы реагировать на взаимодействия с пользователем, такие как addProduct(id) removeProduct(id) и т.д.... по одному и тому же шаблону.

Надеюсь, что этот пример немного помогает, поскольку я нашел это немного сложным для реализации, но, безусловно, помог сохранить мои магазины на 100% синхронно.

0

Здесь я беру на себя следующее: http://www.thedreaming.org/2015/03/14/react-ajax/

Надеюсь, что это поможет.:)

  • 8
    понижение в соответствии с руководящими принципами. размещение ответов на внешних сайтах делает этот сайт менее полезным и снижает качество ответов, снижая полезность сайта. внешние URL, вероятно, тоже сломаются во времени. Даунвот ничего не говорит о полезности статьи, что, кстати, очень хорошо :)
  • 2
    Хороший пост, но добавление краткого описания плюсов / минусов каждого подхода принесет вам положительные отзывы. На SO, нам не нужно нажимать ссылку, чтобы получить суть вашего ответа.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню