Использование с React¶
Для начала стоит подчеркнуть, что Redux не имеет отношения к React. Вы можете создавать Redux-приложения c помощью React, Angular, Ember, jQuery или обычного JavaScript.
И все-таки, Redux работает особенно хорошо с такими фреймворками, как React и Deku, потому что они позволяют вам описать UI как функцию состояния, и, кроме того, Redux умеет менять состояние (state) приложения в ответ на произошедшие экшены (actions).
Мы будем использовать React для создания нашего простого приложения todo и рассмотрим основы использования React с Redux.
Примечание
Смотрите официальную документацию React-Redux для полного руководства о том, как использовать Redux и React вместе.
Установка React Redux¶
React bindings не включены в redux по умолчанию. Вам нужно установить их явно:
1 |
|
Если вы не используете npm, то можете взять последнюю UMD-сборку из unpkg (development или production). Добавив UMD-сборку на страницу при помощи тега <script>
, вы получите глобальный window.ReactRedux
.
Презентационные компоненты и компоненты-контейнеры¶
React байндинг для Redux отделяют презентационные компоненты от компонент-контейнеров Такой подход может облегчить понимание вашего приложения и упростить повторное использование компонентов. Вот краткое изложение различий между презентационными и контейнерными компонентами (но если вы незнакомы, мы рекомендуем вам также прочитать оригинальную статью Дэна Абрамова, описывающую концепцию презентационных и контейнерных компонентов):
Компоненты-представления | Компоненты-контейнеры | |
---|---|---|
Назначение | Как выглядит (разметка, стили) | Как работает (загрузка данных, обновление состояния) |
Знают о Redux | Нет | Да |
Читают данные | Читают данные из props | Подписываются на Redux-состояние |
Изменяют данные | Вызывают колбеки из props | Отправляют Redux-экшены |
Написаны | Руками | Обычно генерируются React Redux |
Большинство компонентов, которые мы напишем, будут представлениями, но чтобы соединить их с Redux-состоянием, нам потребуется сгенерировать несколько контейнеров. Это и дальнейшее описание не означает, что компоненты-контейнеры должны быть расположены ближе к вершине дерева компонентов. Если компонент-контейнер становится слишком сложным, т. е. он имеет сильную вложенность презентационных компонентов, с бесчисленным количеством обратных вызовов, передающихся вниз, используйте еще один контейнер в дереве компонентов, как отмечено в FAQ.
Технически, вы можете написать контейнеры вручную, используя store.subscribe()
. Мы не советуем вам это делать, потому что React Redux производит много оптимизаций производительности, которые было бы трудно написать руками. По этой причине, вместо того чтобы писать контейнеры, мы генерируем их, воспользовавшись функцией connect()
, предоставленной React Redux, об этом ниже.
Проектирование иерархии компонентов¶
Помните, как мы спроектировали структуру корневого объекта состояния? В этот раз мы спроектируем иерархию UI-компонентов, которая будет соответствовать этой структуре. С такого рода задачей Вы можете столкнуться, разрабатывая и не Redux-приложение. Thinking in React — великолепное руководство, которое поясняет весь процесс решения этой задачи.
Наш бриф довольно прост. Мы хотим показать список дел (todo). По клику мы должны зачеркнуть дело, что будет означать, что оно выполнено. Также мы хотим показать поле ввода, с помощью которого пользователь сможет добавить новое дело в список. В футере должны быть переключатели, с помощью которых мы будем показывать все дела, только завершенные, только не завершенные.
Разработка презентационных компонентов¶
Из этого брифа получаются следующие представления и их props:
TodoList
— список, показывающий видимые todos.
todos: Array
— массив todo-объектов, имеющих форму{ id, text, completed }
.onTodoClick(id: number)
— колбек, который будет вызван при клике на todo.
Todo
— отдельный todo.
text: string
— текст для отображения.completed: boolean
— должен ли todo показываться зачеркнутым.onClick()
— колбек, который будет вызван при клике на todo.
Link
— ссылка с колбеком.
onClick()
— колбек, который будет вызван при клике на ссылку.
Footer
— область, где мы позволим пользователю менять текущую видимость todos.
App
— корневой компонент, который рендерит все остальное.
Они описывают вид, но не знают откуда приходят данные или как изменить их. Они только рендерят то, что им дают. Если вы мигрируете с Redux на что-нибудь другое, вы сможете оставить эти компоненты точно такими же. Они не зависят от Redux.
Проектирование компонент-контейнеров¶
Нам также потребуются некоторые контейнеры, чтобы соединить представления с Redux. Например, представлению TodoList
требуется контейнер VisibleTodoList
, который подписывается на Redux-стор и знает, как применять текущий фильтр видимости. Чтобы изменить фильтр видимости, мы предоставим представлению FilterLink
, контейнер, который рендерит Link
, а тот, в свою очередь, отправляет соответствующий экшен при клике:
VisibleTodoList
— фильтрует todos согласно текущему фильтру видимости и рендерит TodoList
.
FilterLink
— получает текущий фильтр видимости и рендерит Link
.
filter: string
— текущий фильтр видимости.
Проектирование других компонент¶
Иногда трудно сказать, каким должен быть компонент — представлением или контейнером. Например, иногда форма и функция действительно соединены вместе, как в случае с этим миниатюрным компонентом:
AddTodo
— инпут с кнопкой "Добавить"
Технически, мы могли бы разделить его на два компонента, но это может быть слишком рано на данном этапе. Вполне допустимо смешивать представление и логику, когда компонент очень маленький. Как только он вырастет, станет более понятно как разделить его, так что мы пока оставим его смешанным.
Реализуем компоненты¶
Давайте напишем компоненты! Мы начнем с представлений, так что пока нам не нужно думать о привязке к Redux.
Компоненты-представления¶
Это все обычные React-компоненты, поэтому мы не будем изучать их детально. Мы пишем функциональные stateless-компоненты, пока нам не потребуются локальное состояние или lifecycle-методы. Это не значит, что представления должны быть функциями, просто так легче. Если/когда вам потребуется добавить локальное состояние, lifecycle-методы или оптимизацию производительности, вы сможете конвертировать их в классы.
components/Todo.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
components/TodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
components/Link.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
components/Footer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
components/App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Компоненты-контейнеры¶
А теперь настало время подключить эти компоненты представления к Redux, создав некоторые компоненты-контейнеры. Технически, контейнер — это просто React-компонент, который использует store.subscribe()
для чтения части Redux-дерева состояний и поставляет props представлению, которое он рендерит. Вы можете написать компонент-контейнер вручную, но вместо этого мы предлагаем генерировать контейнеры с помощью библиотечной функции React Redux connect()
, которая предоставляет много полезных оптимизаций для предотвращения ненужных ре-рендеров. (Одним из результатов этого является то, что вам больше не придется беспокоиться о React performance suggestion своей реализации shouldComponentUpdate
).
Чтобы использовать connect()
, вам нужно определить специальную функцию mapStateToProps
, которая говорит, как трансформировать текущее Redux-состояние стора в props, которые вы хотите передать в оборачиваемое (контейнером) представление. Например, VisibleTodoList
требуется вычислить todos
для передачи в TodoList
, так что нам нужно определить функцию, которая фильтрует state.todos
согласно state.visibilityFilter
, и использовать ее в mapStateToProps
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
В дополнение к чтению состояния контейнеры могут отправлять экшены (dispatch actions). В похожем стиле вы можете определить функцию mapDispatchToProps()
, которая получает метод dispatch()
и возвращает колбек props, который вы можете вставить в представление. Например, мы хотим, чтобы контейнер VisibleTodoList
вставил prop onTodoClick
в представление TodoList
и еще мы хотим, чтобы onTodoClick
отправлял TOGGLE_TODO
экшен:
1 2 3 4 5 6 7 |
|
Наконец, мы создаем VisibleTodoList
вызывая connect()
и передал эти две функции:
1 2 3 4 5 6 7 8 |
|
Это основы React Redux API, но там есть несколько комбинаций и мощных опций, поэтому мы рекомендуем вам подробно изучить эту документацию. В случае если вы переживаете, что mapStateToProps
создает слишком много новых объектов, то вам будет полезно узнать о вычислении полученных данных с reselect.
Остальные компоненты-контейнеры вы найдете ниже:
containers/FilterLink.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
containers/VisibleTodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
Другие Компоненты¶
containers/AddTodo.js
Напомним, как было упомянуто ранее и представление, и логика для компонента AddTodo
смешаны в одном определении.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Если вы не знакомы с атрибутом ref
, прочитайте эту документацию, чтобы ознакомиться с рекомендуемым использованием этот атрибут.
Связывание контейнеров внутри компонента¶
components/App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Передаем стор¶
Всем компонентам-контейнерам необходим доступ к Redux-стору (store), для того чтобы они могли подписаться на него. Как вариант — передать его как prop в каждый контейнер. Однако это становится утомительным, так как вы должны подключать store
, даже если представления просто рендерят контейнер глубоко в дереве компонентов.
Мы рекомендуем другой вариант — использовать специальный React Redux компонент <Provider>
, вызов которого магически делает стор доступным всем контейнерам в приложении без его явной передачи. Вам нужно только воспользоваться им единожды, когда вы рендерите корневой компонент:
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Следующие шаги¶
Прочитайте полный исходный код для этого руководства для лучшего усваивания полученных знаний. А затем прямиком в руководство для опытных для изучения обработки сетевых запросов и роутинга Вам также нужно потратить некоторое время, чтобы прочитать документы React-Redux, чтобы получить лучшее понимание того, как использовать React и Redux вместе.