Redux. Простой как грабли
Мне уже доводилось заглядывать в репозиторий библиотеки redux, но откуда-то появилась мысль углубиться в его реализацию. Своим в некотором роде шокирующим или даже разочаровывающим открытием я хотел бы поделиться с сообществом.
TL;DR: базовая логика redux помещается в 7 строк JS кода.
О redux вкратце (вольный перевод заголовка на гитхабе):
Redux — библиотека управления состоянием для приложений, написанных на JavaScript.
Она помогает писать приложения, которые ведут себя стабильно/предсказуемо, работают на разных окружениях (клиент/сервер/нативный код) и легко тестируемы.
Я склонировал репозиторий redux, открыл в редакторе папку с исходниками (игнорируя docs, examples и прочее) и взялся за ножницы клавишу Delete:
… потому что мог. Ну или потому что поленился писать для них примеры. Но без корнер-кейсов они ещё менее интересны, чем то, что ждёт вас впереди.
А теперь давайте разберём то, что осталось
Пишем redux за 7 строк
Весь базовый функционал redux умещается в малюсенький файлик, ради которого вряд ли кто-нибудь будет создавать github репозиторий 🙂
Так устроен redux. 18 страниц вакансий на HeadHunter с поисковым запросом «redux» — люди, которые надеются, что вы разберетесь в 7 строках кода. Всё остальное — синтаксический сахар.
С этими 7 строками уже можно писать TodoApp. Или что угодно. Но мы быстренько перепишем TodoApp из документации к redux.
Уже на этом этапе я думал бросить микрофон со сцены и уйти, но show must go on.
Давайте посмотрим, как устроен метод.
combineReducers
Это метод, который позволяет вместо того, чтобы создавать один огромный reducer для всего состояния приложения сразу, разбивать его на отдельные модули.
Используется он так:
Дальше использовать этот store можно так же, как предыдущий.
Разница моего примера и описанного в той же документации к TodoApp довольно забавная.
В документации используют модный синтаксис из ES6 (7/8/∞):
и соответственно переименовывают todoReducer в todos и counterReducer в counter. И многие в своём коде делают то же самое. В итоге разницы нет, но для человека, знакомящегося с redux, с первого раза эта штука выглядит магией, потому что ключ части состояния (state.todos) соответствует функции, названной также только по желанию разработчика (function todos()<>).
Если бы нам нужно было написать такой функционал на нашем micro-redux, мы бы сделали так:
Этот код плохо масштабируется. Если у нас 2 «под-состояния», нам нужно дважды написать (state, action), а хорошие программисты так не делают, правда?
В следующем примере от вас ожидается, что вы не испугаетесь метода Object.entries и Деструктуризации параметров функции
Однако реализация метода combineReducers довольно простая (напоминаю, это если убрать валидацию и вывод ошибок) и самую малость отрефакторить на свой вкус:
Мы добавили к нашему детёнышу redux ещё 9 строк и массу удобства.
Перейдём к ещё одной важной фиче, которая кажется слишком сложной, чтобы пройти мимо неё.
applyMiddleware
middleware в разрезе redux — это какая-то штука, которая слушает все dispatch и при определенных условиях делает что-то. Логирует, проигрывает звуки, делает запросы к серверу,… — что-то.
В оригинальном коде middleware передаются как дополнительные параметры в createStore, но если не жалеть лишнюю строчку кода, то использование этого функционала выглядит так:
При этом реализация метода applyMiddleware, когда ты потратишь 10 минут на ковыряние в чужом коде, сводится к очень простой вещи: createStore возвращает объект с полем «dispatch». dispatch, как мы помним (не помним) из первого листинга кода, — это функция, которая всего лишь применяет редюсер к нашему текущему состоянию (newState = reducer(state, action)).
Так вот applyMiddleware не более чем переопределяет метод dispatch, добавляя перед (или после) обновлением состояния какую-то пользовательскую логику.
Возьмём, например, самый популярный middleware от создателей redux — redux-thunk
Его смысл сводится к тому, что можно делать не только
но и передавать в store.dispatch сложные функции
И теперь, когда мы выполним команду
я понимаю, что конструкция выглядит жутковато, но её тоже просто нужно вызвать пару раз с произвольными параметрами и вы осознаете, что всё не так страшно, это просто функция, возвращающая функцию, возвращающую функцию (ладно, согласен, страшно)
Напомню, оригинальный метод createStore выглядел так
То есть он принимал атрибуты (reducer, initialState) и возвращал объект с ключами < dispatch, getState >.
Оказалось, что реализовать метод applyMiddleware проще, чем понять, как он работает.
Мы берём уже реализованный метод createStore и переопределяем его возвращаемое значение:
Вывод
Под капотом redux содержатся очень простые логические операции. Операции на уровне «Если бензин в цилиндре загорается, давление увеличивается». А вот то, сможете ли вы построить на этих понятиях болид Формулы 1 — уже решайте сами.
Redux middleware что это
Вы видели мидлвары в действии в примере асинхронных экшенов. Если вы когда-либо использовали такие серверные библиотеки, как Express и Koa, то, вероятно, вы уже хорошо знакомы с концепцией мидлвар. В этих фреймворках мидлвары — это части кода, которые вы можете поместить между фреймворком, принимающим запрос и фреймворком, генерирующим ответ. Например, мидлвары из Express или Koa могут добавлять CORS-заголовки, логирование, сжатие и т.д. Лучшая особенность мидлваров заключается в том, что их можно соединять в цепочки/последовательности. Вы можете использовать множество независимых сторонних мидлваров в одном проекте.
Redux-мидлвары, в отличие от мидлваров Express или Koa, решают немного другие проблемы, но концептуально схожим способом. Они предоставляют стороннюю точку расширения, между отправкой экшена и моментом, когда этот экшен достигает редьюсера. Люди используют Redux-мидлвары для логирования, сообщения об ошибках, общения с асинхронным API, роутинга и т.д.
Эта статья разделена на углубленное введение, которое поможет вам хорошо разобраться в концепции, и пару практических примеров в самом конце, которые покажут вам всю силу мидлваров. Вам может показаться полезным периодическое переключение между этими частями, так же, как между скукой и вдохновением.
Т.к. мидлвары могут использоваться для различных задач, в том числе и для асинхронных обращений к API, то очень важно, чтобы вы понимали, откуда они пришли. Мы покажем вам ход мыслей, шаг за шагом ведущий к мидлварам, используя логирование и сообщения об ошибках в качестве примера.
Одно из достоинств Redux — он делает изменения состояния приложения предсказуемыми и прозрачными. Каждый раз, когда посылается экшен, новое состояние вычисляется и сохраняется. Состояние не может измениться самостоятельно, оно может меняться только, как последовательность определенных экшенов.
Разве не было бы хорошо, если бы мы записывали каждое действие, которое происходило в приложении, вместе с состоянием, которое было вычислено после этого действия? Когда что-то идет не так, мы можем просмотреть наш лог и понять, какой именно экшен испортил наше состояние.
Как мы подходим к этому с Redux?
Попытка #1: Логируем вручную
Если вы используете react-redux или похожий биндинг, у вас, скорее всего, не будет прямого доступа к экземпляру стора в ваших компонентах. Для следующих нескольких параграфов представьте, что вы передаете состояние явно.
Например, вы вызываете такой код, когда создаете todo-элемент:
Для того чтобы логировать экшен и состояние, вы можете изменить код примерно так:
Это даст желаемый эффект, но вы бы не хотели делать так каждый раз.
Попытка #2: Оборачиваем Dispatch
Вы можете вынести логирование в функцию:
Вы можете использовать ее везде вместо обычного store.dispatch() :
Мы бы могли закончить на этом, но не очень удобно импортировать специальную функцию каждый раз.
Что, если мы просто заменим функцию dispatch в экземпляре стора? Redux стор — это простой объект с парой методов, а мы пишем на JavaScript, следовательно, мы можем применить технику monkeypatch для реализации dispatch :
Это уже ближе к тому, что нам нужно! Не важно, откуда мы посылаем экшен, он гарантированно будет залогирован. Monkeypatching никогда не покажется правильным ходом, но пока мы можем с этим жить.
Проблема: Сообщения об ошибках.
Другое такое изменение, которое приходит мне в голову, это сообщения о JavaScript-ошибках в продакшене. Глобальное событие window.onerror не надежно потому, что оно в некоторых старых браузерах не предоставляет информацию о стеке вызовов, которая важна для понимания того, почему же произошла ошибка.
Разве не было бы полезно, если бы каждый раз, когда ошибка выбрасывалась, как результат отправки какого-либо экшена, мы могли бы отправить ее (ошибку), вместе со стеком вызовов, экшеном, который вызвал ошибку и актуальным состоянием в сервис сообщения об ошибках, такой как Sentry. В таком случае гораздо легче воспроизвести ошибку в разработке.
Однако важно, чтобы мы держали логирование и сообщения об ошибках раздельно. В идеальном случае, мы хотим получить их, как разные модули из разных пакетов. В противном случае, мы не сможем иметь экосистему из такого рода утилит. (Подсказка: мы медленно подходим к тому, что такое мидлвары!)
Если логирование и сообщения об ошибках являются отдельными утилитами, то они могут выглядеть так:
Если эти функции опубликованы, как отдельные модули, то позже мы можем использовать их для изменения нашего стора:
Но это все еще не очень хорошо.
Попытка #4: Прячем Monkeypatching
Мы могли бы предоставить функцию-помощник внутри Redux, которая могла бы применять актуальный monkeypatching, как часть имплементации:
Мы можем использовать такой подход для применения нескольких мидлваров:
Попытка #5: Убираем Monkeypatching
Это важно для возможности объединять мидлвары в цепочки!
Но есть еще другой метод реализации объединения мидлваров в цепочки (chaining). Мидлвар мог бы принимать функцию отправки экшена next() в параметрах вместо того, чтобы читать ее из экземпляра стора.
Это тот момент, когда “we need to go deeper”, так что имеет смысл потратить некоторе время на это. Каскад функций выглядит пугающим. Стрелочные функции из ES6 делают это каррирование чуть более простым для глаз:
Именно так выглядят мидлвары в Redux.
Попытка #6: Простейшее применение мидлваров
Предостережение: отправка во время установки
Дан мидлвар который мы только что написали:
Вот так можно его применить к Redux стору:
Вот и все! Теперь любые экшены, отправленные в экземпляр стора, будут проходить через logger и crashReporter :
Если ваша голова вскипела от прочтения предыдущего раздела, представьте, каково было написать это. Этот раздел предназначен для расслабления меня и вас и поможет запустить ваши шестеренки.
Каждая из функций, приведенных ниже, является валидным Redux-мидлваром. Они не являются в равной степени полезными, но, по крайней мере, они в равной степени забавны.
Mидлвар (Middleware)
Вы видели мидлвары в действии в примере асинхронных экшенов. Если вы когда-либо использовали такие серверные библиотеки, как Express и Koa, то, вероятно, вы уже хорошо знакомы с концепцией мидлвар. В этих фреймворках мидлвары — это части кода, которые вы можете поместить между фреймворком, принимающим запрос и фреймворком, генерирующим ответ. Например, мидлвары из Express или Koa могут добавлять CORS-заголовки, логирование, сжатие и т.д. Лучшая особенность мидлваров заключается в том, что их можно соединять в цепочки/последовательности. Вы можете использовать множество независимых сторонних мидлваров в одном проекте.
Redux-мидлвары, в отличие от мидлваров Express или Koa, решают немного другие проблемы, но концептуально схожим способом. Они предоставляют стороннюю точку расширения, между отправкой экшена и моментом, когда этот экшен достигает редюсера. Люди используют Redux-мидлвары для логирования, сообщения об ошибках, общения с асинхронным API, роутинга и т.д.
Эта статья разделена на углубленное введение, которое поможет вам хорошо разобраться в концепции, и пару практических примеров в самом конце, которые покажут вам всю силу мидлваров. Вам может показаться полезным периодическое переключение между этими частями, так же, как между скукой и вдохновением.
Понимание мидлваров
Т.к. мидлвары могут использоваться для различных задач, в том числе и для асинхронных обращений к API, то очень важно, чтобы вы понимали, откуда они пришли. Мы покажем вам ход мыслей, шаг за шагом ведущий к мидлварам, используя логирование и сообщения об ошибках в качестве примера.
Проблема: логирование
Одно из достоинств Redux — он делает изменения состояния приложения предсказуемыми и прозрачными. Каждый раз, когда посылается экшен, новое состояние вычисляется и сохраняется. Состояние не может измениться самостоятельно, оно может меняться только, как последовательность определенных экшенов.
Разве не было бы хорошо, если бы мы записывали каждое действие, которое происходило в приложении, вместе с состоянием, которое было вычислено после этого действия? Когда что-то идет не так, мы можем просмотреть наш лог и понять, какой именно экшен испортил наше состояние.
Как мы подходим к этому с Redux?
Попытка #1: Логируем вручную
Обратите внимание
Если вы используете react-redux или похожий биндинг, у вас, скорее всего, не будет прямого доступа к экземпляру стора в ваших компонентах. Для следующих нескольких параграфов представьте, что вы передаете состояние явно.
Например, вы вызываете такой код, когда создаете todo-элемент:
Для того чтобы логировать экшен и состояние, вы можете изменить код примерно так:
Это даст желаемый эффект, но вы бы не хотели делать так каждый раз.
Попытка #2: Оборачиваем Dispatch
Вы можете вынести логирование в функцию:
Вы можете использовать ее везде вместо обычного store.dispatch() :
Мы бы могли закончить на этом, но не очень удобно импортировать специальную функцию каждый раз.
Попытка #3: Monkeypatching для Dispatch
Что, если мы просто заменим функцию dispatch в экземпляре стора? Redux стор — это простой объект с парой методов, а мы пишем на JavaScript, следовательно, мы можем применить технику monkeypatch для реализации dispatch :
Это уже ближе к тому, что нам нужно! Не важно, откуда мы посылаем экшен, он гарантированно будет залогирован. Monkeypatching никогда не покажется правильным ходом, но пока мы можем с этим жить.
Проблема: Сообщения об ошибках.
Другое такое изменение, которое приходит мне в голову, это сообщения о JavaScript-ошибках в продакшене. Глобальное событие window.onerror не надежно потому, что оно в некоторых старых браузерах не предоставляет информацию о стеке вызовов, которая важна для понимания того, почему же произошла ошибка.
Разве не было бы полезно, если бы каждый раз, когда ошибка выбрасывалась, как результат отправки какого-либо экшена, мы могли бы отправить ее (ошибку), вместе со стеком вызовов, экшеном, который вызвал ошибку и актуальным состоянием в сервис сообщения об ошибках, такой как Sentry. В таком случае гораздо легче воспроизвести ошибку в разработке.
Однако важно, чтобы мы держали логирование и сообщения об ошибках раздельно. В идеальном случае, мы хотим получить их, как разные модули из разных пакетов. В противном случае, мы не сможем иметь экосистему из такого рода утилит. (Подсказка: мы медленно подходим к тому, что такое мидлвары!)
Если логирование и сообщения об ошибках являются отдельными утилитами, то они могут выглядеть так:
Если эти функции опубликованы, как отдельные модули, то позже мы можем использовать их для изменения нашего стора:
Но это все еще не очень хорошо.
Попытка #4: Прячем Monkeypatching
Мы могли бы предоставить функцию-помощник внутри Redux, которая могла бы применять актуальный monkeypatching, как часть имплементации:
Мы можем использовать такой подход для применения нескольких мидлваров:
Попытка #5: Убираем Monkeypatching
Это важно для возможности объединять мидлвары в цепочки!
Но есть еще другой метод реализации объединения мидлваров в цепочки (chaining). Мидлвар мог бы принимать функцию отправки экшена next() в параметрах вместо того, чтобы читать ее из экземпляра стора.
Это тот момент, когда “we need to go deeper”, так что имеет смысл потратить некоторе время на это. Каскад функций выглядит пугающим. Стрелочные функции из ES6 делают это каррирование чуть более простым для глаз:
Именно так выглядят мидлвары в Redux.
Попытка #6: Простейшее применение мидлваров
Предостережение: отправка во время установки
Финальный подход
Дан мидлвар который мы только что написали:
Вот так можно его применить к Redux стору:
Вот и все! Теперь любые экшены, отправленные в экземпляр стора, будут проходить через logger и crashReporter :
Семь примеров
Если ваша голова вскипела от прочтения предыдущего раздела, представьте, каково было написать это. Этот раздел предназначен для расслабления меня и вас и поможет запустить ваши шестеренки.
Каждая из функций, приведенных ниже, является валидным Redux-мидлваром. Они не являются в равной степени полезными, но, по крайней мере, они в равной степени забавны.
Mидлвар¶
Вы видели мидлвары (Middleware) в действии в примере асинхронных экшенов. Если вы когда-либо использовали такие серверные библиотеки, как Express и Koa, то, вероятно, вы уже хорошо знакомы с концепцией мидлвар. В этих фреймворках мидлвары — это части кода, которые вы можете поместить между фреймворком, принимающим запрос и фреймворком, генерирующим ответ. Например, мидлвары из Express или Koa могут добавлять CORS-заголовки, логирование, сжатие и т. д. Лучшая особенность мидлваров заключается в том, что их можно соединять в цепочки/последовательности. Вы можете использовать множество независимых сторонних мидлваров в одном проекте.
Redux-мидлвары, в отличие от мидлваров Express или Koa, решают немного другие проблемы, но концептуально схожим способом. Они предоставляют стороннюю точку расширения, между отправкой экшена и моментом, когда этот экшен достигает редьюсера. Люди используют Redux-мидлвары для логирования, сообщения об ошибках, общения с асинхронным API, роутинга и т.д.
Эта статья разделена на углубленное введение, которое поможет вам хорошо разобраться в концепции, и пару практических примеров в самом конце, которые покажут вам всю силу мидлваров. Вам может показаться полезным периодическое переключение между этими частями, так же, как между скукой и вдохновением.
Понимание мидлваров¶
Т. к. мидлвары могут использоваться для различных задач, в том числе и для асинхронных обращений к API, то очень важно, чтобы вы понимали, откуда они пришли. Мы покажем вам ход мыслей, шаг за шагом ведущий к мидлварам, используя логирование и сообщения об ошибках в качестве примера.
Проблема: логирование¶
Одно из достоинств Redux — он делает изменения состояния приложения предсказуемыми и прозрачными. Каждый раз, когда посылается экшен, новое состояние вычисляется и сохраняется. Состояние не может измениться самостоятельно, оно может меняться только, как последовательность определенных экшенов.
Разве не было бы хорошо, если бы мы записывали каждое действие, которое происходило в приложении, вместе с состоянием, которое было вычислено после этого действия? Когда что-то идет не так, мы можем просмотреть наш лог и понять, какой именно экшен испортил наше состояние.
Как мы подходим к этому с Redux?
Попытка #1: Логируем вручную¶
Если вы используете react-redux или похожий биндинг, у вас, скорее всего, не будет прямого доступа к экземпляру стора в ваших компонентах. Для следующих нескольких параграфов представьте, что вы передаете состояние явно.
Например, вы вызываете такой код, когда создаете todo-элемент:
Для того чтобы логировать экшен и состояние, вы можете изменить код примерно так:
Это даст желаемый эффект, но вы бы не хотели делать так каждый раз.
Попытка #2: Оборачиваем Dispatch¶
Вы можете вынести логирование в функцию:
Вы можете использовать ее везде вместо обычного store.dispatch() :
Мы бы могли закончить на этом, но не очень удобно импортировать специальную функцию каждый раз.
Попытка #3: Monkeypatching для Dispatch¶
Что, если мы просто заменим функцию dispatch в экземпляре стора? Redux стор — это простой объект с парой методов, а мы пишем на JavaScript, следовательно, мы можем применить технику monkeypatch для реализации dispatch :
Это уже ближе к тому, что нам нужно! Не важно, откуда мы посылаем экшен, он гарантированно будет залогирован. Monkeypatching никогда не покажется правильным ходом, но пока мы можем с этим жить.
Проблема: Сообщения об ошибках.¶
Другое такое изменение, которое приходит мне в голову, это сообщения о JavaScript-ошибках в продакшене. Глобальное событие window.onerror не надежно потому, что оно в некоторых старых браузерах не предоставляет информацию о стеке вызовов, которая важна для понимания того, почему же произошла ошибка.
Разве не было бы полезно, если бы каждый раз, когда ошибка выбрасывалась, как результат отправки какого-либо экшена, мы могли бы отправить ее (ошибку), вместе со стеком вызовов, экшеном, который вызвал ошибку и актуальным состоянием в сервис сообщения об ошибках, такой как Sentry. В таком случае гораздо легче воспроизвести ошибку в разработке.
Однако важно, чтобы мы держали логирование и сообщения об ошибках раздельно. В идеальном случае, мы хотим получить их, как разные модули из разных пакетов. В противном случае, мы не сможем иметь экосистему из такого рода утилит. (Подсказка: мы медленно подходим к тому, что такое мидлвары!)
Если логирование и сообщения об ошибках являются отдельными утилитами, то они могут выглядеть так:
Если эти функции опубликованы, как отдельные модули, то позже мы можем использовать их для изменения нашего стора:
Но это все еще не очень хорошо.
Попытка #4: Прячем Monkeypatching¶
Мы могли бы предоставить функцию-помощник внутри Redux, которая могла бы применять актуальный monkeypatching, как часть имплементации:
Мы можем использовать такой подход для применения нескольких мидлваров:
Попытка #5: Убираем Monkeypatching¶
Это важно для возможности объединять мидлвары в цепочки!
Но есть еще другой метод реализации объединения мидлваров в цепочки (chaining). Мидлвар мог бы принимать функцию отправки экшена next() в параметрах вместо того, чтобы читать ее из экземпляра стора.
Это тот момент, когда “we need to go deeper”, так что имеет смысл потратить некоторе время на это. Каскад функций выглядит пугающим. Стрелочные функции из ES6 делают это каррирование чуть более простым для глаз:
Именно так выглядят мидлвары в Redux.
Попытка #6: Простейшее применение мидлваров¶
Предостережение: отправка во время установки
Финальный подход¶
Дан мидлвар который мы только что написали:
Вот так можно его применить к Redux стору:
Вот и все! Теперь любые экшены, отправленные в экземпляр стора, будут проходить через logger и crashReporter :
Семь примеров¶
Если ваша голова вскипела от прочтения предыдущего раздела, представьте, каково было написать это. Этот раздел предназначен для расслабления меня и вас и поможет запустить ваши шестеренки.
Каждая из функций, приведенных ниже, является валидным Redux-мидлваром. Они не являются в равной степени полезными, но, по крайней мере, они в равной степени забавны.
Middleware
You’ve seen middleware in action in the «Redux Fundamentals» tutorial. If you’ve used server-side libraries like Express and Koa, you were also probably already familiar with the concept of middleware. In these frameworks, middleware is some code you can put between the framework receiving a request, and the framework generating a response. For example, Express or Koa middleware may add CORS headers, logging, compression, and more. The best feature of middleware is that it’s composable in a chain. You can use multiple independent third-party middleware in a single project.
Redux middleware solves different problems than Express or Koa middleware, but in a conceptually similar way. It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.
This article is divided into an in-depth intro to help you grok the concept, and a few practical examples to show the power of middleware at the very end. You may find it helpful to switch back and forth between them, as you flip between feeling bored and inspired.
Understanding Middleware
While middleware can be used for a variety of things, including asynchronous API calls, it’s really important that you understand where it comes from. We’ll guide you through the thought process leading to middleware, by using logging and crash reporting as examples.
Problem: Logging
One of the benefits of Redux is that it makes state changes predictable and transparent. Every time an action is dispatched, the new state is computed and saved. The state cannot change by itself, it can only change as a consequence of a specific action.
Wouldn’t it be nice if we logged every action that happens in the app, together with the state computed after it? When something goes wrong, we can look back at our log, and figure out which action corrupted the state.
How do we approach this with Redux?
Attempt #1: Logging Manually
If you’re using react-redux or similar bindings, you likely won’t have direct access to the store instance in your components. For the next few paragraphs, just assume you pass the store down explicitly.
Say, you call this when creating a todo:
To log the action and state, you can change it to something like this:
This produces the desired effect, but you wouldn’t want to do it every time.
Attempt #2: Wrapping Dispatch
You can extract logging into a function:
You can then use it everywhere instead of store.dispatch() :
We could end this here, but it’s not very convenient to import a special function every time.
Attempt #3: Monkeypatching Dispatch
What if we just replace the dispatch function on the store instance? The Redux store is a plain object with a few methods, and we’re writing JavaScript, so we can just monkeypatch the dispatch implementation:
This is already closer to what we want! No matter where we dispatch an action, it is guaranteed to be logged. Monkeypatching never feels right, but we can live with this for now.
Problem: Crash Reporting
A different useful transformation that comes to my mind is reporting JavaScript errors in production. The global window.onerror event is not reliable because it doesn’t provide stack information in some older browsers, which is crucial to understand why an error is happening.
Wouldn’t it be useful if, any time an error is thrown as a result of dispatching an action, we would send it to a crash reporting service like Sentry with the stack trace, the action that caused the error, and the current state? This way it’s much easier to reproduce the error in development.
However, it is important that we keep logging and crash reporting separate. Ideally we want them to be different modules, potentially in different packages. Otherwise we can’t have an ecosystem of such utilities. (Hint: we’re slowly getting to what middleware is!)
If logging and crash reporting are separate utilities, they might look like this:
If these functions are published as separate modules, we can later use them to patch our store:
Still, this isn’t nice.
Attempt #4: Hiding Monkeypatching
We could provide a helper inside Redux that would apply the actual monkeypatching as an implementation detail:
We could use it to apply multiple middleware like this:
However, it is still monkeypatching. The fact that we hide it inside the library doesn’t alter this fact.
Attempt #5: Removing Monkeypatching
It is essential to chaining middleware!
If applyMiddlewareByMonkeypatching doesn’t assign store.dispatch immediately after processing the first middleware, store.dispatch will keep pointing to the original dispatch function. Then the second middleware will also be bound to the original dispatch function.
But there’s also a different way to enable chaining. The middleware could accept the next() dispatch function as a parameter instead of reading it from the store instance.
It’s a “we need to go deeper” kind of moment, so it might take a while for this to make sense. The function cascade feels intimidating. ES6 arrow functions make this currying easier on eyes:
This is exactly what Redux middleware looks like.
Attempt #6: Naïvely Applying the Middleware
The implementation of applyMiddleware() that ships with Redux is similar, but different in three important aspects:
Because it is cumbersome to apply functions to createStore() before using it, createStore() accepts an optional last argument to specify such functions.
Caveat: Dispatching During Setup
The Final Approach
Given this middleware we just wrote:
Here’s how to apply it to a Redux store:
That’s it! Now any actions dispatched to the store instance will flow through logger and crashReporter :
Seven Examples
If your head boiled from reading the above section, imagine what it was like to write it. This section is meant to be a relaxation for you and me, and will help get your gears turning.
Each function below is a valid Redux middleware. They are not equally useful, but at least they are equally fun.






