Теперь начнем разбираться в том как, мы работаем с данными. Пока, для отображения статей мы использовали денормализированную структуру. Ее видно в файле fixtures.js
. В каждой статье есть вся информация о ней, она напоминает древовидную структуру. Подобная структура удобна для чтения, но она превратит вашу жизнь в ад если вы начнете как-то изменять эти данные. Если у вас будет более или менее сложная структура, где эти зависимости будут пересекаться, например, у статьи есть автор у комментария есть автор, у автора есть своя страница. Если хранить это в том виде как это есть сейчас, то когда вы захотите поменять имя этого автора, вам придется просмотреть все места где даже чисто теоретически он может использоваться и заменить эти данные, и скорее всего Вы что-то пропустите. Поэтому, перед тем как сохранять данные в stores
их нормализируют. Нужно отдельно хранить сущности комментариев, статей, авторов и.т.п, и отдельно хранить связи (например у статьи есть много комментариев), в виде id
.
На клиенте удобно хранить данные в одном месте, к примеру, там где много комментариев, там сразу можно хранить массив с id
этих комментариев. Это довольно удобно, так как все манипуляции с нашими данными можно будет делать в одномreducer
. Давайте реализуем такую структуру. Сделаем reducer
для комментариев, для этого в папке reducer
создадим файл comments.js
:
import { } from '../constants' export default (comments = {}, action) => { const { type, payload, response, error } = action switch (type) { } return comments }
Также мы немного изменим то, как мы храним данные, сейчас мы храним их в виде обычного массива, и это не очень удобно, если мы хотим работать с данными. Нам часто нужно обращаться к конкретному элементу, поэтому будет удобнее хранить данные в виде объектов, ключами в которых выступают id
(статьи например), а их значением будет сама статья. Таким образом мы значительно упростим обращение к элементам нашего store
. Давайте все это перестроим и начнем с reducer/articles.js
. В articles
мы будем уже брать normalizedArticles
, изменим import
следующим образом:
import { normalizedArticles } from '../fixtures'
Также в reducer/comments.js
добавим import:
import { normalizedComments } from '../fixtures'
Продолжим наш урок с использованием библиотеки immutable.js
. Установим ее:
npm i immutable --s
У immutable.js
есть разные структуры данных, одна из самых часто используемых List
, это аналог массива, просто immutable
. Map
это аналог объекта.OrderedMap
– это вариация Map
где вы сохраняете последовательность в которой были добавлены элементы. И еще есть такой элемент Record
, в который позволяет описать вид в котором у Вас находится элемент. Давайте используем Record
и OrderedMap
в нашем проекте. Сделаем следующие записи в reducer/articles.js
:
import { normalizedArticles } from '../fixtures' import { DELETE_ARTICLE } from '../constants' import { OrderedMap, Record } from 'immutable' const Article = Record({ "id": "", "date": "", "title": "", "text": "", "comments": [] }) const defaultArticles = normalizedArticles.reduce((acc, el) => { return acc.set(el.id, new Article(el)) }, new OrderedMap({})) export default (articles = defaultArticles, action) => { const { type, payload } = action switch (type) { case DELETE_ARTICLE: return articles.filter(article => article.id != payload.id) } return articles }
В переменной defaultArticles
мы с Вами идем по массиву normalizedArticles
и с помощью метода reduce
проходим от первого до последнего элемента массива, собирая их в какую-то другую структуру, в нашем случает в immutabele
объект acc
, также здесь есть el
– это элемент к которому мы сейчас обращаемся (первая статья, вторая статья и т.д.). И начальное значение acc
, то с которого мы начинаем – new OrderedMap({}))
. Возвращаем мы с Вами:
acc.set(el.id, new Article(el))
В константе const Article
вы описываете то, какого вида у Вас статьи. В нашем случае мы указываем default
значения для статей. Все записи будут храниться в таком виде. В данном случае можно было воспользоваться и обычными объектами и методом map
и тогда нечего особенно для Вас не поменяется. Но Record
придуман именно для подобных записей. У Вас появляется гарантированная структура Ваших данных (статей) и она будет всегда выдержана . Также будет удобнее обращаться к immutabele
объекту article
, к примеру при использовании простого объекта будет выглядеть так:
article.get('title')
, если мы используем Record
, у нас уже есть готовый getter
и мы сможем просто обращаться:article.title
.
Далее нужно будет немного поменять наш API
, перейдем в containers/Filters.js
и сделаем следующие изменения:
export default connect(state => { const { articles, filters } = state return { articles: articles.valueSeq(), filters } }, { changeFilters
Раньше мы рассчитывали что будем работать с массивом, а сейчас мы получаем объект. Поэтому мы преобразуем его в connect
. Для того чтобы превратить структуру OrderedMap
в массив List
мы вызываем метод valueSeq()
таким образом у нас получится просто массив статей.
Также у нас могут быть проблемы в select
, т.к. мы ожидали что в нем у нас приходит массив, а теперь мы получаем custom structure
. Чтобы ошибок не произошло необходимо пойти в атрибуту options
превратив их в toJS()
:
<Select options = {options.toJS()} multi = {true} value = {filters.selectedArticles} onChange = {this.handleSelectChange} />
Также необходимо будет исправить containers/Articles.js
:
function filterArticles(articles, { from, to, selectedArticles }) { return articles.valueSeq()
Почему мы вызываем valueSeq()
? Потому, что articles
– это объект (ключ : значение), а мы хотим достать из него значения и поместить их в массив. Аналогично этому есть также keySeq
, для того чтобы достать ключи. Дальше преобразовывать ничего не надо, так как React прекрасно понимает immutable.js,
и его структуры. Поэтому не надо переводить их в нативные JS структуры.
Код урока вы можете найти в нашем репозитории.
We are looking forward to meeting you on our website soshace.com