Redux: Как вырезать шаблон при обновлении вложенных сущностей?

Созданный на 3 нояб. 2015  ·  32Комментарии  ·  Источник: reduxjs/redux

Итак, у меня есть эта вложенная структура в моем состоянии

state = {
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe1'},{title: 'exe2'}]}
   ]
}

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

Например, если бы я хотел добавить новое пустое упражнение или обновить существующее, изменив данные, я бы просто сделал:

state.plans[planIdx].exercises.push({})

state.plans[planIdx].exercises[exerciseIdx] = exercise

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

case 'UPDATE_PLAN':
    return {
      ...state,
      plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
      ]
    };

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

Спасибо!

docs question

Самый полезный комментарий

Да, мы предлагаем нормализовать ваши данные.
Таким образом, вам не нужно «углубляться»: все ваши сущности находятся на одном уровне.

Так ваше состояние будет выглядеть

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

Ваши редукторы могут выглядеть как

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

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

Во-вторых, обратите внимание, как мы реагируем на CREATE_PLAN , добавляя соответствующую сущность в редуктор plans _и_, добавляя его идентификатор в редуктор currentPlans . Это важно. В более сложных приложениях у вас могут быть отношения, например, plans reducer может обрабатывать ADD_EXERCISE_TO_PLAN таким же образом, добавляя новый идентификатор в массив внутри плана. Но если само упражнение обновлено, _нет необходимости, чтобы plans reducer знал об этом, поскольку идентификатор не изменился_.

В-третьих, обратите внимание, что редукторы сущностей ( plans и exercises ) имеют специальные предложения, которые следят за action.entities . Это на случай, если у нас есть ответ сервера с «известной правдой», который мы хотим обновить для отражения всех наших сущностей. Чтобы подготовить данные таким образом перед отправкой действия, вы можете использовать normalizr . Вы можете увидеть его использование в «реальном мире» в репозитории Redux.

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

О, и я использовал синтаксис { ...a, ...b } . Он включен на этапе 2 Babel в качестве предложения ES7. Он называется «оператором распространения объекта» и эквивалентен написанию Object.assign({}, a, b) .

Что касается библиотек, вы можете использовать Lodash (будьте осторожны, чтобы не мутировать, например, merge({}, a, b} правильно, а merge(a, b) - нет), updeep , -addons-update или что-то еще. Однако, если вам нужно делать глубокие обновления, это, вероятно, означает, что ваше дерево состояний недостаточно ровное и вы недостаточно используете функциональную композицию. Даже ваш первый пример:

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

можно записать как

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

Все 32 Комментарий

Рекомендуется нормализовать вложенный JSON, например: https://github.com/gaearon/normalizr

Да, мы предлагаем нормализовать ваши данные.
Таким образом, вам не нужно «углубляться»: все ваши сущности находятся на одном уровне.

Так ваше состояние будет выглядеть

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

Ваши редукторы могут выглядеть как

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

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

Во-вторых, обратите внимание, как мы реагируем на CREATE_PLAN , добавляя соответствующую сущность в редуктор plans _и_, добавляя его идентификатор в редуктор currentPlans . Это важно. В более сложных приложениях у вас могут быть отношения, например, plans reducer может обрабатывать ADD_EXERCISE_TO_PLAN таким же образом, добавляя новый идентификатор в массив внутри плана. Но если само упражнение обновлено, _нет необходимости, чтобы plans reducer знал об этом, поскольку идентификатор не изменился_.

В-третьих, обратите внимание, что редукторы сущностей ( plans и exercises ) имеют специальные предложения, которые следят за action.entities . Это на случай, если у нас есть ответ сервера с «известной правдой», который мы хотим обновить для отражения всех наших сущностей. Чтобы подготовить данные таким образом перед отправкой действия, вы можете использовать normalizr . Вы можете увидеть его использование в «реальном мире» в репозитории Redux.

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

О, и я использовал синтаксис { ...a, ...b } . Он включен на этапе 2 Babel в качестве предложения ES7. Он называется «оператором распространения объекта» и эквивалентен написанию Object.assign({}, a, b) .

Что касается библиотек, вы можете использовать Lodash (будьте осторожны, чтобы не мутировать, например, merge({}, a, b} правильно, а merge(a, b) - нет), updeep , -addons-update или что-то еще. Однако, если вам нужно делать глубокие обновления, это, вероятно, означает, что ваше дерево состояний недостаточно ровное и вы недостаточно используете функциональную композицию. Даже ваш первый пример:

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

можно записать как

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

Было бы неплохо превратить это в рецепт.

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

@ andre0799 или просто использовать Immutable.js))

В идеале нам нужен пример доски канбан.

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

Immutable.js - не всегда хорошее решение. Он пересчитывает хэш каждого родительского узла состояния, начиная с узла, который вы изменили, и это становится узким местом в определенных случаях (хотя это не очень распространенные случаи). Так что в идеале вы должны сделать несколько тестов, прежде чем интегрировать Immutable.js в свое приложение.

Спасибо @gaearon за ваш ответ, отличное объяснение!

Итак, когда вы выполняете CREATE_PLAN вы должны автоматически создавать упражнение по умолчанию и добавлять к нему. Как мне обращаться с подобными случаями? Должен ли я затем вызвать 3 действия подряд? CREATE_PLAN, CREATE_EXERCISE, ADD_EXERCISE_TO_PLAN Откуда мне делать эти вызовы, если это так?

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

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

Вы можете использовать промежуточное ПО Redux Thunk, чтобы написать создателя действий, который будет

function createPlan(title) {
  return dispatch => {
    const planId = uuid();
    const exerciseId = uuid();

    dispatch({
      type: 'CREATE_EXERCISE',
      id: exerciseId,
      exercise: {
        id: exerciseId,
        title: 'Default'
      }
    });

    dispatch({
      type: 'CREATE_PLAN',
      id: planId,
      plan: {
        id: planId,
        exercises: [exerciseId],
        title
      }
    });
  };
}

Затем, если вы примените промежуточное ПО Redux Thunk, вы можете вызвать его как обычно:

store.dispatch(createPlan(title));

Итак, допустим, у меня есть редактор сообщений на бэкэнде где-то с такими отношениями (сообщения, авторы, теги, вложения и т. Д.).

Как мне отобразить currentPosts похожий на массив ключей currentPlans ? Нужно ли мне сопоставить каждый ключ в currentPosts с соответствующим объектом в entities.posts в функции mapStateToProps ? Как насчет сортировки currentPosts ?

Все это входит в состав редуктора?

Мне что-то здесь не хватает ...

Что касается исходного вопроса, я считаю, что для этой цели были созданы React Immutability Helpers.

Как мне отобразить currentPosts аналогично массиву ключей currentPlans? Нужно ли мне сопоставить каждый ключ в currentPosts с его соответствующим объектом в entity.posts в функции mapStateToProps? Как насчет сортировки текущих сообщений?

Это верно. Вы будете делать все, когда будете получать данные. Пожалуйста, ознакомьтесь с примерами «корзины покупок» и «реального мира», которые поставляются с репозиторием Redux.

Спасибо, я уже начал понимать, прочитав в документации

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

@ andre0799

Любой компонент connect() ed имеет dispatch введенный в качестве опоры по умолчанию.

this.props.dispatch(createPlan(title));

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

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

Поскольку я допустил ошибку, имея немного глубины в моем состоянии, я сделал эту библиотеку, чтобы помочь модернизировать и управлять глубоким состоянием с помощью Redux: https://github.com/baptistemanson/immutable-path
Может, я ошибся, но мне было бы интересно узнать ваше мнение. Надеюсь, это кому-то поможет.

Это было полезно. Спасибо всем.

Как бы вы добавили упражнение в план, используя ту же структуру, что и в реальном примере? Допустим, добавление упражнения вернуло вновь созданный объект упражнения с полем planId . Можно ли добавить новое упражнение к этому плану без необходимости писать редуктор для планов и специально слушать действие CREATE_EXERCISE ?

Отличное обсуждение и информация здесь. Я хотел бы использовать normalizr для своего проекта, но у меня есть вопрос относительно сохранения обновленных данных на удаленный сервер. В основном, есть ли простой способ вернуть нормализованную форму обратно к вложенной форме, предоставленной удаленным API после обновления? Это важно, когда клиент вносит изменения и должен передать их обратно на удаленный api, где он не может контролировать форму запроса на обновление.

Например: клиент извлекает вложенные данные упражнения -> клиент нормализует их и сохраняет в сокращенном виде -> пользователь вносит изменения в нормализованные данные на стороне клиента -> пользователь нажимает кнопку «Сохранить» -> клиентское приложение преобразует обновленные нормализованные данные обратно во вложенную форму. чтобы он мог отправить его на удаленный сервер -> клиент отправляет на сервер

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

благодаря

Есть что-то под названием https://github.com/gpbl/denormalizr, но я не уверен, насколько внимательно он отслеживает обновления normalizr. Я написал normalizr за пару часов для приложения, над которым работал, вы можете его форкнуть и добавить денормализацию 😄.

Круто, я обязательно посмотрю на денормализацию и внесу свой вклад в ваш проект, когда у меня что-нибудь появится. Отличная работа за пару часов ;-) Спасибо, что ответили мне.

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

Хорошо, если я сделаю ссылку на сообщение StackOverflow, которое я сделал с просьбой оставить здесь отзыв о решении?

Я сейчас привязываю его, пожалуйста, удалите мой пост или скажите, если здесь неуместно размещать ссылку:
http://stackoverflow.com/questions/37171203/manipulating-data-in-nested-arrays-in-redux-with-immutable-js

Я видел, как этот подход рекомендовался в прошлом. Однако этот подход не работает, когда нужно удалить вложенный объект. В этом случае редуктору нужно будет просмотреть все ссылки на объект и удалить их, операция, которая будет O (n), прежде чем сможет удалить сам объект. Кто-нибудь сталкивался с подобной проблемой и решал ее?

@ariofrio : а ... я в замешательстве. Точка нормализации состоит в том, что объекты _не_ хранятся вложенными, и есть только одна ссылка на данный элемент, что упрощает обновление этого элемента. Теперь, если есть несколько других сущностей, которые «ссылаются» на этот элемент по идентификатору, конечно, их тоже нужно обновить, как если бы все не было нормализовано.

Есть ли у вас конкретная проблема или неприятный сценарий, с которым вы имеете дело?

Вот что я имею в виду. Скажем, текущее состояние выглядит так:

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 6]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
      5: {title: 'exe5'}
      6: {title: 'exe6'}
    }
  },
  currentPlans: [1, 2]
}

В этом примере на каждое упражнение может ссылаться только один план. Когда пользователь нажимает «Удалить упражнение», сообщение может выглядеть примерно так:

{type: "REMOVE_EXERCISE", payload: 2}

Но чтобы реализовать это должным образом, нужно будет перебрать все планы, а затем все упражнения в каждом плане, чтобы найти то, которое ссылается на упражнение с идентификатором 2, чтобы избежать висячей ссылки. Это операция O (n), о которой я беспокоился.

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

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

И сообщение об удалении упражнения может выглядеть так:

{type: "REMOVE_EXERCISE", payload: {plan_index: 0, exercise_index: 1}}

Несколько мыслей:

  • Вы можете вести обратный поиск упражнений в планы, чтобы упростить этот сценарий. Точно так же библиотека Redux-ORM автоматически генерирует «сквозные таблицы» для многотипных отношений. Итак, в этом случае у вас будет таблица PlanExercise в вашем магазине, которая будет содержать триплеты {id, planId, exerciseId} . Конечно, сканирование O (n), но простое.
  • Операция O (n) - это не плохо. Это полностью зависит от размера N, постоянного множителя перед термином, от того, как часто это происходит и что еще происходит в вашем приложении. Перебор списка из 10 или 15 элементов и выполнение некоторых проверок равенства при нажатии кнопки пользователя будет совершенно другим делом, чем, скажем, итерация списка из 10 миллионов элементов, поступающих в систему каждые 500 мс, и выполнение некоторых дорогостоящих операций для каждого. вещь. В этом случае велика вероятность, что даже проверка тысяч планов не будет значительным узким местом.
  • Это реальная проблема производительности, которую вы наблюдаете, или вы просто рассматриваете возможные теоретические проблемы?

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

мое решение вроде этого:

function deepCombinReducer(parentReducer, subReducer) {
    return function (state = parentReducer(void(0) /* get parent reducer initial state */, {}) {
        let finalState = {...state};

        for (var k in subReducer) {
          finalState[k] = subReducer(state[k], action);
        }

       return parentReducer(finalState, action);
    };
}

const parentReducer = function(state = {}, action) {
    return state;
}

const subReducer = function(state = [], action) {
    state = Immutable.fromJS(state).toJS();
    switch(action.type) {
       case 'ADD':
          state.push(action.sub);
           return state;
       default:
          return state;
   }
}

export default combineReducers({
   parent: deepCombinReducer(parentReducer, {
       sub: subReducer
   })
})

Тогда вы можете получить такой магазин:

{
    parent: {
       sub: []
    }
}

dispatch({
    type: 'ADD',
    sub: '123'
});

// the store will change to:
{
    parent: {
       sub: ['123']
    }
}

@smashercosmo immutable.js с глубоким вложенным состоянием? Мне любопытно как

@gaearon

У нас никогда не бывает сущностей внутри других сущностей.

Я этого не понимаю. У нас здесь как минимум три уровня вложенности:

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

entities.plans[1] - three levels
entities.exercises[1] - three levels

Это не вложенный объект. Только один уровень.

{
   plans: [1,2, 3],
   exercises: [1,2,3],
   'so forth': [1,2,3]
}

@wzup : К вашему сведению, в наши дни Дэн не тратит много времени на изучение проблем с Redux - у него уже достаточно работы над React.

Смысл «вложенности» здесь заключается в том, что сами данные вложены, как в этом примере из ранее в потоке:

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

В этом примере единственный способ получить доступ к упражнению «exe6» - это покопаться в структуре, например plans[1].exercises[2] .

Меня интересует вопрос @tmonte :

Как бы вы добавили упражнение в план, используя ту же структуру, что и в реальном примере? Предположим, что добавление упражнения вернуло вновь созданную сущность упражнения с полем planId. Можно ли добавить новое упражнение в этот план без необходимости писать редуктор для планов и специально прослушивать действие CREATE_EXERCISE?

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

Я предварительно использовал mergeWith вместо merge для большей гибкости:

import mergeWith from 'lodash/mergeWith';

// Updates an entity cache in response to any action with `entities`.
function entities(state = {}, action) {
  // Here where we STORE or UPDATE one or many entities
  // So check if the action contains the format we will manage
  // wich is `payload.entities`
  if (action.payload && action.payload.entities) {
    // if the entity is already in the store replace
    // it with the new one and do not merge. Why?
    // Assuming we have this product in the store:
    //
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    //
    // We will updated with
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //   }
    // }
    //
    // The result if we were using `lodash/merge`
    // notice the rate `rateCategory` hasn't changed:
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    // for this particular use case it's safer to use
    // `lodash/mergeWith` and skip the merge
    return mergeWith({}, state, action.payload.entities, (oldD, newD) => {
      if (oldD && oldD.id && oldD.id === newD.id) {
        return newD;
      }
      return undefined;
    });
  }

  // Here you could register other handlers to manipulate 
  // the entities
  switch (action.type) {
    case ActionTypes.SOME_ACTION:
      // do something;
    default:
      return state;
  }
}

const rootReducer = combineReducers({
  entities,
});
export default rootReducer;
Была ли эта страница полезной?
0 / 5 - 0 рейтинги