Где должен быть сделан запрос ajax в приложении Flux?
Я создаю реакцию.приложение js с архитектурой flux, и я пытаюсь выяснить, где и когда должен быть сделан запрос на данные с сервера. Есть ли какой-нибудь пример для этого. (Не приложение TODO!)
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.
Проверьте эти удивительные статьи от авторов Кафки, чтобы убедить себя:
- ПОТОКОВАЯ ОБРАБОТКА, ПОИСК СОБЫТИЙ, РЕАКТИВНАЯ, CEP... И ПРИДАНИЕ ВСЕМУ ЭТОМУ СМЫСЛА
- журнал: что каждый инженер-программист следует знать об объединяющей абстракции данных реального времени .
Может быть, мы можем начать с отправки команд на сервер и получения потока событий сервера (через 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/
Надеюсь, это поможет. :)