Подключение Redux к React с помощью библиотеки

Redux – это отдельная от React сущность, которую можно использовать с любым фронт-энд фреймворком JavaScript или с обычным JavaScript. Однако нельзя отрицать, что React и Redux очень часто используются вместе. Чтобы облегчить настройку этой связки, библиотека React Redux предоставляет простые привязки.

API для привязок React Redux очень прост: в него входит компонент Provider, который делает хранилище доступным для нашего приложения, и функция подключения, создающая компоненты-контейнеры, которые могут считывать состояние из хранилища и отправлять действия (экшены).

Подготовка

Для начала давайте инициализируем проект React и установим все необходимые зависимости. Воспользуемся Create React App, чтобы создать тестовое приложение для управления закладками:

$ npx create-react-app fancy-bookmarks

Читайте также: Как использовать Typescript с Create React App

Npx позволяет убедиться, что мы используем последнюю версию Create React Ap.

Теперь перейдем в каталог приложения и добавим пакеты redux и response-redux:

$ yarn add redux react-redux
# or, using npm:
$ npm install redux react-redux

Настройка Redux

После этого займемся настройкой Redux для нашего приложения. Поскольку здесь не будет подробных объяснений, советуем вам ознакомиться с нашим вступительным мануалом.

Читайте также: Введение в Redux: основные понятия

Во-первых, в файле actions/types.js установим некоторые типы действий:

export const ADD_BOOKMARK = 'ADD_BOOKMARK';
export const DELETE_BOOKMARK = 'DELETE_BOOKMARK';

А в файле actions/index.js – создатели действий:

import uuidv4 from 'uuid/v4';
import { ADD_BOOKMARK, DELETE_BOOKMARK } from './types';

export const addBookmark = ({ title, url }) => ({
  type: ADD_BOOKMARK,
  payload: {
    id: uuidv4(),
    title,
    url
  }
});

export const deleteBookmark = id => ({
  type: DELETE_BOOKMARK,
  payload: {
    id
  }
});

Примечание: Обратите внимание, для генерирования случайных идентификаторов мы используем библиотеку uuid.

Нашему простому приложению нужен всего один редуктор, который мы определим в reducer/index.js:

import { ADD_BOOKMARK, DELETE_BOOKMARK } from '../actions/types';

export default function bookmarksReducer(state = [], action) {
  switch (action.type) {
    case ADD_BOOKMARK:
      return [...state, action.payload];
    case DELETE_BOOKMARK:
      return state.filter(bookmark => bookmark.id !== action.payload.id);
    default:
      return state;
  }
}

Как видите, пока что мы работаем исключительно в области Redux и еще ничего не сделали, чтобы наше приложение React могло беспрепятственно взаимодействовать с хранилищем Redux. Этим мы и займемся дальше.

Компонент Provider

Мы будем использовать компонент Provider из библиотеки React Redux, чтобы обернуть основной компонент приложения и сделать хранилище Redux доступным из любого (подключенного) компонента-контейнера в дереве компонентов React (файл index.js):

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers';

import App from './App';

const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Если бы мы использовали React Router, мы бы разместили компонент Provider вокруг компонента BrowserRouter.

Компоненты-контейнеры и функция connect

Теперь, когда хранилище Redux доступно в нашем приложении, мы можем создать компоненты-контейнер (также известные как подключенные компоненты), которые будут иметь доступ для чтения из хранилища или отправки действий. Мы создадим два компонента-контейнера: AddBookmark и BookmarkList, а компонент App будет использовать их. С учетом вышеописанных обновлений файл App.js будет выглядеть следующим образом:

import React, { Component } from 'react';
import AddBookmark from './containers/AddBookmark';
import BookmarksList from './containers/BookmarksList';

class App extends Component {
  render() {
    return (
      <div>
        <AddBookmark />
        <BookmarksList />
      </div>
    );
  }
}

export default App;

Теперь напишем наш первый подключенный компонент, BookmarksList, в файле containers/BookmarksList.js:

import React from 'react';
import { connect } from 'react-redux';
import Bookmark from '../components/Bookmark';
import { deleteBookmark } from '../actions';

function BookmarksList({ bookmarks, onDelete }) {
  return (
    <div>
      {bookmarks.map(bookmark => {
        return (
          <Bookmark bookmark={bookmark} onDelete={onDelete} key={bookmark.id} />
        );
      })}
    </div>
  );
}

const mapStateToProps = state => {
  return {
    bookmarks: state
  };
};

const mapDispatchToProps = dispatch => {
  return {
    onDelete: id => {
      dispatch(deleteBookmark(id));
    }
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BookmarksList);

Мы используем функцию connect из библиотеки React Redux и передаем функции mapStateToProps и mapDispatchToProps. Затем мы вызываем возвращенную функцию с компонентом, который хотим подключить.

mapStateToProps получает текущее значение состояния и должен вернуть объект, который делает части состояния доступными в качестве свойств для подключенного компонента. В данном примере есть только состояние bookmarks, но представить сценарий, в котором несколько частей состояния отображаются на разные свойства, довольно просто. Например:

const mapStateToProps = state => {
  return {
    users: state.users,
    todos: state.todos,
    // ...
  };
};

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

Далее мы напишем компонент AddBookmark в файле containers/AddBookmark.js:

import { connect } from 'react-redux';
import { addBookmark } from '../actions';
import NewBookmark from '../components/NewBookmark';

const mapDispatchToProps = dispatch => {
  return {
    onAddBookmark: bookmark => {
      dispatch(addBookmark(bookmark));
    }
  };
};

export default connect(
  null,
  mapDispatchToProps
)(NewBookmark);

Этому компоненту не нужно читать данные из хранилища, поэтому в качестве первого аргумента функции connect мы передаем null.

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

Презентационные компоненты

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

Например, вот так выглядит презентационный компонент Bookmark, который отображает одну закладку (файл components/Bookmark.js):

import React from 'react';

const styles = {
  borderBottom: '2px solid #eee',
  background: '#fafafa',
  margin: '.75rem auto',
  padding: '.6rem 1rem',
  maxWidth: '500px',
  borderRadius: '7px'
};

export default ({ bookmark: { title, url, id }, onDelete }) => {
  return (
    <div style={styles}>
      <h2>{title}</h2>
      <p>URL: {url}</p>
      <button type="button" onClick={() => onDelete(id)}>
        Remove
      </button>
    </div>
  );
};

Как видите, в качестве свойств он получает bookmark, а также обратный вызов onDelete.

А вот NewBookmark (файл components/NewBookmark.js) – не чисто презентационный, а скорее гибридный компонент, поскольку он содержит некоторое локальное состояние для входных значений. Тем не менее, этот компонент вообще не знает о Redux.

import React from 'react';

class NewBookmark extends React.Component {
  state = {
    title: '',
    url: ''
  };

  handleInputChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  handleSubmit = e => {
    e.preventDefault();
    if (this.state.title.trim() && this.state.url.trim()) {
      this.props.onAddBookmark(this.state);
      this.handleReset();
    }
  };

  handleReset = () => {
    this.setState({
      title: '',
      url: ''
    });
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="title"
          name="title"
          onChange={this.handleInputChange}
          value={this.state.title}
        />
        <input
          type="text"
          placeholder="URL"
          name="url"
          onChange={this.handleInputChange}
          value={this.state.url}
        />
        <hr />
        <button type="submit">Add bookmark</button>
        <button type="button" onClick={this.handleReset}>
          Reset
        </button>
      </form>
    );
  }
}

export default NewBookmark;

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

Чтобы узнать больше, советуем заглянуть в официальную документацию.

Tags: ,

Добавить комментарий