Redux: состояние как массив объектов и объект, связанный с идентификатором

В главе Проектирование формы состояния документы предлагают сохранить ваше состояние в объекте с ключом ID:

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

Они переходят в состояние

Подумайте о состоянии приложений в качестве базы данных.

Я работаю над формой состояния для списка фильтров, некоторые из которых будут открыты (они отображаются во всплывающем окне) или имеют выбранные параметры. Когда я прочитал "Подумайте о состоянии приложений в качестве базы данных", я подумал о том, чтобы думать о них как о реакции JSON, поскольку он будет возвращен из API (сам подкрепленный базой данных).

Итак, я думал об этом как

[{
    id: '1',
    name: 'View',
    open: false,
    options: ['10', '11', '12', '13'],
    selectedOption: ['10'],
    parent: null,
  },
  {
    id: '10',
    name: 'Time & Fees',
    open: false,
    options: ['20', '21', '22', '23', '24'],
    selectedOption: null,
    parent: '1',
  }]

Однако документы предлагают формат, более похожий на

{
   1: { 
    name: 'View',
    open: false,
    options: ['10', '11', '12', '13'],
    selectedOption: ['10'],
    parent: null,
  },
  10: {
    name: 'Time & Fees',
    open: false,
    options: ['20', '21', '22', '23', '24'],
    selectedOption: null,
    parent: '1',
  }
}

В теории, это не имеет значения, если данные сериализуемы (под заголовком "Состояние" ).

Итак, я с удовольствием подошел к массиву объектов, пока я не писал свой редуктор.

При использовании подхода с привязкой к объекту (и либерального использования оператора спреда) часть OPEN_FILTER редуктора становится

switch (action.type) {
  case OPEN_FILTER: {
    return { ...state, { ...state[action.id], open: true } }
  }

В то время как при использовании подхода "массив-объект" он более подробный (и вспомогательная функция зависит)

switch (action.type) {
   case OPEN_FILTER: {
      // relies on getFilterById helper function
      const filter = getFilterById(state, action.id);
      const index = state.indexOf(filter);
      return state
        .slice(0, index)
        .concat([{ ...filter, open: true }])
        .concat(state.slice(index + 1));
    }
    ...

Итак, мои вопросы трижды:

1) Является ли простота редуктора мотивацией для перехода с подхода, основанного на объекте? Существуют ли другие преимущества для этого состояния?

и

2) Похоже, что подход, основанный на объектах с ключом, затрудняет работу со стандартным JSON in/out для API. (Вот почему я пошел с массивом объектов в первую очередь.) Итак, если вы идете с этим подходом, вы просто используете функцию для преобразования ее вперед и назад между форматом JSON и форматом формы штата? Это кажется неуклюжим. (Хотя, если вы защищаете этот подход, является частью ваших рассуждений о том, что это менее clunky, чем редуктор массива объектов выше?)

и

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

Ответ 1

Q1: Простота редуктора - результат отсутствия необходимости в поиске массива для поиска правильной записи. Преимущество не в поиске массива. Селекторы и другие устройства доступа к данным могут часто и часто часто обращаться к этим элементам с помощью id. Необходимость поиска по массиву для каждого доступа становится проблемой производительности. Когда ваши массивы становятся больше, проблема с производительностью сильно ухудшается. Кроме того, поскольку ваше приложение становится более сложным, показывая и фильтруя данные в большем количестве мест, проблема также ухудшается. Комбинация может быть вредной. Получив доступ к элементам с помощью id, время доступа изменяется от O(n) до O(1), что для больших n (здесь элементов массива) имеет огромное значение.

Q2: вы можете использовать normalizr, чтобы помочь вам в преобразовании из API в хранилище. Начиная с normalizr V3.1.0, вы можете использовать denormalize для перехода в другую сторону. Тем не менее, приложения часто больше потребителей, чем производители данных, и поэтому преобразование в хранилище обычно выполняется чаще.

Q3: Проблемы, с которыми вы столкнетесь, используя массив, - это не столько проблемы с соглашением о хранении и/или несовместимостью, но и проблемы с производительностью.

Ответ 2

Подумайте о состоянии приложений в качестве базы данных.

Это ключевая идея.

1) Наличие объектов с уникальными идентификаторами позволяет всегда использовать этот идентификатор при обращении к объекту, поэтому вам необходимо передать минимальный объем данных между действиями и редукторами. Это более эффективно, чем использование array.find(...). Если вы используете подход массива, вам нужно передать весь объект, и он может скоро запутаться, вы можете завершить воссоздание объекта на разных редукторах, действиях или даже в контейнере (вы этого не хотите). Представления всегда смогут получить полный объект, даже если связанный с ним редуктор содержит только идентификатор, потому что при сопоставлении состояния вы получите какую-то коллекцию (представление получает все состояние, чтобы сопоставить его со свойствами). Из-за всего, что я сказал, действия заканчиваются тем, что имеют минимальное количество параметров, а редукторы - минимальное количество информации, дают ему попробовать, попробовать оба метода, и вы увидите, что архитектура становится более масштабируемой и чистой, используя Идентификаторы, если коллекции имеют идентификатор.

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

3) Я использовал массивы для структур с идентификаторами, и это непредвиденные последствия, которые я испытал:

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

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

Также:

4) Большинство коллекций с идентификаторами предназначены для использования идентификатора в качестве ссылки на весь объект, вы должны воспользоваться этим. Вызов API получит идентификатор , а затем остальные параметры, а также ваши действия и редукторы.

Ответ 3

1) Является ли простота редуктора мотивацией для перехода с подхода, основанного на объекте? Существуют ли другие преимущества для этого состояния?

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

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

[{
  id: 1,
  name: 'View',
  open: false,
  options: [
    {
      id: 10, 
      title: 'Option 10',
      created_by: { 
        id: 1, 
        username: 'thierry' 
      }
    },
    {
      id: 11, 
      title: 'Option 11',
      created_by: { 
        id: 2, 
        username: 'dennis'
      }
    },
    ...
  ],
  selectedOption: ['10'],
  parent: null,
},
...
]

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

Лучшим решением было бы нормализовать ответ на:

results: [1],
entities: {
  filterItems: {
    1: {
      id: 1,
      name: 'View',
      open: false,
      options: [10, 11],
      selectedOption: [10],
      parent: null
    }
  },
  options: {
    10: {
      id: 10,
      title: 'Option 10',
      created_by: 1
    },
    11: {
      id: 11,
      title: 'Option 11',
      created_by: 2
    }
  },
  optionCreators: {
    1: {
      id: 1,
      username: 'thierry',
    },
    2: {
      id: 2,
      username: 'dennis'
    }
  }
}

С этой структурой гораздо проще и эффективнее перечислять всех пользователей, которые создали параметры (мы изолируем их в entity.optionCreators, поэтому нам просто нужно пройти через этот список).

Это также довольно просто показать, например. имена пользователей тех, которые создали параметры для элемента фильтра с идентификатором 1:

entities
  .filterItems[1].options
  .map(id => entities.options[id])
  .map(option => entities.optionCreators[option.created_by].username)

2) Похоже, что подход, основанный на объектно-зависимом идентификаторе, затрудняет иметь дело со стандартным входом/выходом JSON для API. (Вот почему я пошел с массив объектов в первую очередь.) Поэтому, если вы пойдете с этим подходом, вы просто используете функцию, чтобы преобразовывать ее между JSON формат и формат формы штата? Это кажется неуклюжим. (Хотя, если вы пропагандировать этот подход, является частью ваших рассуждений о том, что неуклюже, чем редуктор массива объектов?)

Реакция JSON может быть нормализована с использованием, например, normalizr.

3) Я знаю, что Дэн Абрамов проектировал редукцию теоретически агностик государственной структуры данных (как предлагается "По соглашению, Состояние верхнего уровня - это объект или какая-то другая коллекция значений ключа, например, Карта, но технически это может быть любой тип," мой удар ". выше, это просто" рекомендуется", чтобы сохранить объект под ключом ID, или есть другие непредвиденные болевые точки, с которыми я столкнусь используя массив объектов, которые делают его таким, что я должен просто прервать этот план и попытаться придерживаться объекта с ключом ID?

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