Подключение Redux к React с помощью библиотеки
Development | Комментировать запись
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: React, Redux