Всем привет! Продолжаем работу над нашим приложением. Мы с Вами уже умеем делать простые обращения к серверу и в 80% случаев этих знаний будет достаточно для выполнения типовых задач. Но конечно же, как работать с более сложными вещами мы тоже разберемся. Но прежде чем начать с этим разбираться спешу Вас предупредить, что middleware
далеко не всегда надо писать самостоятельно. Т.к. это такие часто используемые (переиспользуемые) элементы, которые уже были написаны до Вас. Рекомендуем ознакомиться с данным ресурсом, здесь можно найти огромное количество разнообразных middlewares
и т.д. К примеру вот готовый logger
. Мы писали с Вами logger
чтобы попрактиковаться, но вот пример прекрасного готового решения.
Иногда бывает так, что мы хотим выполнить dispatch
нескольких actions
. Например в AC/articles.js
deleteArticle
мы делаем dispatch
одного action
. Но иногда нам нужно проделать это над несколькими actions
. Чтобы каждый раз не писать для этого middleware
мы можем просто вернуть массив type:'OTHER_ACTION"
export function deleteArticle(id) { return [{ type: DELETE_ARTICLE, payload: { id } }, { type:'OTHER ACTION' }] }
и чтобы они были dispatch
(‘задиспачились’) один за другим. Для этого существует middleware
redux-multi
. Как она работает?
Вспомните как мы с вами делали проверку – интересует ли нас какой-то action
или нет? Мы делали проверку по какому-либо флажку (как в middlewares/api.js
), но можно это сделать и по-другому. Например, мы знаем, что в reducers
должны попасть “плоские” объекты. Поэтому мы можем сделать проверку по типу. Если тип не объект, а массив – то разбить их и сделать dispatch
одного за другим. Это и делает redux-multi
. Если к нему приходит массив с actions
он делает dispatch
одного за другим. Нужно понимать, что разбивка на action
происходит внутри middleware
, но вы пишите их в одном месте для удобства (в action creator). И пожалуй самая популярная middleware
в Redux это redux-thunk
. По аналогии redux-multi
он проверяет по типу что к нему приходит, и если это не объект а функция, то он вызывает ее внутри себя и далее уже происходит dispatch
. С его помощью можно делать асинхронные actions
(на самом деле как бы асинхронные). Например загрузку статей мы смогли бы описать несколько по-другому.
Установим redux-thunk:
npm i redux-thunk --S
Зайдем в AC/articles.js
и добавим:
export function loadAllArticlesAlt() { return (dispatch, state) => { dispatch({ type: LOAD_ALL_ARTICLES + START }) setTimeout(() => { $.get('/api/article') .done(response => dispatch({ type: LOAD_ALL_ARTICLES + SUCCESS, response })) .fail(error => dispatch({ type: LOAD_ALL_ARTICLES + FAIL, error })) }, 1000) }
Мы возвращаем функцию у которой будет доступ к dispatch
, здесь мы скопировали часть логики из middlewares/api.js
, чтобы не писать дважды, конечно же предварительно немного её изменив . Мы будем делать тоже самое действие, что и до этого, просто другим способом.
Также изменим import
в этом же файле:
import { DELETE_ARTICLE, LOAD_ALL_ARTICLES, START, SUCCESS, FAIL } from '../constants' import $ from 'jquery'
Не забудем подключить эту middleware
в store
, для этого перейдем в store/index.js
и добавим:
import thunk from 'redux-thunk'
И поставим ее где-то в самом начале, так как все наши middleware
ожидают какого-то объекта, а если они получат не объект а функцию, у нас могут появиться ошибки.
const enhancer = compose( applyMiddleware(thunk, dumbMiddleware, randomId, api, logger), window.devToolsExtension ? window.devToolsExtension() : f => f )
А поставив redux-thunk
первым номером он проверит – приходит функция или нет, если да, то будет работать с ней, если нет, передаст управление в другие middlewares
. Теперь убедимся, что у нас все работает, но предварительно зайдем в containers/Articles.js
и изменим один из import
таким образом:
import { loadAllArticles, loadAllArticlesAlt } from '../AC/articles'
И в этом же файле в export defaults
изменим функцию которую мы вызываем на loadAllArticlesAlt
:
export default connect(({ articles, filters }) => { return { loading: articles.get('loading'), articles: filterArticles(articles.get('entities'), filters) } }, { loadAllArticles: loadAllArticlesAlt })(Articles)
Проверяем, все должно прекрасно работать! Посмотрите, мы пишем асинхронные actions
, (к примеру, взгляните на код в AC/articles.js
) прямо в action creator
, хотя на самом деле, код который мы написали исполняется в middlewares
. Здесь можно писать достаточно сложную логику, также у вас есть доступ к состоянию store
:
export function loadAllArticlesAlt() { return (dispatch, state) => { dispatch({ type: LOAD_ALL_ARTICLES + START })
Таким образом вы можете добавить что-то из store
для реализации нужной вам логики.
Мы подключили redux-thunk
вместе с остальными middleware
в store
, он проверяет тип action
который к нему пришел. Когда вы пишите middleware
Вам зачастую нужно проверить , что за action
к Вам пришел, интересует ли его эта middleware
или вам необходимо передать управление дальше. Мы делали проверки по флажку, к примеру как в middlewares/randomId.js
, а redux-thunk
делает проверку по типу. Если к нему пришла функция, то он ее вызывает, примерно вот таким образом:action(next, store.getState())
. (action
это наша функция). А уже этот этот action
(эту функцию) мы описали самостоятельно в Action Creator
, в AC/articles.js
, а конкретно в loadAllArticlesAlt()
, а redux-thunk
ее вызывает, передав как аргумент состояние store
и возможность dispatch
ваш action
дальше. И мы этим пользуемся, мы берем и делаем dispatch
над LOAD_ALL_ARTICLES + START
в AC/articles.js
:
export function loadAllArticlesAlt() { return (dispatch, state) => { dispatch({ type: LOAD_ALL_ARTICLES + START })
Далее делаем асинхронные обращения к API, ждем чего-то, далее делаем еще один dispatch
уже LOAD_ALL_ARTICLES + SUCCESS
или FAIL
. Пишем мы эту логику прямо в Action creator
. Поскольку мы здесь имеем доступ к состоянию store
, а также возможность сделать dispatch
нескольких actions
, возможно они будут асинхронные и.т.п. – это то, что чаще всего нам нужно в middlewares
.
setTimeout(() => { $.get('/api/article') .done(response => dispatch({ type: LOAD_ALL_ARTICLES + SUCCESS, response })) .fail(error => dispatch({ type: LOAD_ALL_ARTICLES + FAIL, error })) }, 1000)
Таким образом redux-thunk
позволяет нам не писать лишние middlewares
. Если нам нужно реализовать какую-то сложную логику, которую вы не будете переиспользовать (т.е. она будет использоваться в одном месте), вы можете просто обернуть это в функцию добавив ее в Action Creator
и далее использоватьredux-thunk
Если нужно выполнить отложенный вызов, то вы тоже делаете это в middleware
. В Action creator
такой опции нет, это просто чистая функция, которая возвращает объект. Также мы не можем сделать return
через setTimeout
. Поэтому Вам придется сделать что-то наподобие такого в вашем Action Creator
AC/articles.js
:
export function defferedDelete(id) { return (dispatch) => setTimeout(() => dispatch({type: DELETE_ARTICLE, payload: { id }}), 1000) }
Почему redux-thunk
все же не так хорош? Очень многие разработчики забывают о middlewares
совсем. Наш совет состоит в том, чтобы помнить, что обращения к серверу делается в middlewares
. Не стоит писать всю логику в action creators
с использованием redux-thunk
. Ну или нужно отдавать себе отчет, зачем вы это делаете. Нужно помнить, что actions
должны всегда быть чистыми функциями, поэтому не стоит писать в них например генерацию случайного id
. Подобные решения ставят крест на дальнейшем переводе приложения в разряд тех где присутствует серверный рендеринг. В чем вся прелесть middlewares
– в том что это централизованное место для всех Side Effects
. Т.е. когда вы захотите перенести эту бизнес-логику на сервер или React-native
, вы теоретически сможете использовать многое из уже существующей логики, тогда как изменениям может подвергнуться только сама middleware
.
Домашнее задание – сделать загрузку комментариев, чтобы по клику на show comments комментарии у Вас загружались с сервера. Таким образом, загрузка комментариев будет выполняться с такого адресу (к примеру) , где в конце находится id
статьи:
http://localhost:8080/api/comment?article=56c782f18990ecf954f6e027
Код урока вы можете скачать здесь.
We are looking forward to meeting you on our website soshace.com