Что такое нормализация данных в Redux?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое нормализация данных в Redux?
Нормализация данных в Redux — это архитектурный подход к организации состояния приложения, при котором вложенные или реляционные данные преобразуются в плоскую (денормализованную) структуру. Основная цель — обеспечить единственность истины для каждого фрагмента данных, избегая дублирования и упрощая операции обновления. В контексте Redux, где состояние должно быть неизменяемым и предсказуемым, нормализация становится ключевой практикой для управления сложными данными, такими как ответы от API.
Проблемы, которые решает нормализация
Без нормализации состояние в Redux часто содержит дублированные данные и вложенные структуры, что приводит к:
- Несогласованности: при обновлении объекта в одном месте, его копии в других частях состояния устаревают.
- Сложности обновлений: для изменения вложенных данных требуются глубокие копии (deep copies) с использованием spread-операторов или библиотек вроде
immer, что усложняет код. - Проблемам с производительностью: компоненты могут перерисовываться из-за изменений в несвязанных частях состояния.
- Трудностям в кэшировании: сложно определить, есть ли объект уже в состоянии.
Принципы нормализации данных
Нормализация в Redux следует тем же принципам, что и в реляционных базах данных:
- Каждый тип данных хранится в своей "таблице" (отдельном слайсе или разделе состояния). Например, пользователи, посты, комментарии.
- Каждая запись (entity) имеет уникальный идентификатор (ID), который выступает в качестве ключа.
- Ссылки между записями осуществляются через эти ID, а не через вложенные объекты.
- Массивы ID используются для сохранения порядка записей (например, список ID постов в ленте).
Пример: денормализованные vs нормализованные данные
Денормализованный ответ API (проблемный):
// Пользователи и их посты вложены друг в друга
const denormalizedState = {
posts: [
{
id: 1,
title: 'Post 1',
author: { id: 101, name: 'Alice' }, // Дубликат автора!
comments: [
{ id: 1001, text: 'Cool!', user: { id: 102, name: 'Bob' } },
{ id: 1002, text: 'Nice', user: { id: 101, name: 'Alice' } } // Дубликат Alice!
]
},
{
id: 2,
title: 'Post 2',
author: { id: 101, name: 'Alice' }, // Ещё один дубликат!
comments: []
}
]
}
Нормализованное состояние (рекомендуемый подход):
const normalizedState = {
entities: {
posts: {
byId: {
'1': { id: 1, title: 'Post 1', author: 101, comments: [1001, 1002] },
'2': { id: 2, title: 'Post 2', author: 101, comments: [] }
},
allIds: [1, 2] // Порядок постов
},
users: {
byId: {
'101': { id: 101, name: 'Alice' }, // Одна запись на пользователя
'102': { id: 102, name: 'Bob' }
}
},
comments: {
byId: {
'1001': { id: 1001, text: 'Cool!', user: 102 },
'1002': { id: 1002, text: 'Nice', user: 101 }
}
}
}
}
Как работать с нормализованными данными в Redux
-
Селекторы: используют резолвинг (denormalization на стороне чтения) для сборки нужных компонентам данных.
// Пример селектора с помощью Reselect import { createSelector } from '@reduxjs/toolkit'; const selectPostsState = state => state.entities.posts; const selectUsersState = state => state.entities.users; export const selectPostsWithAuthors = createSelector( [selectPostsState, selectUsersState], (posts, users) => { return posts.allIds.map(id => ({ ...posts.byId[id], author: users.byId[posts.byId[id].author] // Подставляем автора по ID })); } ); -
Редюсеры: обновляют данные идемпотентно и точечно.
// Обновление имени пользователя становится тривиальным case 'USER_UPDATED': return { ...state, entities: { ...state.entities, users: { ...state.entities.users, byId: { ...state.entities.users.byId, [action.payload.id]: { ...state.entities.users.byId[action.payload.id], ...action.payload.changes } } } } }; -
Библиотеки для нормализации:
* **Redux Toolkit**: включает `createEntityAdapter` — утилиту, которая автоматически генерирует редюсеры, селекторы и структуру `{ ids: [], entities: {} }`.
```javascript
import { createEntityAdapter } from '@reduxjs/toolkit';
const postsAdapter = createEntityAdapter();
const initialState = postsAdapter.getInitialState();
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
postAdded: postsAdapter.addOne,
postUpdated: postsAdapter.updateOne,
}
});
```
* **Normalizr** (от создателя Redux): библиотека для преобразования вложенных JSON в нормализованную схему.
Преимущества и недостатки
Преимущества:
- Устранение дублирования: каждая сущность хранится один раз.
- Эффективные обновления: изменения вносятся в одно место.
- Предсказуемость: состояние становится проще для понимания.
- Оптимизация производительности: через мемоизированные селекторы.
Недостатки:
- Накладные расходы на преобразование: необходимость нормализации при получении данных и денормализации при чтении.
- Сложность для простых случаев: для trivial -приложений может быть избыточной.
- Когнитивная нагрузка: требуется дополнительная абстракция в коде.
Заключение
Нормализация данных в Redux — это не догма, а инструмент для управления сложностью. Для небольших приложений с простыми данными можно обойтись и без неё. Однако в средних и крупных проектах, особенно с реляционными данными, частыми обновлениями и требованиями к производительности, нормализация становится необходимым стандартом. Использование createEntityAdapter из Redux Toolkit значительно снижает порог входа и устраняет бойлерплейт, делая этот паттерн более доступным для разработчиков.