Почему при глубоком клонировании моего состояния ngrx заходит в бесконечный цикл, постоянно вызывая мой редуктор?

У нас был следующий редуктор ngrx, но мы добавили вложенный объект в наше состояние (структура: settings: { a: boolean, b: string }), поэтому мы решили использовать cloneDeep(obj) от lodash вместо оператора распространения ...obj, чтобы гарантировать неизменность нашего состояния, как указано во втором примечании в официальной документации. Раздел «Создание функции редуктора»:

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

// before:
export function reducer(state: MyState = initialState, action: Actions): MyState {
  switch (action.type) {
    // ...
    case MY_TASK: {
      return {
        ...state,
        prop1: action.payload.prop,
        prop2: initialState.anotherProp
      };
    }
    // ..
  }
}

Когда мы изменили это на следующее, ngrx продолжал вызывать метод reducer(), эффективно блокируя (сбой) браузер:

// after - causes infinite loop
import * as _ from 'lodash';
// ..

export function reducer(oldState: MyState = initialState, action: Actions): MyState {
  function nextState(stateChanges?: (MyState) => void): MyState {
    const draftState: MyState = _.cloneDeep(oldState);
    if (stateChanges) {
      stateChanges(draftState);
    }
    return draftState;
  }

  switch (action.type) {
    // ...
    case MY_TASK: {
      return nextState(s => {
        s.prop1 = action.payload.prop;
        s.prop2 = initialState.anotherProp;
      });
    }
    // ..
  }
}

stateChange даже не касается новых вложенных дополнительных свойств состояния (т. Е. Объекта settings), только плоских (prop1, prop2), которые у нас были ранее. settings, однако, является частью oldState и поэтому передается cloneDeep() lodash.

Есть идеи, почему изменение возвращенного состояния (т. е. использование _.deepClone(state) вместо оператора спреда ...state) имело бы такой эффект - и как это остановить?

Мы используем @ngrx/entity, поэтому состояние расширяется EntityState<MyState>. Я понимаю, что функции объекта библиотеки возвращают новое состояние (или " то же состояние, если не было внесено никаких изменений "), и что дополнительные свойства, такие как наш новый вложенный объект settings, могут быть добавлены в состояние, но что
" [t] эти свойства должны быть обновлены вручную "- что я беру то есть я должен сам их клонировать.

Ошибка заключается в том, что мы пытаемся клонировать все состояние (которое расширяет EntityState<T>), а не только дополнительные свойства?

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


person Christian    schedule 03.08.2019    source источник
comment
Кажется, что что-то отправляет действие всякий раз, когда изменяется определенная часть состояния. Поскольку вы все клонируете, состояние всегда меняется, что вызывает отправку действия и т. Д.   -  person Ori Drori    schedule 03.08.2019
comment
Я добавил точки останова в каждый диспетчер событий, который смог найти в нашем коде, но пока ни один из них не срабатывает во время этого цикла. Есть ли способ узнать, что именно вызывает это?   -  person Christian    schedule 05.08.2019


Ответы (1)


_.cloneDeep - это наихудший сценарий для ngrx, потому что он означает, что все было затронуто в магазине, и он снова запускает все селекторы.

Из описанной ошибки я могу предположить, что у вас есть selector, который в некоторых случаях вызывает dispatch, и поскольку отправленный action вызвал _.cloneDeep, селектор снова запускается, что вызывает dispatch и т. Д.

person satanTime    schedule 20.05.2020