Это третья часть серии статей о начале работы с Redux, и в этом уроке мы узнаем, как подключить магазин Redux к React. Redux — это независимая библиотека, которая работает со всеми популярными интерфейсными библиотеками и средами. И это работает безупречно с React из-за его функционального подхода.
Вам не нужно следовать предыдущим частям этой серии, чтобы этот урок имел смысл. Если вы здесь, чтобы узнать об использовании React с Redux, вы можете взять краткий обзор ниже, а затем проверить код из предыдущей части и начать с этого.
Быстрый обзор
В первом посте мы узнали о рабочем процессе Redux и ответили на вопрос: почему Redux? Мы создали очень простое демонстрационное приложение и показали, как связаны различные компоненты Redux — действия, редукторы и хранилище.
В предыдущем посте мы начали создавать приложение со списком контактов, которое позволяет добавлять контакты, а затем отображать их в виде списка. Мы создали магазин Redux для нашего списка контактов и добавили несколько редукторов и действий. Мы попытались отправить действия и получить новое состояние, используя методы store, такие как store.dispatch() и store.getState() .
К концу этой статьи вы узнаете:
- разница между компонентами контейнера и презентационными компонентами
- о реактивно-редукционной библиотеке
- Как связать реагировать и редукции с помощью Connect
connect() - как отправлять действия, используя
mapDispatchToProps - как получить состояние, используя
mapStateToProps
Код для учебника доступен на GitHub в репо -реактив-демо- репо. Возьмите код из ветки v2 и используйте его в качестве отправной точки для этого урока. Если вам интересно узнать, как приложение выглядит к концу этого урока, попробуйте ветку v3. Давайте начнем.
Проектирование иерархии компонентов: умные против тупых компонентов
Это концепция, о которой вы, вероятно, слышали раньше, но давайте кратко рассмотрим разницу между умными и немыми компонентами. Напомним, что мы создали два отдельных каталога для компонентов, один из которых назвал контейнеры /, а другие компоненты / . Преимущество этого подхода заключается в том, что логика поведения отделена от представления.
Говорят, что компоненты представления глупы, потому что они обеспокоены тем, как все выглядит. Они отделены от бизнес-логики приложения и получают данные и обратные вызовы от родительского компонента исключительно через подпорки. Им все равно, подключено ли ваше приложение к хранилищу Redux, если данные поступают из локального состояния родительского компонента.
Компоненты контейнера, с другой стороны, имеют дело с поведенческой частью и должны содержать очень ограниченную разметку и стиль DOM. Они передают данные, которые должны быть обработаны, для немых компонентов в качестве реквизита.
Я подробно рассмотрел эту тему в другом учебном пособии « Компоненты с состоянием и без состояния в React» .
Двигаясь дальше, давайте посмотрим, как мы собираемся организовать наши компоненты.

Презентационные компоненты
Вот презентационные компоненты, которые мы будем использовать в этом уроке.
компоненты / AddContactForm.jsx
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import React from ‘react’;
const AddContactForm = ({onInputChange, onFormSubmit}) =>
(
<form>
<div className=»form-group»>
<label htmlFor=»emailAddress»>Email address</label>
<input type=»email» class=»form-control» name=»email» onChange={onInputChange} placeholder=»name@example.com» />
</div>
{/* Some code omitted for brevity */}
<div className=»form-group»>
<label htmlFor=»physicalAddress»>Address</label>
<textarea className=»form-control» name=»address» onChange={onInputChange} rows=»3″></textarea>
</div>
<button type=»submit» onClick={onFormSubmit} class=»btn btn-primary»> Submit </button>
</form>
)
export default AddContactForm;
|
Это HTML-форма для добавления нового контакта. Компонент получает onFormSubmit вызовы onInputChange и onFormSubmit качестве реквизита. Событие onInputChange запускается при изменении входного значения и onFormSubmit при onFormSubmit формы.
компоненты / ContactList.jsx
|
01
02
03
04
05
06
07
08
09
10
11
12
|
const ContactList = (props) => {
return( <ul className=»list-group» id=»contact-list»>
{props.contactList.map(
(contact) =>
<li key={contact.email} className=»list-group-item»>
<ContactCard contact = {contact}/>
</li>
)}
</ul>)
}
export default ContactList;
|
Этот компонент получает массив контактных объектов в качестве реквизита, отсюда и название ContactList . Мы используем метод Array.map() чтобы извлечь отдельные контактные данные, а затем передать эти данные в <ContactCard /> .
компоненты / ContactCard.jsx
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
const ContactCard = ({contact}) => {
return(
<div>
<div className=»col-xs-4 col-sm-3″>
{contact.photo !== undefined ?
<img src=»img/profile_img.png» alt ={contact.name} className=»img-fluid rounded-circle» />}
</div>
<div className=»col-xs-8 col-sm-9″>
<span className=»name»>{contact.name + ‘ ‘ + contact.surname}
{/* Some code omitted for brevity */}
</div>
</div>
)
}
export default ContactCard;
|
Этот компонент получает объект контакта и отображает имя и изображение контакта. Для практических приложений может иметь смысл размещать изображения JavaScript в облаке .
Компоненты контейнера
Мы также собираемся создать пустые контейнерные компоненты.
Контейнеры / Contacts.jsx
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Contacts extends Component {
constructor(props) {
super(props);
this.returnContactList = this.returnContactList.bind(this);
}
returnContactList() {
// Retrieve contactlist from the store
}
render() {
return (
<div>
<AddContact/>
<br />
<ContactList contactList= {this.returnContactList()} />
</div>
);
}
}
export default Contacts;
|
Функция returnContactList() извлекает массив объектов контактов и передает его компоненту ContactList. Поскольку returnContactList() извлекает данные из хранилища, мы пока оставим эту логику пустой.
Контейнеры / AddContacts.jsx
|
01
02
03
04
05
06
07
08
09
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
39
40
41
42
43
44
45
46
47
|
class AddContact extends Component {
constructor(props) {
super(props);
/* Function binding goes here.
}
showAddContactBox() {
/* Logic for toggling ContactForm */
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
/* Logic for handling Input Change */
}
handleSubmit(e) {
e.preventDefault();
/* Logic for hiding the form and update the state */
}
/* Renders the AddContactForm */
renderForm() {
return(
<div className=»col-sm-8 offset-sm-2″>
<AddContactForm onFormSubmit={this.handleSubmit} onInputChange={this.handleInputChange} />
</div>
)
}
render() {
return(
<div>
{ /* A conditional statement goes here that checks whether the form
should be displayed or not */}
</div>
)
}
}
export default AddContact;
|
Мы создали три метода-обработчика, которые соответствуют трем действиям. Все они отправляют действия для обновления состояния. В методе рендеринга мы исключили логику для отображения / скрытия формы, потому что нам нужно получить состояние.
Теперь давайте посмотрим, как связать реагировать и сокращать вместе
Реактивно-редукционная библиотека
Привязки реактивов по умолчанию недоступны в Redux. Сначала вам нужно будет установить дополнительную библиотеку с именемact-redux.
|
1
|
npm install —save react-redux
|
Библиотека экспортирует только два API, которые вам нужно запомнить, компонент <Provider /> и функцию более высокого порядка, известную как connect() .
Компонент провайдера
Такие библиотеки, как Redux, должны сделать доступными данные хранилища для всего дерева компонентов React, начиная с корневого компонента. Шаблон Provider позволяет библиотеке передавать данные сверху вниз. Приведенный ниже код демонстрирует, как поставщик волшебным образом добавляет состояние ко всем компонентам в дереве компонентов.
Демонстрационный код
|
1
2
3
4
5
6
7
8
|
import { Provider } from ‘react-redux’
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById(‘root’)
)
|
Все приложение должно иметь доступ к магазину. Поэтому мы оборачиваем провайдера вокруг компонента приложения и затем добавляем необходимые данные в контекст дерева. Потомки компонента получают доступ к данным.
Метод connect()
Теперь, когда мы предоставили магазин нашему приложению, нам нужно подключить React к магазину. Единственный способ связаться с магазином — отправлять действия и получать данные о состоянии. Ранее мы использовали store.dispatch() для отправки действий и store.getState() для получения последнего снимка состояния. connect() позволяет вам сделать это, но с помощью двух методов, известных как mapDispatchToProps и mapStateToProps . Я продемонстрировал эту концепцию в следующем примере:
Демонстрационный код
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import {connect} from ‘react-redux’
const AddContact = ({newContact, addContact}) => {
return (
<div>
{newContact.name} <br />
{newContact.email} <br />
{newContact.phone} <br />
Are you sure you want to add this contact?
<span onClick={addContact}> Yes
</div>
)
}
const mapStateToProps = state => {
return {
newContact : state.contacts.newContact
}
}
const mapDispatchToProps = dispatch => {
return {
addContact : () => dispatch(addContact())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddContact)
|
mapStateToProps и mapDispatchToProps оба возвращают объект, и ключ этого объекта становится опорой подключенного компонента. Например, state.contacts.newContact сопоставляется с props.newContact . Создатель действия addContact() сопоставлен с props.addContact .
Но чтобы это работало, вам нужна последняя строка в фрагменте кода выше.
|
1
2
3
4
|
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddContact)
|
Вместо того, чтобы экспортировать компонент AddContact напрямую, мы экспортируем связанный компонент. Соединение предоставляет addContact и newContact качестве реквизита для компонента <AddContact/> .
Как соединить React и Redux
Далее мы рассмотрим шаги, которые необходимо выполнить для подключения React и Redux.
Установить библиотеку реаги-редукса
Установите библиотеку реагировать на редукцию, если вы еще этого не сделали. Вы можете использовать NPM или пряжу, чтобы установить его.
|
1
|
npm install react-redux —save
|
Предоставьте магазин вашему компоненту приложения
Сначала создайте магазин. Затем сделайте объект хранилища доступным для вашего дерева компонентов, передав его в качестве реквизита <Provider /> .
index.js
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
import React from ‘react’;
import {render}from ‘react-dom’;
import { Provider } from ‘react-redux’
import App from ‘./App’;
import configureStore from ‘./store’
const store = configureStore();
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById(‘root’)
)
|
Подключите контейнеры React к Redux
Функция connect используется для привязки контейнеров React к Redux. Это означает, что вы можете использовать функцию подключения для:
- подписаться на магазин и сопоставить его состояние с вашими реквизитами
- действия по отправке и сопоставление обратных вызовов в ваши реквизиты
После подключения вашего приложения к Redux вы можете использовать this.props для доступа к текущему состоянию, а также для отправки действий. Я собираюсь продемонстрировать процесс на компоненте AddContact . AddContact должен отправить три действия и получить состояние двух свойств из магазина. Давайте посмотрим на код.
Сначала импортируйте connect в AddContact.jsx .
|
1
|
import { connect } from ‘react-redux’;
|
Во-вторых, создайте два метода: mapStateToProps и mapDispatchToProps .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function mapStateToProps(state) {
return {
isHidden : state.ui.isAddContactFormHidden,
newContact: state.contacts.newContact
}
}
function mapDispatchToProps(dispatch) {
return {
onFormSubmit: (newContact) => {
dispatch(addContact(newContact));
},
onInputChange: (name,value) => {
dispatch(handleInputChange(name,value));
},
onToggle: () => {
dispatch(toggleContactForm());
}
}
}
|
mapStateToProps получает состояние хранилища в качестве аргумента. Он возвращает объект, который описывает, как состояние магазина отображается в ваших реквизитах. mapDispatchToProps возвращает похожий объект, который описывает, как действия по отправке отображаются на ваши реквизиты.
Наконец, мы используем connect для связывания компонента AddContact с двумя функциями следующим образом:
|
1
|
export default connect(mapStateToProps, mapDispatchToProps) (AddContact)
|
Обновите компоненты контейнера для использования реквизита
Опоры компонента теперь оснащены для чтения состояния из хранилища и отправки действий. Логика для handeInputChange , handleSubmit и showAddContactBox должна быть обновлена следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
showAddContactBox() {
const { onToggle } = this.props;
onToggle();
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
const { onInputChange } = this.props;
onInputChange(name,value);
}
handleSubmit(e) {
e.preventDefault();
this.props.onToggle();
this.props.onFormSubmit();
}
|
Мы определили методы-обработчики, но все еще отсутствует одна часть — условный оператор внутри функции render .
|
1
2
3
4
5
6
7
|
render() {
return(
<div>
{ this.props.isHidden === false ?
</div>
)
}
|
Если isHidden имеет значение false, форма отображается. В противном случае кнопка отображается.
Отображение контактов
Мы завершили самую сложную часть. Теперь осталось только отобразить эти контакты в виде списка. Контейнер Contacts — лучшее место для этой логики.
|
01
02
03
04
05
06
07
08
09
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
39
|
import React, { Component } from ‘react’;
import { connect } from ‘react-redux’;
/* Component import omitted for brevity */
class Contact extends Component {
constructor(props) {
super(props);
this.returnContactList = this.returnContactList.bind(this);
}
returnContactList() {
return this.props.contactList;
}
render() {
return (
<div>
<br />
<AddContact/>
<br />
<ContactList contactList= {this.returnContactList()} />
</div>
);
}
}
function mapStateToProps(state) {
return {
contactList : state.contacts.contactList,
}
}
export default connect(mapStateToProps, null) (Contact);
|
Мы прошли ту же процедуру, что и выше, чтобы соединить компонент Contacts с хранилищем Redux. Функция mapStateToProps отображает объект contactList реквизиты contactList . Затем мы используем connect для привязки значения реквизита к компоненту Contact. Второй аргумент для подключения является нулевым, потому что у нас нет никаких действий для отправки. Это завершает интеграцию нашего приложения с состоянием магазина Redux.
Что дальше?
В следующем посте мы более подробно рассмотрим промежуточное ПО и начнем рассылать действия, связанные с извлечением данных с сервера. Поделитесь своими мыслями в комментариях!
