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


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

6 189

6 ответов:

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

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

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

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

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

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

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


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

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

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

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

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

Аякс-это зло

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

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

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

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

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

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

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

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

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

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

  • ваш мобильный клиент / браузер имеет ограниченные ресурсы и не может хранить все данные локально (поэтому иногда требуется опрос с ajax-запросом тяжелого контента)
  • ваш клиент не должен видеть все данные распределенной системы, поэтому он должен каким-то образом фильтровать события, которые он получает по соображениям безопасности

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

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

У меня есть простое хранилище продуктов некоторые действия продукта и компонент controller-view, который имеет подкомпоненты, которые все реагируют на изменения, внесенные в хранилище продуктов. Например продукт-слайдер, 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% синхронными.

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

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

Билл Фишер, создатель потока https://stackoverflow.com/a/26581808/4258088

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

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

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

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

Вот мой взгляд на это: http://www.thedreaming.org/2015/03/14/react-ajax/

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