Статьи

Начало работы с Redux: соединение Redux с React

Это третья часть серии статей о начале работы с Redux, и в этом уроке мы узнаем, как подключить магазин Redux к React. Redux — это независимая библиотека, которая работает со всеми популярными интерфейсными библиотеками и средами. И это работает безупречно с React из-за его функционального подхода.

Вам не нужно следовать предыдущим частям этой серии, чтобы этот урок имел смысл. Если вы здесь, чтобы узнать об использовании React с Redux, вы можете взять краткий обзор ниже, а затем проверить код из предыдущей части и начать с этого.

В первом посте мы узнали о рабочем процессе Redux и ответили на вопрос: почему Redux? Мы создали очень простое демонстрационное приложение и показали, как связаны различные компоненты Redux — действия, редукторы и хранилище.

В предыдущем посте мы начали создавать приложение со списком контактов, которое позволяет добавлять контакты, а затем отображать их в виде списка. Мы создали магазин Redux для нашего списка контактов и добавили несколько редукторов и действий. Мы попытались отправить действия и получить новое состояние, используя методы store, такие как store.dispatch() и store.getState() .

К концу этой статьи вы узнаете:

  1. разница между компонентами контейнера и презентационными компонентами
  2. о реактивно-редукционной библиотеке
  3. Как связать реагировать и редукции с помощью Connect connect()
  4. как отправлять действия, используя mapDispatchToProps
  5. как получить состояние, используя mapStateToProps

Код для учебника доступен на GitHub в репо -реактив-демо- репо. Возьмите код из ветки v2 и используйте его в качестве отправной точки для этого урока. Если вам интересно узнать, как приложение выглядит к концу этого урока, попробуйте ветку v3. Давайте начнем.

Это концепция, о которой вы, вероятно, слышали раньше, но давайте кратко рассмотрим разницу между умными и немыми компонентами. Напомним, что мы создали два отдельных каталога для компонентов, один из которых назвал контейнеры /, а другие компоненты / . Преимущество этого подхода заключается в том, что логика поведения отделена от представления.

Говорят, что компоненты представления глупы, потому что они обеспокоены тем, как все выглядит. Они отделены от бизнес-логики приложения и получают данные и обратные вызовы от родительского компонента исключительно через подпорки. Им все равно, подключено ли ваше приложение к хранилищу Redux, если данные поступают из локального состояния родительского компонента.

Компоненты контейнера, с другой стороны, имеют дело с поведенческой частью и должны содержать очень ограниченную разметку и стиль DOM. Они передают данные, которые должны быть обработаны, для немых компонентов в качестве реквизита.

Я подробно рассмотрел эту тему в другом учебном пособии « Компоненты с состоянием и без состояния в React» .

  • реагировать
    Функциональные компоненты с состоянием и без состояния в React
    Манджунатх М

Двигаясь дальше, давайте посмотрим, как мы собираемся организовать наши компоненты.

Проектирование иерархии компонентов

Вот презентационные компоненты, которые мы будем использовать в этом уроке.

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 формы.

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 /> .

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 в облаке .

Мы также собираемся создать пустые контейнерные компоненты.

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() извлекает данные из хранилища, мы пока оставим эту логику пустой.

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’)
)

Все приложение должно иметь доступ к магазину. Поэтому мы оборачиваем провайдера вокруг компонента приложения и затем добавляем необходимые данные в контекст дерева. Потомки компонента получают доступ к данным.

Теперь, когда мы предоставили магазин нашему приложению, нам нужно подключить 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.

Установите библиотеку реагировать на редукцию, если вы еще этого не сделали. Вы можете использовать NPM или пряжу, чтобы установить его.

1
npm install react-redux —save

Сначала создайте магазин. Затем сделайте объект хранилища доступным для вашего дерева компонентов, передав его в качестве реквизита <Provider /> .

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’)
)

Функция connect используется для привязки контейнеров React к Redux. Это означает, что вы можете использовать функцию подключения для:

  1. подписаться на магазин и сопоставить его состояние с вашими реквизитами
  2. действия по отправке и сопоставление обратных вызовов в ваши реквизиты

После подключения вашего приложения к 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.

В следующем посте мы более подробно рассмотрим промежуточное ПО и начнем рассылать действия, связанные с извлечением данных с сервера. Поделитесь своими мыслями в комментариях!