Как уменьшить количество и увеличить читаемость кода в react-redux, redux-saga

В этой статье я хотел бы поделиться своим опытом использования связки react-redux и redux-saga, а точнее, какой «велосипед» я использую, для уменьшения количества однотипного кода и упрощению его восприятия.

Что меня не устраивало


Библиотеки react-redux и redux-saga просты, гибки и удобны, однако имеют избыточность кода. Основные элементы это:

  1. Фабрики событий


    const actionCreatorFactory = type => payload => ({ type, payload });
    
    export const INITIALIZE = 'INITIALIZE';
    
    export const ON_FIELD_CHANGE = 'ON_FIELD_CHANGE';
    export const onFieldChange = actionCreatorFactory(ON_FIELD_CHANGE);
    
    export const HANDLE_FIELD = 'HANDLE_FIELD';
    export const handleField = actionCreatorFactory(HANDLE_FIELD);
    
    export const GO_TO_NEXT_STEP = 'GO_TO_NEXT_STEP';
    export const goToNextStep = actionCreatorFactory(GO_TO_NEXT_STEP);
    

    В таком виде меня смущает несколько вещей:

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

    — если вы забыли структуру пайлоада(payload), что бы его вспомнить, надо перейти к reducer/saga, где используется это событие, и посмотреть что там нужно передавать
  2. Редьюсеры


    import getInitialState, {
      formDataInitialState as initialState,
    } from '../helpers/initialState';
    import { HANDLE_FIELD_DONE, ON_FIELD_CHANGE, RESET } from '../actionCreators';
    
    export default (state = initialState, { type, payload }) => {
      switch (type) {
        case RESET: {
          return getInitialState().formDataInitialState;
        }
    
        case ON_FIELD_CHANGE: {
          const { name } = payload;
          return {
            ...state,
            [name]: '',
          }
        }
    
        case HANDLE_FIELD_DONE: {
          const { name, value } = payload;
          return {
            ...state,
            [name]: value,
          }
        }
      }
    
      return state;
    };
    

    Тут в целом напрягает только использование конструкции switch
  3. Саги


    import { all, put, select, fork, takeEvery } from 'redux-saga/effects';
    import { runServerSideValidation } from '../actionCreators';
    import { HANDLE_FIELD } from '../actionCreators';
    
    function* takeHandleFieldAction() {
      yield takeEvery(HANDLE_FIELD, function*({ payload }) {
        const { validation, formData } = yield select(
          ({ validation, formData }) => ({
            validation: validation[payload.name],
            formData,
          })
        );
    
        const valueFromState = formData[payload.name];
    
        if (payload.value !== valueFromState) {
          const { name, value } = payload;
          const { validator } = validation.serverValidator;
          yield put(
            runServerSideValidation({
              name,
              value,
              validator,
              formData,
            })
          );
        }
      });
    }
    
    export default function* rootSaga() {
      yield all([fork(takeHandleFieldAction())]);
    }
    

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

    import { all, put, select, fork, takeEvery } from 'redux-saga/effects';
    import { runServerSideValidation } from '../actionCreators';
    import { HANDLE_FIELD } from '../actionCreators';
    
    function* takeHandleFieldWorker({ payload }) {
        const { validation, formData } = yield select(
          ({ validation, formData }) => ({
            validation: validation[payload.name],
            formData,
          })
        );
    
        const valueFromState = formData[payload.name];
    
        if (payload.value !== valueFromState) {
          const { name, value } = payload;
          const { validator } = validation.serverValidator;
          yield put(
            runServerSideValidation({
              name,
              value,
              validator,
              formData,
            })
          );
      }
    }
    
    function* takeHandleFieldWatcher() {
      yield takeEvery(HANDLE_FIELD, takeHandleFieldWorker);
    }
    
    export default function* rootSaga() {
      yield all([fork(takeHandleFieldWatcher())]);
    }
    

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

Как я пытаюсь решить эти проблемы


Давайте по порядку.

  1. Фабрики событий


    import { actionsCreator, payload } from 'sweet-redux-saga';
    
    @actionsCreator()
    class ActionsFactory {
      initialize;
      
      @payload('field', 'value')
      onFieldChange;
      
      @payload('field')
      handleField;
      
      @payload('nextStep')
      goToNextStep;
    }
    

    создаем класс с аннотацией actionsCreator(). При создании экземпляра класса, полям не имеющим значения(initialize;/onFieldChange;/handleField;/gpToNextStep;) будет присвоен привычный нам action creator. Если событие содержит данные, имена полей передаем через аннотацию payload(...[fieldNames]). После преобразования предыдущий пример будет выглядеть вот так:

    class ActionsFactory {
      initialize = () => ({
        type: 'INITIALIZE',
        payload: undefined,
      });
    
      onFieldChange = (field, value) => ({
        type: 'ON_FIELD_CHANGE',
        payload: {
          field,
          value,
        },
      });
    
      handleField = field => ({
        type: 'HANDLE_FIELD',
        payload: {
          field,
        },
      });
    
      goToNextStep = nextStep => ({
        type: 'GO_TO_NEXT_STEP',
        payload: {
          nextStep,
        },
      });
    }
    

    так же у полей будут переопределены методы toString, toPrimitive, valueOf. Они будут возвращать строковое представление типа события:

    const actionsFactory = new ActionsFactory();
    console.log(String(actionsFactory.onFieldChange)); //Вернет 'ON_FIELD_CHANGE'
    
  2. Редьюсеры


    import getInitialState, {
      formDataInitialState as initialState,
    } from '../helpers/initialState';
    import { HANDLE_FIELD_DONE, ON_FIELD_CHANGE, RESET } from '../actionCreators';
    import { reducer } from '../../../leadforms-gen-v2/src/decorators/ReducerDecorator';
    
    @reducer(initialState)
    export class FormDataReducer {
      [RESET]() {
        return getInitialState().formDataInitialState;
      }
    
      [ON_FIELD_CHANGE](state, payload) {
        const { name } = payload;
        return {
          ...state,
          [name]: '',
        };
      }
    
      [HANDLE_FIELD_DONE](state, payload) {
        const { name, value } = payload;
        return {
          ...state,
          [name]: value,
        };
      }
    }
    

    создаем класс с аннотацией reducer([initialState]). При создании экземпляра класса, на выходе получится функция принимающая состояние и экшен, и возвращающая результат обработки экшена.

    function reducer(state = initialState, action) {
      if (!action) {
        return state;
      }
    
      const reducer = instance[action.type];
      if (reducer && typeof reducer === 'function') {
        return reducer(state, action.payload);
      }
    
      return state;
    }
    
  3. Саги


    import { all, put, select, } from 'redux-saga/effects';
    import { runServerSideValidation } from '../actionCreators';
    import { HANDLE_FIELD } from '../actionCreators';
    import { sagas, takeEvery, filterActions } from 'sweet-redux-saga';
    
    @sagas()
    class MySagas {
      @takeEvery([HANDLE_FIELD])
      @filterActions(
        ({state, payload }) => state.formData[payload.name] === payload.value
      )
      * takeHandleFieldAction({ payload }) {
        const { validation, formData } = yield select(({ validation, formData }) => ({
          validation: validation[payload.name],
          formData,
        }));
    
        const { name, value } = payload;
        const { validator } = validation.serverValidator;
        yield put(
          runServerSideValidation({
            name,
            value,
            validator,
            formData,
          })
        );
      }
    }
    
    export default function* rootSaga() {
      yield all([
        new MySagas(),
      ]);
    }
    

    создаем класс с аннотацией sagas(). При создании экземпляра класса получаем генератор функций, вызывающий все поля класс помеченных аннотацией takeEvery([...[actionTypes]]) или takeLatest([...[actionTypes]]) в отдельном потоке:

    function* mySagas() {
      yield all([fork(mySagas.takeHandleFieldAction())]);
    }
    

    так же с полями можно использовать аннотацию filterActions({ state, type, payload }), в этом случае сага будут вызвана только если функция вернет true.

Заключение


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

Эти аннотация я вынес в пакет sweet-redux-saga. Если есть другие решения, буду рад, если поделитесь со мной.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 142

    +1
    Как уменьшить количество и увеличить читаемость кода в react-redux, redux-saga?
    — Забыть уже наконец об этом ужасе, который тянется с давних пор, открыть глаза и использовать MobX.
      0
      не работал с MobX, но посмотрел пару статей на хабре только что, и не совсем понятно пару моментов:
      1) Как на 1 событие изменить 2 независимые части стора?
      2) Как создавать динамические хранилища? имею ввиду, если список полей одного из куска стора, известен только во время выполнения?

      ну и такая мелочь, что бизнес логика и обновление стора идет в одном месте, может это во мне привычка говорит, но идея эта мне не очень нравится
        +1
        Не нужно опираться на то, что написано в статьях, нужно просто принимать это к сведению, но думать важно именно своей головой, а не статьями и тем что говорят и пишут «авторитеты». MobX можно использовать вообще в разных вариациях, он не обладает ограничениями и не загоняет в рамки.
        Вот вам живой пример — codesandbox.io/s/ecstatic-cloud-l956n

        Вообще на дворе 2020 год и мне кажется дико когда React developer'ы не знают что такое MobX и даже не пробовали его, ладно ещё в 2016 году, но в 2020 это нонсанс конечно)
        P.S. я заменил Redux на MobX ещё в 2016 году и с тех пор получаю удовольствие от работы с реактом, до этого было больше боли из-за редакса.
          0
          будь MobX серебряной пуле, что-то мне подсказывает, он бы использовался на всех проектах, на которые я приходил. но это ладно, мне и вправду интересно, у Вас 4 года опыта в MobX, как там описывается логика взаимодействия между несвязанными компонентами?
          пример:
          изменилось поле А, я его проверил, оно корректно, мне надо сделать запрос на бэк, получить данные других полей и обновить их так же. (изменили ZIP code, надо получить город и штат)
          это все будет в том же месте, где я буду обновлять основное поле в перемешку?
            0
            Да как душе угодно это можно сделать, я же говорю он не накладывает ограничений. Вы просо работаете с ним как с любым другим объектом/классом, просто у него есть реактивные свойства на изменения которых автоматически реагирует реакт, так же вы сами можете назначить реакции на изменения тех или иных свойств. Я же кидал сссылку выше там можете посмотреть как оно работает и поэксперементировать сразу же.
              0
              ну я покопал в том направлении, в принципе выглядит интересно, надо попробовать )
                0
                посмотрел вакансии фронтов, и на том сайте было 90 вакансий с упоминанием  redux и только 4 с mobx. Интересно, почему…
                  +2
                  — Есть проекты которые начинаются с нуля.
                  — Есть проекты где начинают писать версию «2.0».
                  — Есть проекты которые можно рефакторить и заменять redux на mobx.
                  — Во многих вакансиях указан только Redux, но по факту никто не против внедрить mobx.

                  Критической проблемы в этом нет, да большинство существующих проектов к сожалению написаны на redux'e, но тут причина банальная, люди не поднимают голову и не смотрят по сторонам. И/или не рискуют попробовать что-то другое.

                  Я например с 2016 года проекты только с mobx'ом пишу и вообще кайфую. До этого меня раздражал реакт из-за редакса, а с момента как узнал про mobx то эта связка стала идеальной для меня.
                    +1
                    это все понятно, но все же, 4 против 90, ладно там 30/90 ну 20/90, а так, Вы говорите с 2016, так мало кто перешел? vue с 2015 первая версия, и на том же сайте соотношение react/vue 283/83
                      +1

                      Ну у нас до недавнего времени MobX не упоминался в вакансиях, собственно даже знание React "будет плюсом", поскольку практика показла, что нормальный фронтендер довольно быстро осваивает и React, и MobX

                        0
                        в целом, мне интересно попробовать, я лишь высказал вещи, которые бросились в глаза ;)
                        0
                        Вам же говорят, написано одно, по факту другое
                      +2
                      Потому-что:
                      * Redux раньше появился.
                      * Redux хорошо пропиарили в начале и до сих продолжают.
                      * Как следствие пиара, большинство работали только с redux.
                      * Как следствие пиара, большинство библиотек компонентов, темплейтов используют redux.
                      * В redux также описана архитектура, поэтому понятно как с ним сделать одинаковую структуру для разных страниц проекта. В случае mobx нужен соответствующий опыт, либо самому до этого додуматься.
                      * Абрамов прям супер ит-евангелист. Несмотря на его небольшой опыт, к нему прислушиваются большинство. Какую бы чушь он не продвинул в мире реакта, большинство реакт-разработчиков будут ею пользоваться.
                      * Команды бэкендеров, фулл-стэков переходя на написание SPA, смотрят, что сейчас популярно. Ага, react, redux. Будем тоже это использовать. Новички в веб-разработке поступают аналогично.
                        +2
                        Плюс многим удобно, что редакс — это говнокод возведённый в идеологию
                          0
                          Плюсую и дает возможность говнокодить и это будет считаться нормой в этом сообществе. Что конечно крайне печально, потому что иногда это наследие достается другим(
                +2
                По мне идея триггерить события и затем в других местах множественно их отлавливать и на это как-то реагировать конечно интересная, но на практике все это дело дебажить и пытаться читать сам data flow крайне утомляет.
                  +1
                  Так на 1 событие изменить 2 независимые части стора?

                  Очень просто: взять и изменить.


                  @action foo() {
                      store.bar.x++;
                      store.baz.y = "Hello, world!";
                  }

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


                  Как создавать динамические хранилища? имею ввиду, если список полей одного из куска стора, известен только во время выполнения?

                  Используя observable map, observable object или же можно класс динамически создать (у нас же javascript, в конце концов).


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

                  Пишите в разных, никто не запрещает же

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

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

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

                      Ну а сейчас у вас, видимо, огромные саги, вызывающие разные, не связанные экшены...


                      Если же огромных саг у вас не наблюдается — почему экшены в MobX должны магическим образом стать таковыми?


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

                      Не понимаю о чём вы пишете.

                        0
                        Если же огромных саг у вас не наблюдается — почему экшены в MobX должны магическим образом стать таковыми?

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

                        если я правильно понял
                        Пишите в разных, никто не запрещает же

                        Вы имели ввиду, вынести логику в сервисы, и вызывать их из экшенов
                        @action foo() {
                            store.bar.x++;
                            store.baz.y = someService.someProcessing(store.bar); //какая-то бизнес логика
                        }

                        и это, мне не очень нравится, я об этом говорил выше
                          +3

                          Если вам так нравится слабая связность — не одна только саги её вам могут обеспечить. Простейший EventEmiter пишется за пару минут...


                          и это, мне не очень нравится, я об этом говорил выше

                          Альтернатива-то какая?

                            0
                            Если вам так нравится слабая связность — не одна только саги её вам могут обеспечить. Простейший EventEmiter пишется за пару минут...

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

                            я без понятия, в сторону mobx только начал смотреть, может Вы знаете решения
                  +1
                  Как на 1 событие изменить 2 независимые части стора?
                  Непонятно, что за события.

                  Ну, допустим, нам по веб-сокетам инфа какая-то пришла? У вас есть листенер сокетов. В этом листенере должны быть ссылки на 2 нужные части стора. Когда приходит событие — обновляете одну часть стора, а потом вторую часть стора.

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

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

                    я имел ввиду, ввод данных пользователем, и я понимаю, что из одно экшена мы можем изменять весь стор, но это приведет в сильно большим экшенам(action()). Но это вначале были мои мысли, сейчас я разобрался как этого избежать.
                    Это вообще непонятно. Как работать неизвестно с чем? Вы к каким полям обращаться потом будете? Можно практический пример? Я за свою практику никогда не видел такой странной задачи.

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

                    обновление стора правильно лежит, меня смутило, что там не только обновление стора, но и вся логика лежит, в случае с redux-saga, редьюсеры отвечают только за обновление стора, а бизнес логика лежит в сагах
                      0
                      Нужен инструмент для удобного создания форм, есть набор полей
                      Я б делал это через массив типизированных инстансов. С массивом в JS работать значительно удобнее, чем с хешем.
                        0
                        Я б делал это через массив типизированных инстансов. С массивом в JS работать значительно удобнее, чем с хешем.

                        если это сделать через массив, очень много придется перебирать эти массивы для поиска, изменения и удаления. Как это выглядит в коде, мне абсолютно не понравилось, а с хэшами, немного приятнее, но тоже не обошлось без неудобств.
                          0
                          очень много придется перебирать эти массивы для поиска, изменения и удаления
                          Учитывая, что в JS при добавлении в хеш и удалении из хеша пересоздаётся внутренний класс — выгоднее делать это классически через фильтрацию, к примеру. А сортировать как-то иначе хеш вообще нереально)
                            0
                            Учитывая, что в JS при добавлении в хеш и удалении из хеша пересоздаётся внутренний класс — выгоднее делать это классически через фильтрацию, к примеру. А сортировать как-то иначе хеш вообще нереально)

                            Вопрос не в производительности, а в читаемости. для обновления значения в сторе используя массив, код будет выглядеть примерно так:
                              [fieldsActions.changeFieldValue] = (state, payload) => {
                                const index = state.indexOf(payload.fieldName);
                                const nextState = [...state];
                                nextState.splice(index, 1, {
                                  ...(state[payload.fieldName] || initialFieldData),
                                  enteredValue: payload.value,
                                });
                                return nextState;
                              };
                            

                            используя хэш так:
                            [fieldsActions.changeFieldValue] = (state, payload) => ({
                                ...state,
                                [payload.fieldName]: {
                                  ...(state[payload.fieldName] || initialFieldData),
                                  enteredValue: payload.value,
                                },
                              });
                            

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

                              @action deleteItemByIndex (index: number) {
                                this.items = this.items.filter(it => it.index !== index);
                              }
                              


                              А изменение вообще просто делается:
                              field.value = newValue


                              Просто ваш хреновый инструмент заставляет вас крутиться как ужа
                                +1

                                В Mobx это пишется куда проще:


                                @action
                                function changeFieldValue(payload) {
                                    payload.field.enteredValue = payload.value;
                                }

                                Причём это работает одинаково что для массива, что для хеша...

                                  0
                                  очень странный код
                                  payload.field.enteredValue = payload.value;

                                    +1

                                    А что странного-то? Ну, за исключением сомнительной необходимости функции changeFieldValue, но её не я придумал.

                                      0
                                      странно то, что Вы присваиваете значение в пайлоад, беря значение из того-же пайлоада
                                        0

                                        А что делать, раз пайлоад такой получился? Всего-то fieldName (идентификатор модели) на field (саму модель) заменил, как это в mobx принято.


                                        Я мог бы вовсе убрать функцию changeFieldValue, но тогда кто-то мог бы спросить: "а куда функция делась?" :-)

                                          +1
                                          Вы зря оставили куски говнокода из оригинального примера на редаксе) Просто там так много кода, что то, что он хреновый тяжело увидеть.

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

                                          class FieldModel {
                                            @observable title;
                                            @observable value;
                                          
                                            @action setValue = (newValue) => {
                                              this.value = newValue;
                                            }
                                          }
                                          


                                          <FieldView
                                            title={fieldModel.title}
                                            value={fieldModel.value}
                                            onChange={fieldModel.setValue} />
                                            0
                                            В мобиксе, конечно, нету как в Редаксе никаких странных пейлоадов

                                            В защиту redux (да знаю, что меня за одно только это слово побьют) скажу, что нет реально никаких причин использовать payload. Это какое-то коллективное помешательство. Единственное зарезервированное поле в action-ах это type. Моя не понимать, зачем все так страдают.

                                              0

                                              От замены payload на action странность не пропадает...

                                                0
                                                По-вашему, как надо, вместо использования payload?

                                                Разок пришлось писать на redux вместе с другой командой. Местами там писали примерно такое:
                                                // компонент
                                                dispatch(updateUser(id, fisrtName, lastName, email, phone));
                                                
                                                //action
                                                updateUser = (id, fisrtName, lastName, email, phone) => ({
                                                    type: UPDATE_USER,
                                                    id,
                                                    firstName,
                                                    lastName,
                                                    email,
                                                    phone,
                                                });
                                                
                                                //saga
                                                function* updateUser({ id, fisrtName, lastName, email, phone }) {
                                                     yield userApi.update(id, fisrtName, lastName, email, phone);
                                                
                                                //api
                                                const update = async (fisrtName, lastName, email, phone) => api.patch(`/user/${id}/`, {
                                                    fisrtName, lastName, email, phone
                                                });


                                                Как по мне, чем меньше участков кода знает о структуре передаваемых данных, тем лучше. Меньше кода и меньше вероятность ошибок. Вариант с payload:
                                                // компонент
                                                dispatch(updateUser({ id, fisrtName, lastName, email, phone }));
                                                
                                                // action
                                                updateUser = (payload) => ({
                                                    type: UPDATE_USER,
                                                    payload
                                                });
                                                
                                                //saga
                                                function* updateUser({ payload }) {
                                                     yield userApi.update(payload);
                                                
                                                //api
                                                const update = async ({ id, ...payload }) => api.patch(`/user/${id}/`, payload);


                                                  0
                                                  Аж глаз дергается видя это
                                                    +1

                                                    Да я абсолютно убеждён, что большая часть classic-redux way это мусор. И слово payload в том числе.


                                                    Как по мне, чем меньше участков кода знает о структуре передаваемых данных, тем лучше

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


                                                    Меньше кода и меньше вероятность ошибок

                                                    Вы кстати хороший такой антипаттерн нарисовали. У вас всё пробрасывается вслепую вплоть до api. Это очень плохо. Особенно без TypeScript-а. По сути у вас бомба замедленного действия. Вы напрямую связали удалённые участки кода из разных слоёв. Малейшее изменение с одной стороны приводит к непредсказуемым (и возможно трудноотлавливаемым) багам в конце цепочки.

                                                      0
                                                      Более того redux way уже давно сам по себе мусор) Но народ до сих пор живет в прошлом, это как попасть в страну где до сих пор советский союз, еда по талонам и дефицит товаров)
                                                        0
                                                        Мне тоже оба варианта не нравятся, оба плохие. Особенно без типизации, как в моем примере. Но иногда приходиться из двух зол выбирать меньшее.

                                                        Кстати, вы не сказали, что вместо payload использовали бы в redux, если бы пришлось на нем писать? Или вы имели ввиду просто давать этому объекту более подходящее именование?
                                                          0
                                                          Кстати, вы не сказали, что вместо payload использовали бы в redux, если бы пришлось на нем писать? Или вы имели ввиду просто давать этому объекту более подходящее именование?

                                                          Просто все необходимые поля располагаю прямо внутри action-а. На 1-м его уровне. Там же где и поле type. К чёрту бюрократию.

                                                            0
                                                            Ну не знаю. Как на меня — в этот момент нарушается типизация. То есть Update должен быть формата
                                                            { type: string, value: Partial<Unit> }


                                                            Эти комбинированные типы — как-то пошло. Всё-таки тип на более высоком уровне иерархии.

                                                            Просто не должно быть дурацких абстрактных названий типа «payload», дурацкой динамической типизации с совершенно непонятными и неконтролируемыми структурами и, конечно, дурацкого редакса
                                                              0

                                                              Дык в TS структурная типизация, а не номинативная. Не испытываю решительно никаких проблем с этим. Все типы как нужно выводятся и никакой type где попало не всплывает. Правда я забыл уточнить что у нас 100500 своих велосипедов и суммарно это всё не похоже на redux (хотя он всё ещё под капотом). Это несколько меняет дело. Если делать всё по заветам Ильич… Абрамова, то там везде грусть конечно. Но грустить некогда, надо писать копипасту во имя ынтерпрайза


                                                              { type: string, value: Partial<Unit> }

                                                              Ну я и не предлагал поля Unit пихать на 1-й уровень action-а. Но помимо value: Partial<Unit> там ещё могут быть какие-нибудь дополнительные поля, не из Unit. И всё это будет на верхнем уровне вместе с value (maybe diff?). Т.е. я не предлагаю уплощать всё до неадеквата. Я просто предлагаю избавиться от лишнего уровня иерархии в виде некоего payload

                                                                0
                                                                Опс. Сам критиковал и механически написал `value` вместо `unit`.
                                                                Да, в таком виде согласен, пейлоад — просто мусор.
                                                                { type: string,
                                                                  unit: Partial<Unit>,
                                                                  options: IOptions }
                                                        0
                                                        Откуда у фанатов редакса страсть к отвратительным названиям? Почему когда вы хотите передать конкретную переменную — вы называете её неправильно? Почему payload, есть внутри лежит вполне себе user? У вас и так код неподдерживаемый, а вы ещё и усложняете.
                                                          0
                                                          Я сейчас на скорую руку пример написал, не заботясь об именовании. Написал примерно, что видел в коде redux проекта. Остальные вопросы к фанатам редакса, а я не фанат и стараюсь избегать проектов на нем.
                                                          0
                                                          Как по мне, чем меньше участков кода знает о структуре передаваемых данных, тем лучше.

                                                          Вечная борьба между связанностью и связностью.

                                        0

                                        Массивы перебирать вам не придётся если вы сохраните ссылку на элемент вместо имени поля.

                                          0
                                          Массивы перебирать вам не придётся если вы сохраните ссылку на элемент вместо имени поля.

                                          не совсем понял Вашу мысль, я инициализировал список полей как массив, потом пользователь вводит данные в одно из полей и мне надо в торе изменить значение value. Как это сделать, не перебирая массив в поисках нужного поля?
                                            0
                                            store.form.fields.map(field => {
                                                // ...
                                            
                                                return <input ... onchange={e => field.setRawValue(e.target.value)} />
                                            });

                                            Для производительности тут надо обязательно отдельный компонент выделить (или хотя бы Observer), но это уже детали.

                                              –1
                                              прикольно конечно, это же в рендере, а как тут добавить серверную валидацию по мимо обновления значения?
                                                0

                                                У вас же есть метод setRawValue. В него любую валидацию можно добавить...

                                                  0
                                                  Вы наверное путаете валидацию на фронте, с серверной, она будет асинхронна, и влаг валидности поля будет установлен после ответа сервера, а значение в поле установить сразу надо
                                                    0

                                                    Ничего я не путаю. Что вам мешает сразу установить значение в поле, и одновременно инициировать запрос на сервер?

                                                      0
                                                      все, я понял что вы имели виду, спасибо за ваше потраченное время ;)
                                      +1
                                      обновление стора правильно лежит, меня смутило, что там не только обновление стора, но и вся логика лежит, в случае с redux-saga, редьюссеры отвечают только за обновление стора, а бизнес логика лежит в сагах
                                      Я придерживаюсь того, что в любом сторе могут храниться только данные этого стора и методы для работы с этими данными (кэширование, преобразование данных перед их сохранением в стор или передачей в компонент).
                                      Фактически этим и занимается mobx, vuex и даже redux. Только в redux это усложнено и размыто по куче ненужных сущностей.

                                      Еще недавно стал считать нормальным подход (но не пробовал его), где api метод (или посредник с сайд-эффектами, вроде redux-saga) вызывается в методах стора и после выполнения стор сохраняет пришедшие данные. Но только вызов, преобразование и сохранение данных. Никакой сетевой логики, сайд эффектов в коде стора.

                                      Наличие другой логики, кроме описанной (но я может что-то и упустил), превращает стор в какой-то контроллер.

                                      Может в примерах mobx и не сделано, как я описал. Но там можно так организовать архитектуру. Конечно, минус mobx-у за то, что нет рекомендаций (я не видел), best practices по архитектуре.
                                        0
                                        Только в redux это усложнено и размыто по куче ненужных сущностей.

                                        Я как раз люблю слабую связанность кода, и разбитие на слои, мне больше нравится, чем иметь только контроллер и сервисы
                                          0
                                          Ну, я как раз описал один из слоев и упомянул пару других.
                                          В более полном виде выглядело бы так:
                                          * слой взаимодействия с сервером
                                          * слой сайд-эффектов
                                          * слой стора (хранения глобальных данных и уведомление об изменениях в этих данных)
                                          * слой логики компонента (custom hooks)
                                          // я тоже предпочитаю разбивать на слои и не считаю нормальным, что одну и же логику можно писать в двух разных сущностях (в компоненте и в custom hook)
                                          * слой view (jsx)
                                  0
                                  зачем mobx, если есть реакт хуки? Забыть вообще о реактивных стейт-контейнерах
                                    +2

                                    Зачем о них забывать?

                                      –3
                                      Затем, что useState и useReducer предоставляют те же опции, что стейт контейнеры. При этом не создают гемороя и идиотизма, свойственного приложениям со стейт контейнерами
                                        +1
                                        1) Гемороя? Идиотизма? Можно по подробнее пожалуйста.
                                        2) Быть может это просто вы пока слишком далеки от этого из-за нехватки квалификации, не понимая асинхронности, не понимая реактивности?
                                          +2

                                          Но хуки из коробки не предоставляют никакого механизма подписки на изменения частичного стейта из useReducer для компонентов-потомков в глубине дерева.

                                            –4
                                            Это и не нужно, прямая передача колбэков в пропсах в разы проще. А для глобальных сущностей есть контекст
                                              0
                                              Мда… Тяжелый случай.
                                      0

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

                                        +1
                                        Хорошая шутка
                                          0

                                          о, а можно пруфы про низкую производительность, сравнения какие-нибудь?

                                            –1
                                            Какие пруфы? Ссылки на статьи чтоли?) Вы тоже шутник походу) Единственный пруф это самостоятельные тесты или исходники которые можно запустить любому и убедится в цифрах и правильности этих сравнений. Все остальное это пустые слова и брехня.
                                        +1
                                        Сами создаем себе проблемы, а потом героически их преодолеваем.

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

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

                                        Что бы порекомендовал — просто не усложнять. Или посмотреть в сторону более простых решений. Каких, не буду рекомендовать, что бы не разводить холивар.
                                          0
                                          понятна точка зрения, но как как упростить пока сложно представить, наверное не достаточно опыта еще, что бы придумать, что-то проще (
                                            +1
                                            Упрощение начинается с архитектуры проекта.

                                            Вся логика выносится в service layer. Это наша бизнес логика приложения. Запросы к серверу, обработка данных, кеширование итд. Все это находится в этом слое. Обычно у меня этот слой не зависит от какий либо фреймворков.

                                            Далее, ппределяются данные, которые будут использоваться в двух местах одновременно. Как пример — профиль пользователя. И если посмотреть, то таких данных действительно очень мало и нет никакого смысла держать и в каком либо сторе. Проще и понятнее обратится к API service layer и взять данные когда они нужны.

                                            Для данных которые будут использоваться в двух местах одновременно — уже выбрать стор. Это может быть redux/mobx/свое решение. У меня в проекте для этого свой велосипед. Маленькое и простое решение, которое покрывает всю необходимую функциональность. И ради трех/четырех пропертей тянуть в проект redux/mobx просто нет смысла.

                                            Сейчас проверил, что данных в сторе всего три объекта.
                                              0

                                              Кстати, есть еще вот такой https://github.com/diegohaz/constate способ обойтись React Context API для не очень сложной логики. В двух словах: оборачиваем custom Hook в Context.


                                              Бонусом идет bottom-up проектирование:


                                              • пишем логику непосредственно в компоненте,
                                              • стало сложно — оборачиваем в custom Hook
                                              • стало нужно в двух местах — оборачиваем custom Hook в Context.

                                              При этом объем рефакторинга минимальный. Вес самой либы 0.5 Kb

                                                –3
                                                Вот это дичь) Зачем такие извращения, когда уже много лет есть шикарный и реактивный стейт менеджер который заменяет как глобальный стейт, так и локальный стейт компонента?
                                                Надеюсь я такое «чудо» не увижу никогда) Рефакторить потом такое, это прям ржавым ножом в самую душу)
                                                  –1

                                                  Ну прелесть этого подхода в том, что он не вносит нового API. Просто расшаривает состояние custom Hook на поддерево компонентов.


                                                  Нравится нам это или нет, но в React по сути придумали свою систему реактивности,


                                                  • с атомами: useState()@observable,
                                                  • производными: useMemo()@computed,
                                                  • и эффектами: useEffect()reaction().

                                                  Часто этого бывает достаточно.


                                                  P.S. Против MobX ничего не имею. Сам начинал с Knockout еще, и был очень рад, когда кто-то реализовал подобную концепцию для React.

                                                    0
                                                    Ну как бы разница в коде и в подходе небо и земля, и победитель тут явно не React custom Hook, а MobX. Поэтому зачем заведомо усложнять себе жизнь? И ещё тем, кто после вас будет работать с этим «замечательным» кодом.
                                                      0

                                                      Проблема в том, что видение авторов React отличается от авторов MobX. И такое чувство, что первые пытаются усложнить жизнь вторым.


                                                      Например, если раньше можно было просто навесить @observer на класс, то теперь с хуками встречаются вот такие чудеса: useObservableSource, потому что props не реактивны.


                                                      Все это печально, и отпугивает новичков от MobX. Поэтому он и не так популярен, как мог бы.


                                                      кто после вас будет работать с этим «замечательным» кодом

                                                      Ну на MobX я видел не менее «замечательный» код, где reaction на reaction-е и subscribe-ом погоняет. Хотя многое можно было запихнуть в @computed. Это все говорит о том, что инструментом нужно еще уметь пользоваться. А не «вот вам MobX и будет счастье».

                                                        0

                                                        Так ведь и сейчас можно взять и навесить @observer на класс...


                                                        Вот где и правда разработчики React усложняют жизнь — так это вот тут: https://github.com/facebook/react/issues/15317

                                                          –5
                                                          Да новички без разницы что используют, меня это мало волнует, я с ними не работаю и работать не буду, нянькой не нанимаюсь и разгребать постоянно за ними тоже, у меня серьезный вопрос к профессионалам, как можно быть профессионалам и использовать до сих пор Redux у меня просто не укладывается это в моей картине жизни разработчика, это реальный мазохизм.
                                                  0
                                                  слой сервиса в проекте есть, но так же используются саги для обработки сложных эффектов, которые были бы очень сложны в прочтении, используя например thunk, а как Вы обрабатываете сложные взаимодействия между независимыми компонентами?
                                                    0
                                                    Можете привести пример сложных взаимодействий?
                                                      0
                                                      ну копировать код или задачи из жиры я не будут, опишу в вольном стиле приблизительно, что для меня сложные эффекты:
                                                      есть блок полей связанных, например личная информация о пользователях, с бэка приходят возможные наборы, взятые из баз партнеров. Эти блоки могут не содержать часть полей, необходимых нам, так же некоторые данные этих полей могут оказаться «плохими».
                                                      Задача: узнать у пользователя какие данные его, если нет его данных предоставить все поля для заполнения. Если узнали какой блок данных пользователя, показать пользователю только те поля, которых у нас нет, а так же поля, данные которых «плохие». Так же могут меняться типы поле и подписи к ним, в зависимости от количества пришедших блоков данных.
                                              +5
                                                +1
                                                В новой версии они прям сильно порадовали. Чем больше «стандартная библиотека», тем больше ясности и меньше споров по поводу реализации. Бойлерплейта не сильно меньше стало, но порядка заметно больше.
                                                  0

                                                  Если остановиться на createReducer и createAction, может, и не сильно меньше. А вот createSlice прям прилично сократил бойлерплейт и порезал несколько папкофайлов. Значительно сократился объём исходников, даже общий объём приложения уменьшился (не смотря на подключение, по сути, дополнительной либы).

                                                  +1

                                                  А еще rematch, easy-peasy, симбиоты тут ниже в треде проскакивали, тысячи их…
                                                  И на каждом проекте своя байда. А голый Redux неюзабелен. Тем и бесит =(

                                                    +1

                                                    По крайней мере она позиционируется как официальная, поэтому может стать стандартом де-факто.

                                                  0

                                                  Лучшее средство для упрощения redux-кода, которое я встречал: redux-symbiote. Для асинхронных действий там тоже есть решение, но оно не использует redux-saga.


                                                  MobX это не всегда решение. Например, он с трудом натягивается на приложения с возможностью отменять и повторять действия.


                                                  Я поставил минус за вставку кода скриншотами. Такой код нельзя скопировать и трудно читать на маленьких экранах. Ознакомьтесь, пожалуйста, с возможностями разметки статей на Хабрахабре.

                                                    +1
                                                    Для отмены и повтора есть opinionated Mobx — Mobx State Tree. Не говоря уже о том, что паттерн Команда без проблем реализуется вручную независимо от стейт-менеджера: en.wikipedia.org/wiki/Command_pattern
                                                    Как раз в Redux путешествие по истории реализовано максимально неэффективно — запоминаются N последних состояний, вместо того, чтобы запоминать только разницы между состояниями. Представьте, что если бы Git хранил коммиты не как diff'ы от начального состояния, а как промежуточные состояния с большим количеством дублирования. Это именно то, что предлагает документация Redux: redux.js.org/recipes/implementing-undo-history#second-attempt-writing-a-reducer-enhancer
                                                    В Mobx State Tree напротив можно хранить историю патчей состояния, а не сами состояния: mobx-state-tree.js.org/concepts/patches
                                                      0

                                                      Именно Mobx State Tree я подразумевал под натягиванием. Я в одно время, вдохновившись рассказами про MobX, провёл детальное сравнение Redux и MobX в контексте использования в моём приложении, и пришёл к выводу, что Redux подходит лучше. К сожалению, я не записал данные исследования и забыл подробности.


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

                                                      Не правда. Когда вы «копируете» состояние приложения, например, таким кодом:


                                                      const newState = {
                                                        ...oldState,
                                                        foo: {
                                                          ...oldState.foo,
                                                          bar: 'new bar',
                                                        },
                                                      };

                                                      Браузер не копирует все объекты, на которые ссылается oldState. Все атрибуты newState, кроме foo, будут ссылаться на те же самые экземпляры объектов, что oldState. Аналогично с атрибутами newState.foo. В память добавятся только те части состояния, которые изменились. Это менее эффективно, чем хранить патчи, когда объекты имеют много атрибутов, но в реальных проектах это малая толика от общего вклада в производительность.

                                                        +1
                                                        Представьте, что если бы Git хранил коммиты не как diff'ы от начального состояния, а как промежуточные состояния с большим количеством дублирования.

                                                        Вы не поверите, но Git так и делает

                                                          0
                                                          запоминаются N последних состояний

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

                                                            0

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

                                                            0
                                                            git не хранит diff'ы, git хранит snapshot'ы
                                                            0
                                                            спасибо за конструктивный комментарий, в последующим сделаю по вашему совету)
                                                              +1
                                                              fixed :)
                                                                0

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

                                                                  0

                                                                  Какие были сложности? У меня опыт с ними небольшой, всего 1 готовый проект, и я пока не испытал проблемы.

                                                                +1

                                                                просто оставлю ето здесь https://redux-toolkit.js.org/

                                                                  0
                                                                  Мне кажется хороший вариант — это redux-toolkit + typescript. Без TypeScript я, вообще, не представляю как написать, что-то более менее большое, получается всё время надо ходить по коду и вспоминать где какие параметры принимаются и какая структура данных. Еще мне очень помогает Either из fp-ts. Использую её, когда надо либо вернуть результат, либо ошибку:
                                                                  const result: Either<ApiError, Payment> = yield api(...);
                                                                    +1

                                                                    А есть рациональный ответ на вопрос: "Зачем примеры кода делать картинкой?"?

                                                                      +1
                                                                      первый пост и больше думал о том, как донести мысль, не сильно задумываясь о картинках, но уже поправил примеры
                                                                      +3
                                                                      Библиотеки react-redux и redux-saga просты, гибки и удобны
                                                                      Это ваше мнение или откуда-то скопировано?) Не первый раз уже встречаю подобные высказывания в начале статей о redux.

                                                                      Какое удобство на каждый чих создавать или менять по 10 файлов, или искать ошибку в 10 файлах?
                                                                      Какая простота, когда на каждый запрос данных для компонента нужны, грубо говоря, код в компоненте, selector, action, action type, api, saga, reducer?
                                                                        0
                                                                        Мое мнение, более простых альтернатив я не пробовал. А по поводу излишества кода, пост как раз об этом.
                                                                        0

                                                                        Вы не сделали последний шаг — надо убрать actions/actionsCreators и вместо них вызывать на сторе непосредственно ф-и из редьюсера.

                                                                          0

                                                                          Тогда теряется "главная" фишка redux-а: pub/sub. Когда на 1 action может реагировать сколько надо reducer-ов. Правда далеко не всем эта фишка нужна.

                                                                            +1
                                                                            Интересно. И часто этой фишкой пользуются? Я вот на практике ни разу не видел
                                                                              0

                                                                              Думаю это для тесносплетённых сущностей, которые фигурируют сразу везде. Скажем может пригодиться в социальных сетях. Когда клик по кнопке в одном месте должен автоматически отразиться во всех остальных, т.к. переиспользуется одна сущность.


                                                                              Мы вот как раз пишем соц. сеть, но этой фишкой не пользуемся. Мы всё свели к паттерну "команда". И теперь столкнулись с тем, что обеспечить нормальную нормализацию и последующую мемоизацию бывает очень и очень сложно. В случае pub/sub можно было бы просто тупо понавешать reducer-ов на типовые action-ы и менять сущности не централизованно, а децентрализованно. Но там тоже свои проблемы.


                                                                              А вообще pub/sub имхо это больше для всяких сильно связанных систем с плагинами, вроде того же babel-я. Где куча всяких хуков и не стабильная структура кода (привет плагины для плагинов для плагинов). Но это не про redux.

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

                                                                                  Это случится в одном из двух случаев:


                                                                                  • У вас всё нормализовано. Тогда не нужен pub/sub, но есть другие проблемы.
                                                                                  • У вас всё денормализовано. Тогда нужен pub/sub, чтобы обновления доехали до всех мест.

                                                                                  Оба варианта паршивые, т.к. имеют ряд проблем. У нас 1-ый. И мы уже "приехали" в то, что если сервер возвращает сущности в разном виде с разным перечнем полей, то обеспечить вменяемую нормализацию, скажем так, не тривиально. Особенно на уже большой кодовой базе.


                                                                                  У 2-го подхода другие проблемы.

                                                                                    0
                                                                                    Не совсем понимаю. Если у тебя есть данные — они лежат в одном месте. Везде, где эти данные нужно дублировать — используется что-то вроде `computed` из MobX
                                                                                      0

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


                                                                                      Сам redux тут особо не причём, это касается и MobX, и Vue, и KnockoutJS и чего угодно ещё. Ибо проблема этажом выше. Computed тут совсем не причём, т.к. речь идёт не о калькуляции чего-то из чего-то.

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

                                                                                          Вероятно верно и то и другое. Для этого нужны условия.


                                                                                          Дано:


                                                                                          • есть множество разрозненных, не связанных с друг другом, элементов на странице
                                                                                          • они отчасти питаются из одного источника данных (далее "реестр")
                                                                                          • они не зависимо друг от друга обновляют этот реестр общих данных
                                                                                          • они обновляют его разными срезами одной и той же информации (т.к. это не одна модель на одну сущность, а сколько угодно бакенду моделей, где разные наборы полей)
                                                                                          • всё это должно быть реактивным
                                                                                          • все корневые модели (скажем: пост, пользователь и пр.) довольно тесно связаны (например у поста сложная ветвистая структура где в нескольких местах могут быть указаны данные других сущностей, например автор поста)
                                                                                          • это всё должно работать быстро

                                                                                          Мы используем стандартный подход для redux — нормализация данных. Т.е. мы не храним одни и те же вещи в разных местах. Точнее очень стараемся не хранить, но не везде руки дошли сделать правильно. Стало быть мы точечно обновляет общий реестр данных. Разными наборами данных (там хитрый merge для каждой сущности). Каждый подписчик на нужные сущности реагирует на изменение самой колллекции и не только пытается выдрать оттуда нужные сущности, но ещё и вынужден проверять а хватает ли ему полей. Может его пак данных ещё по сети идёт, а те что уже есть от других компонент приложения недостаточны. При этом если он загружает список то там может быть так, что по одним сущностям хватает, а по другим нет. Ввиду разных пересечений разных компонент по моделям. Да и сами поля могут быть неравнозначны (это отдельный геморрой).


                                                                                          Т.к. это redux и immutability то нужно ещё это всё дело уметь выгружать из реестра. Да заодно выгружать не крупицами а сразу в едином обогощённом виде. Иногда сразу сотнями. Т.е. выгружать, скажем, данные поста сразу со всеми вложенными в него сущностями, а не лезть за каждой в стор отдельно. А это приводит к довольно сложной мемоизации (в меру сложной).


                                                                                          А ещё в виду той же иммутабельности и всяких уже браузерных sideeffect-ов хочется избегать лишних ререндеров, когда например были получены с сервера данные о посте пользователя, разумеется вместе с данными автора. И загрузили мы 10 постов, от 2 авторов. И важно почём зря не обновить авторов в реестре, если у них ничего не изменилось ничего, чтобы не вызвать лишние ререндеры в тех местах, где это вроде и не нужно (не все UI компоненты простые, всякие там Masonry таблицы бывают чувствительными к обновлению данных, плюс всякие скроллы и useEffect-ы).


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


                                                                                          В противовес всему этому можно было тупо забить на нормализацию. Хранить везде всё как взбренится. И просто отлавливать повсеместно только те события которые важно отловить и обновить нужные наборы данных уже поместу. Скажем есть пользователь "Вася", и вы за-follow-или его. Надо чтобы аватарка Васи теперь была с нужной рамкой везде, т.к. Вася теперь не просто Вася, а ваш кореш. Так что можно просто тупо отправить событие в стиле "изменить_статус_follow-ности для пользователя Вася", а все кому надо поймают и свои модели как им угодно поправят. Или не поправят, если им плевать. Вот это будет pub/sub. Какие-то вещи в нём делаются проще.

                                                                                            0
                                                                                            А можно чуть более конкретно? Ну, к примеру:

                                                                                            У нас есть следующие сущности в приложении:

                                                                                            1. Статьи
                                                                                            2. Комментарии
                                                                                            3. Пользователи

                                                                                            У каждой статьи и комментария есть автор (пользователь). У каждой статьи — список комментариев


                                                                                            Допустим, у нас есть такая структура. В каком именно месте вы стыкаетесь с проблемой? Какая именно связь является сомнительной?

                                                                                            Пока то, что вы описываете выглядит как проблемы именно кривости редакса.
                                                                                              0

                                                                                              Мы сталкиваемся с проблемами:


                                                                                              • как положить данные в стор (их нужно провалидировать, конвертировать, разобрать на составные части и положить в стор раздельно)
                                                                                              • данные о статье могут быть частичными, а могут и полными, может вообще только 1 поле придти
                                                                                              • данные о статье в разных частях приложения будут частичными очень по-разному
                                                                                              • некоторые наборы данных с бакенда очень сложно между собой согласовать
                                                                                              • потребители этих данных (компоненты) не хотят собирать эти паззлы по частям, хотят удобоваримый цельный кусок
                                                                                              • и не хотят чтобы сборка этого куска приводила тем или иным тормозам, вроде лишних рендеров, сбросов скроллов, всякой UI нечисти
                                                                                              • и не хотят чтобы были баги уровня — данных не хватило
                                                                                              • и не хотят чтобы были race-conditions
                                                                                              • и не хотят чтобы кто-то удалил данные зазря, когда кто-то их ещё использует
                                                                                              • и не хотят чтобы данные жили вечно (у юзера памяти не хватит если на длительное время оставить открытой вкладку)
                                                                                              • а потребителей масса, и в каждом могут быть свои баги

                                                                                              Redux тут только отчасти причём, ввиду того что навязывает нормализацию и иммутабельность. По сути будь тут что Vuex, что Redux суть примерно одна. Одни и те же проблемы. Полагаю что и MobX State Tree не выделится примерно ничем. Они этажом ниже. "Разруха, она ведь в головах".

                                                                                            +2

                                                                                            Если на языке DDD, то, по-моему, речь идёт о ситуации, когда одна сущность предметной области маппится на разные "классы" в разных ограниченных контекстах.

                                                                                              0
                                                                                              Хммм… Что-то вроде есть ArticleAuthor и CommentAuthor, хотя, по сути — они оба ссылаются на одну сущность в базе данных — User. Правильно?
                                                                                                0

                                                                                                И это тоже, да. Причём в одном могут быть подробные данные об аватарке пользователя в 100500 полей, а в другом только 1 поле url: string. И как их согласовывать? "Как-то"… По сути многие вещи решаются "как-то". Это печалит.

                                                                                                  0
                                                                                                  Ну вы ведь с этим «как-то» работаете в той же вьюшке? Почему бы, ну я не знаю. Не приводить всё к одному типу в модели?

                                                                                                  То есть проблема у вас в том, что хреновый АПИ, но при этом нужно как-то на клиенте синхронизировать все варианты?
                                                                                                    0
                                                                                                    Не приводить всё к одному типу в модели?

                                                                                                    Ну тогда вам нужен pub/sub. Вам ведь нужно как-то распространить все обновлениям по всем местам? С этого разговор и начался. Либо вы приводите всё к одному формату и храните в одном месте. Это нормализация. Либо не делаете этого и храните везде по отдельности всё. Куда пришло там и лежит в том же виде. Это денормализация. Чтобы заставить оба подхода обновлять соответствующие данные ВЕЗДЕ вам придётся изрядно попотеть.


                                                                                                    Касательно API, если у вас High-Load, то не backend пляшет от ваших хотелок, а вы от него. Да и не всегда сподручно ради одного-двух полей тащить громоздкие сущности в списках.

                                                                                                      0
                                                                                                      Ну тогда вам нужен pub/sub
                                                                                                      Ну допустим. И в чем проблема?

                                                                                                      Касательно API, если у вас High-Load, то не backend пляшет от ваших хотелок, а вы от него. Да и не всегда сподручно ради одного-двух полей тащить громоздкие сущности в списках.

                                                                                                      Обычно нужен баланс. Это не значит, что если у нас хайлоад, то один раз сервер присылает { user_id: 123 }, а второй раз: { user: { id: 123 } }. Таких соглашений достаточно, чтобы иметь адекватный и предсказуемый апи.
                                                                                                        0
                                                                                                        Ну допустим. И в чем проблема?

                                                                                                        Я окончательно потерял нить беседы. Ты хотел чтобы я показал где может быть полезным pub/sub — я показал. Ты спросил в чём могут быть сложности без него — я написал. Причём достаточно подробно.


                                                                                                        Что ещё осталось непонятным? :) Про сервер не готов дискутировать. Во всяком случае здесь это уже будет совсем флейм.

                                                                                                          0
                                                                                                          Ааа. Теперь понял. Правда, не понял, зачем на один экшен несколько редьюсеров в редаксе) Вот, изначальный твой комментарий:
                                                                                                          Тогда теряется «главная» фишка redux-а: pub/sub. Когда на 1 action может реагировать сколько надо reducer-ов. Правда далеко не всем эта фишка нужна.

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

                                                                                                            Если ты это делаешь, то pub/sub не нужен. Мы как раз так и делаем. У нас нет pub/sub. Но это создаёт ряд проблем.


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


                                                                                                            В общем pub/sub это обыкновенная подписка на изменения или события. Streams, Events, Computed — всё это примерно об одном и том же в разной форме.

                                                                                –1
                                                                                Смешно видеть рядом слова redux и фишка =))
                                                                                  0
                                                                                  Тогда теряется "главная" фишка redux-а: pub/sub. Когда на 1 action может реагировать сколько надо reducer-ов.

                                                                                  Вообще это антипаттерн. Несколько редьюсеров на один экшон нарушают принцип single source of thuth. Если надо обрабатывать один экшон в разных местах — стоит сделать рефакторинг.

                                                                                    0

                                                                                    Тем не менее это одна из главных заповедей redux ;-) А вообще подобным образом много что в IT устроено. Та же самая Apache Kafka (поправьте если я не прав) столь популярная на бакенде.

                                                                                      0
                                                                                      pub/sub не связан с single source of thuth от слова совсем. Это вообще разные вещи.
                                                                                        –2

                                                                                        А при чем тут сам pub/sub? Мы про конкретную реализацию т.е. редакс. А идеология редакса, по сути, прямо запрещает обработку одного экшона в разных редьюсерах.

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

                                                                                          можно ссылочку на это? (всё наоборот)

                                                                                            0
                                                                                            Да кому какое дело до его идеологии, люди обвешивают его зоопарком библиотек, чтобы хоть как то, хоть чуть чуть упростить себе жизнь… Идеология просто выше всяких похвал.
                                                                                              0
                                                                                              А при чем тут сам pub/sub?

                                                                                              В основе Redux лежит принцип pub/sub. Просто это не так очевидно на первый взгляд, потому что это не просто EventEmitter.
                                                                                                +3

                                                                                                Если бы идеология редакса запрещала обработку одного экшона в разных редьюсерах — у него была бы совершенно другая архитектура.

                                                                                                  0
                                                                                                  Если бы идеология редакса запрещала обработку одного экшона в разных редьюсерах — у него была бы совершенно другая архитектура.

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

                                                                                                    +2
                                                                                                    К сожалению, всё не так. Идеология redux прямо предполагает, что на один action реагируют много reducer и соответствующим образом меняют только ту ветвь состояния, за которую эти reducer отвечают.

                                                                                                    redux.js.org/faq/actions#is-there-always-a-one-to-one-mapping-between-reducers-and-actions
                                                                                                      –1
                                                                                                      Вы сейчас исходите из того, что редакс написал разумный человек. Это не так.

                                                                                                      Верно подмечено! Жаль пока что не все это понимают.
                                                                                                –1

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


                                                                                                Даже если несколько редьюсеров объединять через compose, получится что-то вроде middleware.

                                                                                            0
                                                                                            Вы сейчас исходите из того, что редакс написал разумный человек. Это не так.

                                                                                            Верно подмечено! Жаль пока что не все это понимают.

                                                                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                            Самое читаемое