Должны ли потоковые хранилища или действия (или и то, и другое) касаться внешних сервисов?

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

-OR-

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

Мне кажется, что это должно быть одно или другое (а не смесь того и другого). Если да, то почему один из них предпочтительнее/рекомендуется, а не другой?


person plaxdan    schedule 02.09.2014    source источник
comment
Этот пост может помочь code-experience.com /   -  person Markus-ipse    schedule 04.09.2014
comment
Тем, кто оценивает различные реализации шаблона потока, я настоятельно рекомендую взглянуть на Redux github.com/rackt/ redux Хранилища реализованы как чистые функции, которые принимают текущее состояние и создают новую версию этого состояния. Поскольку они являются чистыми функциями, вопрос о том, могут ли они вызывать сетевые службы и службы хранения, снимается с вас: они не могут.   -  person plaxdan    schedule 19.11.2015


Ответы (6)


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

  1. Ваши магазины становятся полностью синхронными. Это значительно упрощает отслеживание и тестирование логики вашего магазина: просто создайте экземпляр хранилища с заданным состоянием, отправьте ему действие и проверьте, не изменилось ли состояние. как и ожидалось. Кроме того, одна из основных концепций в потоке — предотвращение каскадных отправок и предотвращение одновременной отправки нескольких отправок; это очень сложно сделать, когда ваши магазины выполняют асинхронную обработку.

  2. Все отправки действий происходят от создателей действий. Если вы обрабатываете асинхронные операции в своих магазинах и хотите, чтобы обработчики действий ваших хранилищ были синхронными (и вы должны это сделать, чтобы получить гарантии однократной отправки потока) , вашим магазинам потребуется запускать дополнительные действия SUCCESS и FAIL в ответ на асинхронную обработку. Вместо этого размещение этих диспетчеров в создателях действий помогает разделить работу создателей действий и хранилищ; кроме того, вам не нужно копаться в логике вашего магазина, чтобы выяснить, откуда отправляются действия. Типичное асинхронное действие в этом случае может выглядеть примерно так (измените синтаксис вызовов dispatch в зависимости от того, какой вариант потока вы используете):

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

    Логика, которая в противном случае может дублироваться в различных действиях, должна быть выделена в отдельный модуль; в этом примере этим модулем будет SomeDataAccessLayer, который обрабатывает фактический запрос Ajax.

  3. Вам нужно меньше создателей действий. Это не так важно, но приятно иметь. Как упоминалось в № 2, если ваши магазины имеют синхронную обработку отправки действий (а они должны), вам нужно будет запускать дополнительные действия для обработки результатов асинхронных операций. Выполнение отправки в создателях действий означает, что один создатель действия может отправлять все три типа действий, обрабатывая результат самого асинхронного доступа к данным.

person Michelle Tilley    schedule 03.09.2014
comment
Я думаю, что то, что вызывает вызов веб-API (создатель действия или хранилище), менее важно, чем тот факт, что обратный вызов успеха/ошибки должен создавать действие. Таким образом, поток данных всегда такой: действие -> диспетчер -> магазины -> представления. - person fisherwebdev; 03.09.2014
comment
Будет ли лучше/проще тестировать фактическую логику запроса в модуле API? Таким образом, ваш модуль API может просто вернуть обещание, из которого вы отправляете. Создатель действия просто отправляет на основе разрешения/неудачи после отправки первоначального «ожидающего» действия. Остается вопрос, как компонент прослушивает эти «события», поскольку я не уверен, что состояние запроса должно сопоставляться с состоянием хранения. - person backdesk; 29.05.2015
comment
@backdesk Это именно то, что я делаю в приведенном выше примере: отправляю начальное ожидающее действие ("SOME_ACTION"), использую API для выполнения запроса (SomeDataAccessLayer.doSomething(userId)), который возвращает обещание, и в двух функциях .then отправляю дополнительные действия. Состояние запроса может (более или менее) сопоставляться с состоянием хранения, если приложению необходимо знать о состоянии состояния. Как это отображается, зависит от приложения (например, может быть, каждый комментарий имеет индивидуальное состояние ошибки, как Facebook, или, может быть, есть один глобальный компонент ошибки) - person Michelle Tilley; 29.05.2015
comment
@MichelleTilley одна из основных концепций в потоке - предотвратить каскадные отправки и предотвратить несколько отправок одновременно; это очень сложно сделать, когда ваши магазины выполняют асинхронную обработку. Это ключевой момент для меня. Хорошо сказано. - person ; 20.12.2015

Я написал этот вопрос разработчикам в Facebook и получил ответ от Билла Фишера:

Отвечая на взаимодействие пользователя с пользовательским интерфейсом, я бы сделал асинхронный вызов в методах создателя действия.

Но когда у вас есть тикер или какой-то другой нечеловеческий драйвер, звонок из магазина работает лучше.

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

person Markus-ipse    schedule 04.09.2014
comment
Хотя это имеет смысл, есть идеи, почему a call from store works better when action triggers from non-human driver ? - person SharpCoder; 29.01.2016
comment
@SharpCoder Я думаю, если у вас есть живой тикер или что-то подобное, вам действительно не нужно запускать действие, и когда вы делаете это из магазина, вам, вероятно, придется писать меньше кода, поскольку магазин может мгновенно получить доступ к состоянию & испустить изменение. - person Florian Wendelborn; 19.02.2016

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

В каждой реализации Flux, которую я видел, действия — это в основном строки событий, превращенные в объекты, например, традиционно у вас было бы событие с именем «anchor: clicked», но во Flux оно было бы определено как AnchorActions.Clicked. Они даже настолько «тупые», что в большинстве реализаций есть отдельные объекты Dispatcher для фактической отправки событий в прослушиваемые хранилища.

Лично мне нравится реализация Flux в Reflux, где нет отдельных объектов Dispatcher, а объекты Action выполняют диспетчеризацию сами.


редактировать: Flux от Facebook фактически выбирает «создателей действий», поэтому они используют умные действия. Они также готовят полезную нагрузку, используя магазины:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/ChatMessageActionCreators.js#L27 (строки 27 и 28)

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

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

Так что я думаю, что это лучшее решение.

person Rygu    schedule 02.09.2014
comment
Что это за реализация Reflux? Я не слышал об этом. Ваш ответ интересен. Вы имеете в виду, что реализация вашего магазина должна иметь логику для выполнения вызовов API и так далее? Я думал, что магазины должны просто получать данные и просто обновлять их значения. Они фильтруют определенные действия и обновляют некоторые атрибуты своих магазинов. - person Jeremy D; 03.09.2014
comment
Reflux — это небольшой вариант Flux от Facebook: github.com/spoike/refluxjs Магазины управляют всем доменом Model. вашего приложения по сравнению с действиями/диспетчерами, которые только сшивают и склеивают элементы. - person Rygu; 03.09.2014
comment
Итак, я еще немного подумал об этом и (почти) ответил на свой вопрос. Я бы добавил это как ответ здесь (чтобы другие могли проголосовать), но, видимо, у меня слишком бедная карма в stackoverflow, чтобы я мог опубликовать ответ. Вот ссылка: groups.google.com/d/msg/reactjs /PpsvVPvhBbc/BZoG-bFeOwoJ - person plaxdan; 03.09.2014
comment
Спасибо за ссылку на группу Google, она кажется действительно информативной. Я также больше люблю все, что проходит через диспетчер, и действительно простую логику в магазине, в основном, обновление их данных, вот и все. @Rygu Я проверю рефлюкс. - person Jeremy D; 03.09.2014
comment
Я отредактировал свой ответ с альтернативным представлением. Кажется, оба решения возможны. Я почти наверняка предпочел бы решение Facebook другим. - person Rygu; 04.09.2014

Я приведу аргумент в пользу «тупых» действий.

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

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

Это дает возможность более многочисленным, но меньшим, более специализированным магазинам. Я выступаю за этот стиль, потому что

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

Цель Магазина — предоставить данные представлениям. Название «Действие» предполагает, что его цель — описать изменение в моем приложении.

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

С «умными» действиями вам может потребоваться изменить действие «обновить панель инструментов», чтобы использовать новый API. Однако «Обновление приборной панели» в абстрактном смысле не изменилось. Требования к данным ваших представлений — это то, что изменилось.

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

person Carlos Lalimarmo    schedule 11.10.2015

flux-react-router-demo имеет хороший полезный вариант «правильного» подхода.

ActionCreator создает обещание из внешней службы API, а затем передает обещание и три константы действия функции dispatchAsync в прокси/расширенном Dispatcher. dispatchAsync всегда будет отправлять первое действие, например. «GET_EXTERNAL_DATA», и как только обещание вернется, оно отправит либо «GET_EXTERNAL_DATA_SUCCESS», либо «GET_EXTERNAL_DATA_ERROR».

person William Myers    schedule 05.06.2015

Если вы хотите когда-нибудь иметь среду разработки, сравнимую с той, что вы видите в известном видео Брета Виктора Inventing on Principle, вам лучше использовать тупые хранилища, которые являются просто проекцией действий/событий внутри структуры данных без каких-либо побочных эффектов. Также было бы полезно, если бы ваши магазины действительно были членами одной и той же глобальной неизменной структуры данных, как в Redux.

Дополнительные пояснения здесь: https://stackoverflow.com/a/31388262/82609

person Sebastien Lorber    schedule 22.07.2015