Должны ли flux store или действия (или оба) касаться внешних сервисов?

122

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

-ИЛИ-

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

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

plaxdan
источник
2
Этот пост может помочь code-experience.com/…
Markus-ipse
Тем, кто оценивает различные реализации шаблона потока, я настоятельно рекомендую взглянуть на Redux. Github.com/rackt/redux. Магазины реализованы как чистые функции, которые принимают текущее состояние и генерируют новую версию этого состояния. Поскольку это чистые функции, вопрос о том, могут ли они вызывать сетевые службы и службы хранения, снимается с ваших рук: они не могут.
plaxdan

Ответы:

151

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

  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, если в ваших магазинах есть синхронная обработка диспетчеризации действий (а она должна), вам необходимо запустить дополнительные действия для обработки результатов асинхронных операций. Выполнение диспетчеризации в создателях действий означает, что один создатель действия может отправлять все три типа действий, обрабатывая результат самого асинхронного доступа к данным.

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

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

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

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

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

Markus-IPSE
источник
Хотя это имеет смысл, есть идеи, почему a call from store works better when action triggers from non-human driver ?
SharpCoder
@SharpCoder Я думаю, если у вас есть live-тикер или что-то подобное, вам действительно не нужно запускать действие, и когда вы делаете это из магазина, вам, вероятно, придется писать меньше кода, поскольку магазин может мгновенно получить доступ к состоянию & выдать изменение.
Флориан Венделборн,
8

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

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

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


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

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

Думаю, это лучшее решение.

Rygu
источник
Что это за реализация Reflux? Я не слышал об этом. Ваш ответ интересный. Вы имеете в виду, что реализация вашего магазина должна иметь логику для вызовов API и т. Д.? Я думал, что магазины должны просто получать данные и просто обновлять свои значения. Они фильтруют определенные действия и обновляют некоторые атрибуты своих магазинов.
Джереми Д.
Reflux - это небольшая вариация Flux от Facebook: магазины github.com/spoike/refluxjs управляют всей «моделью» вашего приложения, в отличие от действий / диспетчеров, которые только сшивают и склеивают элементы.
Rygu 03
1
Итак, я подумал об этом еще и (почти) ответил на свой вопрос. Я бы добавил его в качестве ответа здесь (для других, чтобы проголосовать), но, видимо, я слишком беден кармой в stackoverflow, чтобы еще иметь возможность опубликовать ответ. Итак, вот ссылка: groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan
Спасибо за ссылку на группу Google, она кажется действительно информативной. Я также больше люблю все, что проходит через диспетчер, и очень простую логику в магазине, в основном, обновление своих данных, вот и все. @Rygu Я проверю рефлюкс.
Джереми Д.
Я отредактировал свой ответ с другой точки зрения. Кажется, возможны оба решения. Я бы почти наверняка предпочел решение Facebook другим.
Rygu 03
3

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

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

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

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

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

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

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

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

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

Карлос Лалимармо
источник
2

Flux-React-Router -демонстрация от компании Gaeron есть хорошая служебная вариация «правильного» подхода.

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

Уильям Майерс
источник
1

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

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

Себастьян Лорбер
источник