Всем привет! Эта статья будет полезна тем, кто устал использовать constants в Redux (частично показано на превью выше). Под катом я покажу очередной возможный велосипед и как на нем кататься.




Модуль + документация (https://github.com/pavelivanov/redaction)



Введение


Использование Redux предполагает наличие экшнов (actions) и редьюсеров (reducers), а также констант (constants), которые используются для связи экшнов с редьюсерами посредством передачи type (типа экшна).


Пример использования:


const ADD_TODO = 'ADD_TODO'

export {
  ADD_TODO
}

import { ADD_TODO } from 'constants'

export const addTODO = () => {
  return (dispatch) => {
    dispatch({
      type: ADD_TODO,
      item
    })
  }
}

const ADD_TODO = 'ADD_TODO'

const initialState = {
  TODO: []
}

export default (state = initialState, action) => {
  switch (action.type) {

    case 'ADD_TODO':
      return {
        ...state,
        TODO: [
          ...state.TODO,
          action.TODO
        ]
      }

    default:
      return state
  }
}

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


Простые Reducers


В Redaction я избавился от этих проблем. Все что вам нужно — это создать экшн и использовать его. Все.


Пример того же кода (выше) с использованием Redaction:


import { createAction } from 'redaction'

export const initialState = {
 TODO: []
}

export const addTODO = createAction((state, payload) => {
  return { 
    ...state, 
    TODO: [ 
      ...state.TODO, 
      payload 
    ] 
  }
})

Принципиальное отличие: для передачи начального state делается экспорт из файла.


Request Actions


Что касается request экшнов, Redaction предлагает большое кол-во сахара. Пример использования:


Создаем экшн:


// actions/users.js

import { createAction } from 'redaction'

export const getFeed = createAction({
  endpoint: '/api/users/me/posts',
  method: 'GET'
})

Вызываем созданный экшн:


// containers/Users/Feed.js

import actions from 'core/actions'

actions.users.getFeed({
  subset: 'posts'
})

В результате вызова getFeed в state будет:


{
  users: {
    posts: {
      pending: false,
      data: RESPONSE_BODY,
      error: null
    }
  }
}

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


У каждого subset есть 3 состояния:


1) начало запроса { pending: true, data: null, error: null }
2) запрос выполнен { pending: false, data: RESPONSE_BODY, error: null }
3) запрос выполнен с ошибками { pending: false, data: null, error: RESPONSE_ERROR }


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


Подробнее о createAction


Для отправки запросов внутри используется superagent. createAction в опциях принимает почти все параметры, которые используются в superagent.


Основные опции:


params <Object>


Любой ключ из опций может быть функцией, в этом случае одним из аргументов этой функции будет объект params. Исключениями являются onResponse и onError


endpoint <String | Function>


URL запроса.
Пример использования с params:


export const getFeed = createAction({
  endpoint: ({ userId }) => `/api/users/${userId}/posts`,
  method: 'GET'
})

getFeed({
  params: {
    userId: 100
  }
})

subset <String | Function>


Название ключа, в котором будут храниться данные в state. Стоит учесть, что полный путь будет строиться по схеме: НАЗВАНИЕ_ФАЙЛА.subset


modifyResponse <Function>


Хендлер для редактирования ответа от сервера. Принимает два аргумента — объект Response от сервера и params. Ожидает возвращение нового объекта данных. При этом необходимо возвращать response.body


modifyState <Function>


Хендлер для изменения непосредственно state. Принимает два аргумента — весь объект State и params. Может быть полезно для изменения отдельных частей хранилища при использовании одного экшна. Использовать с осторожностью!


onResponse <Function>


Хендлер, вызывающийся при удачном выполнении запроса, принимает один аргумент — объект Response от сервера


onError <Function>


Хендлер, вызывающийся при невыполнении запроса, принимает два аргумента — объект ошибки и объект Response от сервера


Процесс инициализации Redaction


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


Заключение


Буду рад, если мой модуль окажется полезен. Открыт для вопросов, замечаний и предложений по расширению функциональности. Исходный код модуля, доступен в GitHub репозитории




UPD1: переименовал модуль в RedAction (https://github.com/pavelivanov/redaction)

Поделиться с друзьями
-->

Комментарии (63)


  1. webmasterx
    26.09.2016 13:19

    Я не понял. А где редьюсер? Или как его создать?


    1. Grammka
      26.09.2016 13:23
      -5

      Что вы понимаете под редьюсером? По факту обычные редьюсеры в явном виде скрыты под капотом. Если вам нужен метод для изменения state, то в статье есть пример:

      import { createAction } from 'redbox'
      
      export const initialState = {
       TODO: []
      }
      
      export const addTODO = createAction((state, payload) => {
        return { 
          ...state, 
          TODO: [ 
            ...state.TODO, 
            payload 
          ] 
        }
      })
      

      Это и есть экшн + редюсер. При вызове addTODO('do some stuff') в state добавится этот элемент.


      1. webmasterx
        26.09.2016 13:37

        Это конечно хорошо, но я хочу иметь ActionReducer\<T\>, как это получить? (чтобы привязать хранилище в ангуляре)


      1. raveclassic
        26.09.2016 17:18
        +1

        Ну Вы взяли и в очередной раз скрыли «под капот» то, ради чего redux создавался — явной обработки всех экшенов в редьюсерах. Этот подход скрытия — это начало развития flux, даже библиотека была такая — reflux, которая как раз прятала диспетчер внутрь экшенов.


        1. Grammka
          26.09.2016 17:22
          -1

          А что неявного в моем подходе? Я не претендую на звание создателя чего-то нереального. Я делал либу в первую очередь для себя и для использования в реальном проекте и это помогло сократить код в разы. И все работает и команде удобно использовать такой подход. Redbox это не замена Redux это скорее сахар.


          1. raveclassic
            26.09.2016 18:01
            +2

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

            Это отлично, но, по-сути, вы сделали reflux поверх redux, хотя в последнем намеренно избегается такой подход.
            Неявная тут реакция стейта на экшен, т.к. стейт у вас теперь «как бы упакован» в функцию addTodo, когда по философии redux необходимо явно описать реакцию в редьюсере.
            Как вот быть, если нужно в нескольких местах в общем стейте отреагировать на 1 экшен addTodo без изменения непосредственно addTodo?


            1. Grammka
              26.09.2016 18:06
              -2

              Напишите, пожалуйста, пример что вы хотите реализовать посредством просто Redux, т.к. я не совсем понял что вы хотите написав «без изменения непосредственно addTodo».

              Если же я правильно понял и вы хотите просто изменить основное дерево state в нескольких местах при вызове одного экшна что мешает вам вызвать несколько экшн-редьюсеров? Пример в этом комментарии


              1. raveclassic
                26.09.2016 21:44
                +2

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


  1. xGromMx
    26.09.2016 14:23

    тут недавно еще это проскакивало https://medium.com/@nate_wang/a-new-approach-for-managing-redux-actions-91c26ce8b5da#.cj2jzhbf7


    1. anotherpit
      26.09.2016 16:08

      Ага, уже были попытки изобрести похожий велосипед: https://github.com/erikras/ducks-modular-redux


      1. VasilioRuzanni
        26.09.2016 17:14

        Это не велосипед, а паттерн организации проекта на Redux. И это совсем другое.


        1. anotherpit
          26.09.2016 17:20
          +1

          Паттерн, да. Говоря «велосипед», не имел в виду ничего плохого. Идея звучит вполне здраво, хотя в деле я её не пробовал.

          Это совсем другое, чем что? Чем RedBox, о котором пишет автор этой статьи на Хабре? Тогда да, конечно. Чем подход, о котором пишет человек на Медиуме по ссылке. Тогда нет, не согласен. Довольно близкие идеи и реализация.


          1. VasilioRuzanni
            26.09.2016 18:31

            Ну да, не уточнил. Под «другое» я имел в виду, что это не способ как-то особенно писать код, это не какая-то библиотека сама по себе, просто паттерн. Действительно, близко к тому, о чем говорится по ссылке выше (A New Approach for Managing Redux Actions). Пропустил этот момент, думал сравнивается с либой из топика.


  1. Miraage
    26.09.2016 14:45
    -3

    Уже был RedBox в контексте React. Некрасиво названия «красть».


    1. NeXTs_od
      26.09.2016 14:47
      -2

      да, хорошо бы переименовать


    1. Grammka
      26.09.2016 14:48
      -4

      Эммм, название я не крал, не надо обвинять плз… я название выбираю по свободности в npm https://www.npmjs.com/package/redbox. Как видите оно пренадлежит мне…

      Хабр как всегда… лишь бы обосрать, а не написать по делу. Можно придраться еще к тому что я использовал 'red' в начале названия… идиотизм


      1. xGromMx
        26.09.2016 15:03
        +4

        В чем новизна ваших идей? https://github.com/acdlite/redux-actions


        1. Grammka
          26.09.2016 15:15

          Явной новизны самой идеи нет. Естественно такую задачу как упрощение создания редьюсеров в Redux решали и до меня. Ваш пример я посмотрел (поверхностно). В нем все равно есть намек на типы. Нет встроенного решения для экшнов, т.е. это голые редьюсеры, над которыми вам по старинке придется создавать экшены и передавать данные в созданный редьюсер… Мое решение как минимум изящнее. Что касается возможных сложностей с реализацией частных случаев при использовании Redbox, как я и писал в статья, я готов к предложениям и критике… модуль новый, сырой и нуждается в доработках (ессесно).


          1. shanhaichik
            26.09.2016 16:02
            +1

            А я всегда думал, что сначала надо обкатать модуль на нескольких проектах, сложнее TODO List. Стабилизировать и только потом идти в сообщество в поиске фидбека и контрибьютеров.


            1. Grammka
              26.09.2016 16:04

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


              1. shanhaichik
                26.09.2016 16:13

                Я бы для демонстрации модуля сделал бы не большое приложение и выложил бы его исходники. И написал бы об этом пост. Где, как раз, рассказал и показал как оно работает и какие плюсы.
                Мне кажется у всех могло бы возникнуть меньше вопросов или более конкретизированные.
                Плюс многие из нас код читают лучше чем статьи :)


                1. Grammka
                  26.09.2016 16:14

                  Вы правы, спасибо за совет. Постараюсь на днях сделать подробный пример с описанием. Правда как это оформить? Как апдейт к статье?


                1. Grammka
                  27.09.2016 18:27
                  +1

                  Пример добавил: https://pavelivanov.github.io/redaction/
                  Исходники тут: https://github.com/pavelivanov/redaction/tree/master/example
                  Позже добавлю тесты


      1. justboris
        26.09.2016 16:53
        +3

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


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


        1. Grammka
          26.09.2016 16:59

          Если бы вы зашли на гитхаб, то заметили бы откуда взялось название. Я пользовался либой React Toolbox. Она дает набор комонентов для реакта. Я искал название созвучное с Redux. Есть множество вариаций с начальными буквами «Red». Исходя из этого я решил что Redux Tool Box будет нормально. Потому что модуль дает набор функционала для удобной работы с Redux. Таким образом получилось название REDuxtoolBOX. А что касается красного квадратика… квадратик это Square на английском, если уж на то пошло… А Box — коробка.


          1. TheShock
            26.09.2016 17:05
            +1

            А что касается красного квадратика… квадратик это Square на английском, если уж на то пошло… А Box — коробка.

            Для справедливости — она и чаще прямоугольничек показывает, чем квадратик, потому пусть называется RedRectangle. Хотя еще более семантически подходит RedBackground. Не парьтесь, все нормально, это что-то ребятам делается сегодня, что они так в это название вцепились.


          1. justboris
            26.09.2016 17:21
            +1

            Спасибо за объяснение этимологии вашего названия, ваша идея стала понятнее.


    1. TheShock
      26.09.2016 16:04
      +2

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


      1. justboris
        26.09.2016 16:56

        так удобно же.
        Поправил скрипт, сохранил, увидел. Когда экран маленький и нет места для консоли — самое то.


        Разумеется, для продакшена эту фичу нужно отключать.


        1. TheShock
          26.09.2016 17:01
          +1

          А красный он чтобы еще неудобнее было? ;) Что, если бы экран белый остался, то программист не догадался бы, что там ошибка?


        1. raveclassic
          26.09.2016 18:03

          А место в стектрейсе Вы идете в коде руками искать?


          1. justboris
            26.09.2016 18:13

            не понимаю вопрос.
            А как надо?


            1. raveclassic
              26.09.2016 21:47

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


              1. justboris
                27.09.2016 15:23

                У меня в консоли ничего не кликается (пользуюсь стандартым терминалом MacOs).


                Более того, оказывается в rebox можно сделать ссылки кликабельными, но я этим никогда не пользовался


                1. raveclassic
                  27.09.2016 16:54

                  Я про клиентскую консоль же :) В смысле браузерную


  1. anotherpit
    26.09.2016 16:05
    +4

    Подождите, мне казалось, что весь прикол как раз в том, чтобы отделять экшны от редьюсеров, разве нет? Иначе вместо модели событий и обработчиков мы возвращаемся к модели вызова функций (название функции — тип экшна, параметры — его payload, тело функции — сам редьюсер). Другими словами, как в RedBox реализовать редьюсер, который обрабатывает несколько экшнов, и, наоборот, несколько редьюсеров, реагирующих на один и тот же экшн?

    А к вопросу стрёмных текстовых констант вот — https://github.com/pauldijou/redux-act


    1. Grammka
      26.09.2016 16:10
      -2

      Это хорошее замечание. Вы отчасти правы. Но. Что вам мешает создать редьюсер и использовать его в нескольких экшнах? Или создать экшн и в его onResponse вызывать несколько редьюсеров?
      В целом я не ушел далеко от стандартов, просто упаковал их в сахар.


      1. anotherpit
        26.09.2016 16:23

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


        1. Grammka
          26.09.2016 16:30

          import actions from 'core/actions'
          
          export const doMultipleActions = createAction((state, { foo, bar }) => {
            actions.reducersFolderName.setFoo(foo)
            actions.reducersFolderName.setBar(bar)
          })
          

          export const initialState = {
            foo: null,
            bar: null
          }
          
          export const setFoo = createAction((state, payload) => ({ ...state, foo: payload }))
          export const setBar = createAction((state, payload) => ({ ...state, bar: payload }))
          

          doMultipleActions({
            foo: 1,
            bar: 2
          })
          

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


          1. Grammka
            26.09.2016 16:38

            мимо.


          1. brusher
            26.09.2016 22:42

            По-моему тут возникает проблема зависимостей.

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

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

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

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

            Надеюсь я понятно изложил пример. Если я не прав, то поправьте меня. Спасибо.


            1. Grammka
              26.09.2016 22:49

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


              1. brusher
                26.09.2016 23:33

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

                // actions
                export const login = (username, password) => (dispatch) => {
                  dispatch({
                    type: 'API_REQUEST',
                    payload: {
                      uri: '/login',
                      body: { username, password },
                    },
                  });
                
                  setTimeout(
                    () => dispatch({
                      type: 'API_RESPONSE',
                      payload: {
                        uri: '/login',
                        response: 401,
                      },
                      error: true,
                    }
                  ), 5000);
                };
                
                // users/reducer
                export default (userState = {}, action) => {
                  if (
                    action.payload.uri === '/login' &&
                    action.type === 'API_RESPONSE'
                  ) {
                    if (action.error) {
                      return {
                        error: 401,
                      };
                    }
                
                    userState = action.payload.response;
                  }
                
                  return userState;
                };
                
                // progress/reducer
                export default (progressState = [], action) => {
                  if (action.type === 'API_REQUEST') {
                    progressState.push(action.payload.uri);
                  }
                  if (action.type === 'API_RESPONSE') {
                    const i = progressState.indexOf(action.payload.uri);
                    progressState.splice(i, 1);
                  }
                
                  return progressState;
                };
                
                // errors/reducer
                import { ERROR_MESSAGES } from './messages';
                export default (errorsState = [], action) => {
                  if (action.type === 'API_RESPONSE' && action.error) {
                    errorsState.push({
                      requestUri: action.payload.uri,
                      message: ERROR_MESSAGES[action.payload.response],
                    });
                  }
                
                  return errorsState;
                };
                
                // rootReducer
                import users from '/users/reducer';
                import progress from '/progress/reducer';
                import errors from '/errors/reducer';
                
                export default combineReducers({
                  users,
                  progress,
                  errors,
                });
                


                1. brusher
                  26.09.2016 23:38

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

                  • формочка показывает ошибки, если они есть или закрывается если есть юзердата
                  • прогрессбар в данном случае просто крутилка, которая отображается пока длина массива больше 0
                  • ошибки показываются, если есть что показать (в данном примере без удаления и проверок на актуальность)


                  1. Grammka
                    26.09.2016 23:59
                    -1

                    Пример ваш понял, вот набросал:

                    // actions/user.js
                    
                    export const login = createAction({
                      endpoint: '/login',
                      method: 'POST'
                    })
                    
                    
                    // containers/Login/index.js
                    
                    @connect((state) => ({
                      authentication: state.user.authentication
                    }))
                    export default class Login extends React.Component {
                      auth = () => {
                        actions.auth.login({
                          subset: 'authentication',
                          body: {
                            username,
                            password
                          },
                          onResponse: ({ body }) => {
                            // запускаем любой другой асинхронный вызов, если нужно
                          }
                        })
                      }
                    
                      render() {
                        const { authentication } = this.props
                    
                        if (authentication.pending) {
                          return (
                            <Loader />
                          )
                        }
                    
                        if (authentication.error) {
                          return (
                            <div>Authentication ERROR</div>
                          )
                        }
                    
                        return (
                          <div>
                            <AuthForm onSubmit={this.auth} />
                          </div>
                        )
                      }
                    }
                    

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


                  1. Grammka
                    27.09.2016 00:02
                    -2

                    Забыл описание добавить к коду: (возможно не оч корректный пример был) т.к. внутри есть 3 состояния, то и через коннект в компонент попадают эти состояния, которые можно использовать pending — рисуем лоадер, error — рисуем ошибку, data есть — рисуем ее. И не нужно городить однотипную логику каждый раз… ну или изобретать все-таки свой велосипед прийдется чтобы не плодить код внутри файлов =/


                    1. brusher
                      27.09.2016 00:23
                      +1

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

                      п.с.
                      Минусы не мои :)


                      1. Grammka
                        27.09.2016 00:30
                        -1

                        Т.е. вы внутри одного файла экшнов хотите вызывать редьюсеры из разных файлов?

                        п.с.
                        К минусам необоснованным на хабре я привык… пофигу)


                        1. brusher
                          27.09.2016 00:50

                          Вернее будет сказать, что хочу чтобы различные редюсеры могли реагировать на одни и те же экшены :)


                      1. Grammka
                        27.09.2016 10:51

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

                        Что мешает тогда сделать так:

                        // actions/index.js
                        import actions from './redbox-actions'
                        
                        
                        export const someAction = () => {
                          actions.user.authStart()
                          
                          request()
                            .then((result) => {
                              actions.user.authSuccess(result)
                              actions.user.authComplete()
                            })
                            .catch((err) => {
                              actions.user.authError(err)
                              actions.user.authComplete()
                            })
                          
                          //...
                        }
                        
                        export default actions
                        

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


                        1. brusher
                          27.09.2016 11:41

                          … и передаете тип вызываемого редьюсера. Чтобы вызвать 3 редьюсера придется сделать 3 диспатча.

                          Это утверждение не верно. В Redux есть экшены, которые изначально являются простыми объектами с определенной структурой (Flux Standart Actions), который содержит в том числе тип экшена. Тип же это любая строка, циферка или что угодно, с чем мы сможем потом сравнить (а чтобы избежать ошибок — их выносят в отдельный файлик констант). И есть редюсеры, которые комбинируются с помощью combineReducers. То есть фактически редюсер всегд один, но может состоять из подмножества небольших редюсеров.

                          Что происходит во время диспатча — экшен прокидывается в этот самый единственный редюсер, а тот в свою очередь прокидывает экшен внутрь всех дочерних, каждый вовзвращает свой кусочек стейта и в итоге мы получаем новый стейт.
                          То есть, если не связывать тайпы с редюсерами, а чтобы тайп был фактом действия (как это рекомендует сам Дэн Абрамов), то получится что экшентайпы будут выглядеть, например, так:
                          COUNTER_TICK
                          API_REQUEST
                          MESSAGE_SENDED
                          MESSAGE_READED
                          и т.д.

                          При этом любой редюсер может реагировать на любой из экшенов. В примерах Redux-а это делается с помощью switch(action.type) {… } внутри любого из них.


                          1. Grammka
                            27.09.2016 11:48

                            Все, я вас, кажется, понял… вы хотите один и тотже ЭКШНТИП назначать в switch case разных редьюсеров?


                            1. raveclassic
                              27.09.2016 12:01

                              Именно!


                          1. TheShock
                            27.09.2016 20:41

                            То есть, если не связывать тайпы с редюсерами, а чтобы тайп был фактом действия (как это рекомендует сам Дэн Абрамов), то получится что экшентайпы будут выглядеть, например, так:
                            COUNTER_TICK
                            API_REQUEST
                            MESSAGE_SENDED
                            MESSAGE_READED
                            и т.д.


                            Я вот не понимаю, зачем придумывать новые страшные названия для самых обычных «событий»? Ну кроме желания показать, что ты модный и молодежный? Может если бы не придумывали свои названия для старых вещей, то люди легче понимали б вас. Вот придумали "Presenter-Component" как синоним связке «View-Controller». Зачем? При этом все, что написано на редакс — тонкая Model. Но нет. «Мы не используем грязный MVC, как эти задроты».


                            1. brusher
                              28.09.2016 12:48

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

                              Например:
                              Действия (аналог событий) — главное отличие от привычных нам событий, что на них нельзя подписываться для изменений данных в стейте (модели), их можно только вызывать. Но есть всякие штуки типа redux-saga, которые позволяют создавать подписки для получения неких сайд-эффектов, но это другая песня и делать это нужно осторожно.
                              Редюсеры (аналог моделей) — все модельки упаковываются в один стор. Главная особенность, что никто напрямую не дергает модельку, а каждая моделька сама применяет к себе действия (события) и, возможно, в следствии меняется. И стор всегда возвращает консистетное состояние всей системы (всех моделей).
                              Контейнеры (statefull components) (аналог контроллеров) не должен выполнять какой-то логики обработки данных, он должен ее лишь получать и назначать вьюхе (все как в правильном активном MVC). И привязывать вызов экшенов (! но не решать какую модельку когда потрогать и как). И каждый такой контейнер может работать сразу с множеством «моделей» (получать любые данные из стора), хотя привычные всем контроллеры чаще всего работают с одной моделькой. А в связке с редуксом и вовсе многие рекомендуют делать их тоже стейтлесс и все стейты хранить в общем стейте — это снимает множество боли.
                              Компоненты — да, привычные всем тупые вьюхи.


                              Отсюда следует вывод, что называть это MVC не верно, т.к. оно имеет ряд концептуальных отличий и одно из самых жирных — отсутствие pub/sub. (с тем же успехом можно MVP и MVVM называть MVC)

                              Не подумайте что я ярый фанат реакта, но из всех ангуляров и бекбонов он мне доставляет меньше всего боли при разработке сложных приложений (вспоминается цитата из выступления Александра Соловьева про FRP и кложуру «У меня уже все болит от этих чик-чик»). Но я все чаще смотрю в сторону FRP библиотек и языков.


                            1. raveclassic
                              28.09.2016 12:52

                              Это не «новые страшные названия», а старые добрые команды: http://stackoverflow.com/a/4964228/1961479


                              1. xGromMx
                                28.09.2016 14:06

                                Мне казалось что это все отсюда пошло https://ru.wikipedia.org/wiki/CQRS


        1. Grammka
          26.09.2016 16:39
          -1

          К сожалению исправить уже не могу… написал чушь… что мешает сделать doMultipleActions обычным методом через экспорт и вызывать несколько экшнов / редьюсеров сразу? Тоже самое и с асинхроннщиной…


  1. bustEXZ
    26.09.2016 16:32

    Что-то я в простых событиях не вызываю диспатч, только в асинхронных. Код ниже прекрасно работает

    export const testAction = () => ({
        type: TEST_ACTION
    });
    


    1. Grammka
      26.09.2016 16:34
      -3

      потому что он вызывается под капотом?


  1. Shablonarium
    26.09.2016 20:48
    -5

    Нельзя такие изображения постить в общественном пространстве. Что за маньячество? Куда смотрит администрация?


    1. Grammka
      26.09.2016 20:52
      -1

      Спешл фо ю переделал…


  1. hell0w0rd
    27.09.2016 00:51

    А где тесты?