redux: состояние как массив объектов против объекта с ключом id
в главе о проектирование формы состояния, документы предлагают сохранить ваше состояние в объекте с ключом 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 } }
}
в то время как с массивом объектов подход, это более подробный (и вспомогательная функция reliant)
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 и форматом формы состояния? Это кажется неуклюжим. (Хотя если вы защитите этот подход, является ли частью вашего рассуждения, что это менее неуклюжий, чем редуктор массива объектов выше?)
и
3) я знаю, что Дэн Абрамов разработал redux, чтобы теоретически быть агностиком структуры данных состояния (как предложено "по соглашению, состояние верхнего уровня-это объект или какая-то другая коллекция ключевых значений, например карта, но технически это может быть любой тип," выделено мной). Но учитывая вышесказанное, это просто "рекомендуется", чтобы сохранить его объект, управляемый ID, или есть другие непредвиденные болевые точки, с которыми я столкнусь, используя массив объектов, которые делают его таким, что я должен просто прервать этот план и попытаться придерживаться объекта, управляемого ID?
3 ответа:
К1: простота редуктора результат не искать через массив для того чтобы найти правый вход. Не нужно искать через массив является преимуществом. Селекторы и другие средства доступа к данным могут и часто имеют доступ к этим элементам с помощью
id
. Необходимость поиска в массиве для каждого доступа становится проблемой производительности. Когда ваши массивы становятся больше, проблема производительности резко ухудшается. Кроме того, поскольку ваше приложение становится более сложным, показывая и фильтруя данные в большем количестве мест, проблема также ухудшается. Комбинация может быть вредной. Путем доступа к элементам поid
, время доступа изменяется отO(n)
доO(1)
, который для большихn
(здесь элементы массива) имеет огромное значение.Q2: вы можете использовать
normalizr
чтобы помочь вам с преобразованием из API в магазин. Начиная с normalizr V3.1.0 вы можете использовать denormalize, чтобы пойти другим путем. Тем не менее, приложения часто являются более потребителями, чем производителями данных, и поэтому преобразование в хранилище обычно это делается чаще.Q3: проблемы, с которыми вы столкнетесь при использовании массива, - это не столько проблемы с соглашением о хранении и/или несовместимостью, сколько проблемы с производительностью.
подумайте о состоянии приложения как базы данных.
Это ключевая идея.
1) наличие объектов с уникальными идентификаторами позволяет всегда использовать этот идентификатор при ссылке на объект, поэтому вы должны передать минимальное количество данных между действиями и редукторами. Это более эффективно, чем использование массива.находить.(..). Если вы используете подход к массиву, вам нужно передать весь объект, и это может очень скоро стать беспорядочным, вы можете в конечном итоге воссоздать объект различные редукторы, действия, или даже в контейнере (вы не хотите этого). Представления всегда смогут получить полный объект, даже если их связанный редуктор содержит только идентификатор, потому что при сопоставлении состояния вы получите коллекцию где-то (представление получает все состояние, чтобы сопоставить его со свойствами). Из-за всего того, что я сказал, действия в конечном итоге имеют минимальное количество параметров и уменьшают минимальное количество информации, попробуйте, попробуйте оба метода, и вы увидите архитектура становится более масштабируемой и чистой с использованием идентификаторов, если коллекции имеют идентификатор.
2) подключение к API не должно влиять на архитектуру хранения и редукторов, поэтому у вас есть действия, чтобы сохранить разделение. Просто поместите свою логику преобразования в API и из него в многоразовый модуль, импортируйте этот модуль в действия, которые используют API, и это должно быть так.
3) я использовал массивы для структур с идентификаторами, и это непрошеные последствия, которые я пережил:
- воссоздание объектов постоянно toughout код
- передача иннесарной информации редукторам и действиям
- как следствие этого, плохой, не чистый и не масштабируемый код.
Я закончил тем, что изменил свою структуру данных и переписал много кода. вы были предупреждены, пожалуйста, не попадайте в беду.
также:
4) большинство коллекций с идентификаторами предназначены для использования идентификатора в качестве ссылки на весь объект,вы должны воспользоваться этим. Вызовы API получат идентификатор а то остальные параметры, так и ваши действия и редукторы.
1) является ли простота редуктора мотивацией для использования объектно-ориентированного подхода? Есть ли другие преимущества в этой форме государства?
основная причина, по которой вы хотите сохранить объекты keep в объектах, хранящихся с идентификаторами в качестве ключей (также называемых нормированный), это действительно громоздко работать с глубоко вложенные объекты (это то, что вы обычно получаете от 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.имя пользователя:.
лучшим решением было бы нормализовать в ответ на:
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' } } }
С этой структурой гораздо проще и эффективнее перечислить всех пользователей, которые создали параметры (у нас они изолированы в сущностях.optionCreators, поэтому нам просто нужно перебрать этот список).
Он также довольно просто показать, например имена пользователей, которые создали параметры для элемента фильтра с идентификатором 1:
entities .filterItems[1].options .map(id => entities.options[id]) .map(option => entities.optionCreators[option.created_by].username)
2) похоже, что подход с объектным ключом по идентификатору затрудняет работа со стандартным JSON in / out для API. (Вот почему я пошел с массив объектов в первую очередь.) Так что если вы идете с этим подходом, вы просто используете функцию для преобразования ее туда и обратно между JSON формат и формат формы состояния? Это кажется неуклюжим. (Хотя если вы отстаивать этот подход, является часть ваших рассуждений, что это меньше неуклюжий, чем редуктор массива объектов выше?)
JSON-ответ может быть нормализован с помощью, например,normalizr.
3) я знаю, что Дэн Абрамов разработал redux, чтобы теоретически быть государство-структура данных агностик (как предложено " по Конвенции, состояние верхнего уровня-это объект или другая коллекция ключей-значений, например Карта, но технически она может быть любого типа", - подчеркнул мой). Но учитывая этот выше, это просто "рекомендуется", чтобы сохранить его объект с ключом ID, или есть другие непредвиденные болевые точки, с которыми я собираюсь столкнуться используя массив объектов, которые делают его таким, что я должен просто прекратить этот план и попытаться придерживаться объекта с ключом ID?
это, вероятно, рекомендация для более сложных приложений с большим количеством глубоко вложенных ответов API. Однако в вашем конкретном примере это не имеет большого значения.