В главе, посвященной разработке формы состояния , документы предлагают сохранить ваше состояние в объекте с ключом по идентификатору:
Храните каждую сущность в объекте с идентификатором в качестве ключа и используйте идентификаторы для ссылки на нее из других сущностей или списков.
Они продолжают утверждать
Думайте о состоянии приложения как о базе данных.
Я работаю над формой состояния для списка фильтров, некоторые из которых будут открытыми (они отображаются во всплывающем окне) или с выбранными параметрами. Когда я прочитал «Думайте о состоянии приложения как о базе данных», я подумал о том, чтобы думать о них как о ответе 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 для API. (Вот почему я в первую очередь выбрал массив объектов.) Итак, если вы придерживаетесь этого подхода, вы просто используете функцию для преобразования его между форматом JSON и форматом формы состояния? Это кажется неуклюжим. (Хотя, если вы отстаиваете этот подход, вы считаете, что это менее неуклюже, чем редуктор массива объектов выше?)
а также
3) Я знаю, что Дэн Абрамов разработал redux так, чтобы теоретически не зависеть от структуры данных состояния (как предполагает «По соглашению, состояние верхнего уровня - это объект или некоторая другая коллекция значений ключа, например Map, но технически это может быть любое типа " курсив мой). Но, учитывая вышеизложенное, просто «рекомендуется» сохранить его как объект с ключом по идентификатору, или есть другие непредвиденные болевые точки, с которыми я собираюсь столкнуться, используя массив объектов, которые делают его таким, что я должен просто прервать это планировать и пытаться придерживаться объекта с ключом по ID?
источник
sort_by
?const sorted = _.sortBy(collection, 'attribute');
Ответы:
Q1: Простота редуктора - результат того, что вам не нужно искать в массиве нужную запись. Преимуществом является отсутствие необходимости искать в массиве. Селекторы и другие средства доступа к данным могут и часто имеют доступ к этим элементам с помощью
id
. Необходимость поиска в массиве для каждого доступа становится проблемой производительности. Когда ваши массивы становятся больше, проблема производительности резко ухудшается. Кроме того, по мере того, как ваше приложение становится более сложным, отображая и фильтруя данные в большем количестве мест, проблема также усугубляется. Комбинация может быть вредной. При доступе к элементам поid
, время доступа изменяется сO(n)
наO(1)
, что для большихn
(здесь элементов массива) имеет огромное значение.Q2: вы можете использовать,
normalizr
чтобы помочь вам с преобразованием из API в магазин. Начиная с normalizr V3.1.0, вы можете использовать denormalize, чтобы пойти другим путем. Тем не менее, приложения часто являются больше потребителями, чем производителями данных, и поэтому преобразование в хранилище обычно выполняется чаще.Q3: Проблемы, с которыми вы столкнетесь при использовании массива, связаны не столько с условиями хранения и / или несовместимостью, сколько с проблемами производительности.
источник
Это ключевая идея.
1) Наличие объектов с уникальными идентификаторами позволяет вам всегда использовать этот идентификатор при обращении к объекту, поэтому вы должны передавать минимальный объем данных между действиями и редукторами. Это более эффективно, чем использование array.find (...). Если вы используете массивный подход, вам нужно передать весь объект, и это может очень скоро стать беспорядочным, вы можете в конечном итоге воссоздать объект на разных редукторах, действиях или даже в контейнере (вы этого не хотите). Представления всегда смогут получить полный объект, даже если связанный с ними редуктор содержит только идентификатор, потому что при сопоставлении состояния вы где-то получите коллекцию (представление получает все состояние, чтобы сопоставить его со свойствами). Из-за всего того, что я сказал, действия в конечном итоге имеют минимальное количество параметров и сокращают минимальный объем информации, попробуйте,
2) Подключение к API не должно влиять на архитектуру вашего хранилища и редукторов, поэтому у вас есть действия, чтобы сохранить разделение проблем. Просто поместите свою логику преобразования в API и извлеките из него в многоразовом модуле, импортируйте этот модуль в действия, которые используют API, и это должно быть все.
3) Я использовал массивы для структур с идентификаторами, и это непредвиденные последствия, которые я понес:
В итоге я изменил структуру данных и переписал много кода. Вас предупредили, пожалуйста, не доставляйте себе неприятностей.
Также:
4) Большинство коллекций с идентификаторами предназначены для использования идентификатора в качестве ссылки на весь объект, вы должны воспользоваться этим. Вызовы API получат идентификатор, а затем остальные параметры, а также ваши действия и редукторы.
источник
Основная причина, по которой вы хотите сохранить сущности в объектах, хранящихся с идентификаторами в качестве ключей (также называемых нормализованными ), заключается в том, что действительно тяжело работать с глубоко вложенными объектами (что вы обычно получаете от 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, поэтому нам просто нужно пройтись по этому списку).
Также довольно просто показать, например, имена пользователей тех, кто создал параметры для элемента фильтра с ID 1:
entities .filterItems[1].options .map(id => entities.options[id]) .map(option => entities.optionCreators[option.created_by].username)
JSON-ответ можно нормализовать, например, с помощью normalizr .
Вероятно, это рекомендация для более сложных приложений с большим количеством глубоко вложенных ответов API. Однако в вашем конкретном примере это не имеет большого значения.
источник
map
возвращает undefined, как здесь , если ресурсы выбираются отдельно, чтоfilter
слишком усложняет задачу. Есть решение?