Что должно быть проще, действия или редукторы?

Это один из способов записи действий при использовании thunk, что приводит к очень простым сокращениям.

   getCurrentUserPicture(){
        return (dispatch,getState) => {
            dispatch({type: "isloading", isLoading: true}); // shows a loading dialog
            dispatch({type: "errorMessage"}); // clear errorMessage
            dispatch({type: "warningMessage"}); // clear warningMessage

            const userId = getState().user.get("currentUser").id;
            getUserPicture(userId) // get from backend
                .then(picture => {
                    dispatch({type: "userPicture", picture});
                    dispatch({type: "isLoading", isLoading: false}); 
                }
            )
            .catch(e=>{
                dispatch({type: "errorMessage", e});
                dispatch({type: "isLoading", isLoading: true});
                }
            )
        }
    }

С редуктором, включая следующие:

export reducer(state = initialState, action = {}) {
    switch(action.type) {
        case "isLoading":
            return state.set("isLoading", action.isLoading)

Вот еще один подход, когда действия "чище", но редукторы более сложны:

   getCurrentUserPicture(){
        return (dispatch,getState) => {
            dispatch({type: "gettingCurrentUserPicture", true});

            const userId = getState().user.get("currentUser").id;
            getUserPicture(userId)
                .then(picture => {
                    dispatch({type: "retrievedCurrentUserPicture", picture}); 
                }
            )
            .catch(e=>{
                dispatch({type: "errorRetrievedCurrentUserPicture", e});
                }
            )
        }
    }

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

export reducer(state = initialState, action = {}) {
    switch(action.type) {
        case "gettingCurrentUserPicture":
            return state.set("isLoading", true)
                        .delete("errorMessage")
                        .delete("warningMessage")

Один подход лучше другого?

Ответ 1

Почему не оба?

Действия и Редукторы должны быть как можно более простыми.

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

Действия

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

Мое мнение состоит в том, что бизнес-логика/правила либо принадлежат здесь напрямую, либо могут направляться сюда через вспомогательные служебные функции. См. Ниже разделы о async и утилите.

Он должен описать, что произошло. Описать, описать, описать

Переходники

Редукторы должны формировать полное (ish) представление вашего приложения с минимальным объемом данных. По возможности держите свое государственное дерево нормализованным, чтобы убедиться, что вы держите минимальное состояние.

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

Как должно выглядеть государство на основе описания, которое я только что получил? (из действия)

Подход

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

Я буду буквально говорить (иногда даже outloud) через шаги, например:

  • Наведите указатель мыши на заголовок сообщения и нажмите править
  • Приложение переходит на страницу "Редактировать сообщение"
  • Вы можете редактировать поле заголовка (и поле будет обновляться)
  • Вы можете редактировать поле тела (и поле будет обновляться)
  • Приложение будет сохранять каждые 90 секунд в виде черновика, значок сохранения будет отображаться в правом верхнем углу во время автоматического сохранения, но вы можете продолжить работу
  • Вы также можете сохранить, нажав кнопку сохранения в любое время. За это время вы не сможете редактировать, и вы будете перенаправлены на индексную страницу.

Это можно легко перевести на действия и редукторы, посмотрев на то, что описывает и что является, в результате, изменением состояния:

  • Нажмите "Изменить": "Действие". Мы описываем для приложения, в котором была нажата запись с идентификатором X.
  • Переключает на страницу "Редактировать сообщение": Редуктор. Когда мы "слышим" "редактирование сообщения", изменим состояние приложения, чтобы теперь он отображал страницу редактирования сообщения.
  • Изменить название/тело: Действие - Мы описываем, какое поле и какое значение вводится пользователем.
  • Обновить название/тело: Reducer - Состояние приложения изменено в соответствии с введенными полями в
  • Автосохранение: действие/редуктор/действие/редуктор
    • Действие: описать приложение, в котором началось автосохранение
    • Редуктор: состояние приложения изменяется, чтобы показать, что происходит автосохранение.
    • Действие: описать завершено автосохранение
    • Редуктор: состояние приложения изменяется, чтобы скрыть работу автосохранения.
  • и т.д.

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

Но мои действия все еще не тонкие..

Все вышеизложенное достаточно просто сказать, но как вы держите свои эти бизнес-действия чистыми /slim/light/simple/etc?

В конце дня большинство бизнес-логики не имеют большого отношения к React/Redux - поэтому их обычно можно перекопать в свои служебные функции или классы. Это замечательно по нескольким причинам: а) проще проверить, потому что у него нет нулевой привязки к React/Redux и b) держит ваши действия светлыми, а c) более инкапсулирован - с минимальными знаниями о реакции/сокращении - это не плохо.

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

Но но как насчет async..

Async обычно начинает очень быстро загромождать действия. Сага действительно стоит посмотреть, если вы хотите очистить свои действия, но вводите определенную кривую обучения, если вы не знакомы с генераторами и еще что-то еще. В этом случае thunks по-прежнему полезны и помните, что вы можете объединить несколько асинхронных функций в одно обещание, которое может сыграть thunk (что опять же, что сгруппированное обещание можно разделить на класс функции полезности, например generateSaveSequencePromise(), который может обернуть 4 или 5 асинхронных выборок и вернуть одно обещание).

Боковое примечание - вы должны минимизировать отправку нескольких раз из одного потока, как в первом примере. Если возможно, попробуйте создать родительское действие, которое объединяет все действие в одно. Поэтому, используя ваш первый пример, это может быть:

// action
dispatch({type: "FETCHING_USER_IMAGE", id: userId });

И тогда ваши различные редукторы должны очистить свои очереди сообщений или что-то еще, если они "услышат" этот тип.

Ответ 2

Есть преимущества/недостатки, если вы идете в любом случае. Но мои варианты состоят в том, чтобы упростить редукторы, чем создатели действий/действий.

Обработка бизнес-логики в редукторах (упрощение действий)

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

Преимущества

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

Недостатки

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

Обработка бизнес-логики в Action Creators (упрощение упрощения)

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

Преимущества

  • У вас есть доступ к dispatch.
  • Вы можете выполнять асинхронные задачи (см. redux-thunk).
  • Создатели Action имеют доступ к целому состоянию (redux-thunk), даже если вы комбинировали редукторы.

Недостатки

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

Здесь подробно обсуждается.

Ответ 3

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

С жирными действиями, которые просто указывают на то, что произошло (например, запрос был сделан, был получен успешный ответ, таймер выстрелил, нажата кнопка), вы можете управлять изменениями в бизнес-логике, не меняя actionCreators. Например, вы решите, что хотите начать отображение масок загрузки во время операций - теперь вам не нужно обновлять всех создателей ваших действий для запуска действий по загрузкеStart и loadEnd, вы просто обновляете свои редукторы, чтобы соответствующим образом установить состояние загрузки.

Лично я считаю, что лучшая модель для сложного приложения - сделать actionCreators тривиальным (или полностью отбросить их, а также просто отправить полезную нагрузку непосредственно из ваших подключенных компонентов) и вместо этого использовать redux-сагу (https://github.com/yelouafi/redux-saga), чтобы обрабатывать ваши асинхронные и нечистые вещи - это гораздо более мощная модель. Например, он упрощает выполнение действий, таких как операции debouncing и throttling, реагирует на изменения состояния хранилища, а также на действия и т.д.

Ответ 4

Я согласен с @TomW в том, что отправка многих небольших действий приводит к тому, что действия становятся сеттерами.

Создатели Action - это изначально простые функции, которые формируют очень простые объекты, которые затем отправляются.

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

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

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

Таким же образом, отправка нескольких действий, которые сами по себе не передают намерения, не подходит для Redux.

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

Redux не просто применяет действия. Middleware может записывать их и возвращать. Вы можете просмотреть историю и посмотреть, что произошло.

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

Создатели асинхронных действий - это немного клочья. То, что отправляется, - это не pojo, как мандаты Redux, а функция. Тот факт, что он отправляется, не записывается, если вы используете инструменты Redux Dev. Его нельзя воспроизвести. Таким образом, создатели асинхронного действия не создают действия.

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

(Ab) с использованием асинхронных действий для реализации бизнес-логики означает отказ от некоторых из наиболее важных преимуществ использования Redux.