Это третья часть серии статей о начале работы с 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=»[email protected]» />
</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.
Что дальше?
В следующем посте мы более подробно рассмотрим промежуточное ПО и начнем рассылать действия, связанные с извлечением данных с сервера. Поделитесь своими мыслями в комментариях!