<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 20:30:53 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="_CEo0UjnsvjG8xALFJH4GNktxIa_WdWPgM8FkcYvvp4T8OPmupkfmHCwNJMYnghvGSTpLLduKy09L5_FlChZ8A";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Мидлвары | React: Redux Toolkit</title>
<meta name="description" content="Мидлвары / React: Redux Toolkit: Учимся подключать и использовать мидлвары">
<link rel="canonical" href="https://ru.hexlet.io/courses/js-redux-toolkit/lessons/middlewares/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Мидлвары">
<meta property="og:title" content="React: Redux Toolkit">
<meta property="og:description" content="Мидлвары / React: Redux Toolkit: Учимся подключать и использовать мидлвары">
<meta property="og:url" content="https://ru.hexlet.io/courses/js-redux-toolkit/lessons/middlewares/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="2clQLlOh3t0nutX7C1LOSQGqmzVCVM_0u0vMFziqCJA2GJsZod9zvZH58WMHXT4-waO2n0pjMVYGq1ZDaq3v_g" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNywicHVyIjoiYmxvYl9pZCJ9fQ==--2d5cbbf5c3b4a73ae4b2c50632305d78f5872e4d/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0OSwicHVyIjoiYmxvYl9pZCJ9fQ==--a6531362dd1f3afb65f5b269e1a23113df7171b1/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Devices-amico.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0MywicHVyIjoiYmxvYl9pZCJ9fQ==--e2c6c0775e2308e42fbc5dc592ba2db0470632ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDc3MywicHVyIjoiYmxvYl9pZCJ9fQ==--5c7e03f5c0b090edfd085c95a269f9996b8bb090/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Source%20code-amico.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T20:30:53.105Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"Hd-SfxbZW3EfsQPCdUg1afYHHkb-N2_zJ1o0IfoS0m_yDllI5Kf2EanyJ1p5R8UeNg4z7PYAkVGauq51qBU1AQ","topics":[{"id":95184,"title":"И еще на решение учителя ругается Линтер\n\n`error Assignment to property of function parameter 'action' no-param-reassign`","plain_title":"И еще на решение учителя ругается Линтер error Assignment to property of function parameter 'action' no-param-reassign ","creator":{"public_name":"Денис","id":682763,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":185874,"body":"**Денис**, спасибо! Поправил.","topic_id":95184}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":93197,"title":"Как бы хотелось, чтобы было больше заданий по этой теме, хотелось бы получше отработать))\n\nА то как будто тема сложная, а упражнение решается в лет. \n\nСпасибо, что в теме много примеров и есть пример структуры приложения с мидлварами - выстраивается четкая картинка, как это всё должно быть устроено\n","plain_title":"Как бы хотелось, чтобы было больше заданий по этой теме, хотелось бы получше отработать)) А то как будто тема сложная, а упражнение решается в лет. Спасибо, что в теме много примеров и есть пример структуры приложения с мидлварами - выстраивается четкая картинка, как это всё должно быть устроено ","creator":{"public_name":"Екатерина Пашина","id":413152,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":183229,"body":"**Екатерина Пашина**, \n\nСпасибо, за обратную связь, создала тикет с пожеланием добавить еще практику про мидлвары. ","topic_id":93197}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":93183,"title":"Вообще, увидев заветные зеленые галочки в тестах, хочется сразу бежать дальше и забыть про этот урок как страшный сон, но разобраться судя по всему надо.\n\n1. \n> const result = next(action);\nВызов этой функции в теле мидлвары с действием в качестве аргумента может прокидывать действие дальше по цепочке мидлвар.\nНо если next() вызван в последней мидлваре в цепочке (цепочка может состоять и из одной мидлвары), то она передает действие в редьюсер (диспатчинг) вызывая обновление стейта.\n\nПри прокидывании действия дальше по цепочке (в конце концов до редьюсера) обновится стейт и как следствие произойдет рендеринг приложения, так? Для чего нам тогда из миддлвары возвращать результат вызова функции next() и чем он собсна является, этот результат? \nВ нашей задаче, как и в теории, добавляется что-то в текст таски. Но вообще не понятно, как это используется, состояние должно быть изменено на этапе const result = next(action), потому как константе мы присваиваем результат вызова функции next, который в свою очередь прокидывает действие дальше и обновляет состояние. После этого мы меняем payload, но не прокидываем его дальше, а возвращаем результат вызова со старым payload. Какая-то жиденькая история ><\n\n2. \n> В этом случае в хранилище передается результат функции compose(), при этом функция compose() принимает на вход мидлвары.\n\nВ какой последовательности будут выполняться миддлвары? В порядке добавления? Или на практике это не важно\n\n3. \n\n> Теперь мы подобрались к главному. Для Redux написано специальное браузерное расширение Redux DevTools. Установите его в свой браузер. Это ваш главный помощник в отладке на протяжении всего курса.\n\nВот тут вообще самая печалька. Не будет он главным помощником, если вы не расскажите как с ним работать. Я конечно возможно заблуждаюсь и у других студентов это работает по другому, но для меня все скорее всего все будет так же как с React DevTools. Ой ребят, смотрите какая клевая штука, ваще пушка, ну давайте, разберётесь. Ни разу я его так и не применил, как бы не пытался. Разобраться в доке, которая написана для практикующих разработчиков, практически нереально на данном этапе. Мне нужно отложить курс и посвятить месяц изучению функционала, самостоятельно выискивая задачи, где я могу увидеть простой пример использования. Хотя одного урока с простыми примерами и даже без практики хватило бы. Отправляется в ящик установленных \"для дела\" расширений.\n\n4. \n> Ниже код подключения этого расширения к хранилищу:\n\nА для чего его подключать к хранилищу? Это же расширение для браузера\n\n5. \n\n> В общем случае функция createStore принимает три параметра:\nРедьюсер\nНачальное состояние\nМидлвары\n\n> `const store = createStore(\n reducer,\n /* preloadedState, */\n reduxDevtools && reduxDevtools(),\n );`\n\nПолучается, что reduxDevtools && reduxDevtools() в данном случае мидлвара? \n\n> Обратите внимание, что при работе с расширением функция applyMiddleware не требуется.\n\nНе требуется для reduxDevtools && reduxDevtools() или не требуется для добавляемых в дальшейшем мидлвар?\n\n","plain_title":"Вообще, увидев заветные зеленые галочки в тестах, хочется сразу бежать дальше и забыть про этот урок как страшный сон, но разобраться судя по всему надо. > const result = next(action); Вызов этой функции в теле мидлвары с действием в качестве аргумента может прокидывать действие дальше по цепочке мидлвар. Но если next() вызван в последней мидлваре в цепочке (цепочка может состоять и из одной мидлвары), то она передает действие в редьюсер (диспатчинг) вызывая обновление стейта. При прокидывании действия дальше по цепочке (в конце концов до редьюсера) обновится стейт и как следствие произойдет рендеринг приложения, так? Для чего нам тогда из миддлвары возвращать результат вызова функции next() и чем он собсна является, этот результат? В нашей задаче, как и в теории, добавляется что-то в текст таски. Но вообще не понятно, как это используется, состояние должно быть изменено на этапе const result = next(action), потому как константе мы присваиваем результат вызова функции next, который в свою очередь прокидывает действие дальше и обновляет состояние. После этого мы меняем payload, но не прокидываем его дальше, а возвращаем результат вызова со старым payload. Какая-то жиденькая история >< > В этом случае в хранилище передается результат функции compose(), при этом функция compose() принимает на вход мидлвары. В какой последовательности будут выполняться миддлвары? В порядке добавления? Или на практике это не важно Теперь мы подобрались к главному. Для Redux написано специальное браузерное расширение Redux DevTools. Установите его в свой браузер. Это ваш главный помощник в отладке на протяжении всего курса. Вот тут вообще самая печалька. Не будет он главным помощником, если вы не расскажите как с ним работать. Я конечно возможно заблуждаюсь и у других студентов это работает по другому, но для меня все скорее всего все будет так же как с React DevTools. Ой ребят, смотрите какая клевая штука, ваще пушка, ну давайте, разберётесь. Ни разу я его так и не применил, как бы не пытался. Разобраться в доке, которая написана для практикующих разработчиков, практически нереально на данном этапе. Мне нужно отложить курс и посвятить месяц изучению функционала, самостоятельно выискивая задачи, где я могу увидеть простой пример использования. Хотя одного урока с простыми примерами и даже без практики хватило бы. Отправляется в ящик установленных \"для дела\" расширений. > Ниже код подключения этого расширения к хранилищу: А для чего его подключать к хранилищу? Это же расширение для браузера В общем случае функция createStore принимает три параметра: Редьюсер Начальное состояние Мидлвары const store = createStore( reducer, /* preloadedState, */ reduxDevtools && reduxDevtools(), ); Получается, что reduxDevtools && reduxDevtools() в данном случае мидлвара? Обратите внимание, что при работе с расширением функция applyMiddleware не требуется. Не требуется для reduxDevtools && reduxDevtools() или не требуется для добавляемых в дальшейшем мидлвар? ","creator":{"public_name":"Aleksandr Manujlo","id":536195,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":183240,"body":"**Aleksandr Manujlo**, здравствуйте! Спасибо за развернутый фидбек! Я завел тикет на доработку этого урока, так как материал действительно запутанный. Доработаем, чтобы было все понятно. На несколько вопросов отвечу сразу:\n\n> Для чего нам тогда из миддлвары возвращать результат вызова функции next()\n\nЭто делается, чтобы не потерять результат мидлвары. Функция `next()` - это следующая мидлвара в цепочке мидлвар.\n\nМидлвары выполняются в том же порядке, в котором были добавлены.","topic_id":93183},{"creator":{"public_name":"Aleksandr Manujlo","id":536195,"is_tutor":false},"id":183262,"body":"> Это делается, чтобы не потерять результат мидлвары. Функция next() - это следующая мидлвара в цепочке мидлвар.\n\nА как/где этот результат используется и как он выглядит? Я так понимаю, вопрос с обновлением payload уже после вызова next() лучше отложить до обновления урока. Но разве смысл мидлвары не в том, что бы изменить эти данные и прокинуть их дальше? В конце концов мы проваливаемся до редьюсера, он обновляет состояние и по подписке рендерится приложение. И как будто не нужен этот результат. А выглядит все так, как будто он вообще ундефинде будет) Так как из редьюсера в конечном итоге ничего не возвращается и этот возврат доходит до самой первой мидлвары(\n\nА я могу как-то подписаться на обновления урока, что бы узнать, когда можно будет перечитать?","topic_id":93183},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":183296,"body":"**Aleksandr Manujlo**, я вам напишу сюда, когда текст обновиться.\n\n","topic_id":93183}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":96684,"title":"Добрый день! \nПожалуйста, прокомментируйте вот эту формулировку. \n\n> Если просто вызывать next() без возврата результата, то в нашем примере на самом деле ничего не сломается. Логирование по-прежнему будет выводить все данные и будет добавляться имя пользователя. Но такой возврат нужен, чтобы мидлвара могла видеть результат следующей мидлвары. Например, это позволяет строить цепочки мидлвар, использующие промисы, когда одна мидлвара должна дождаться завершения промиса другой мидлвары. Хорошей практикой считается всегда возвращать результат next() из мидлвары.\n\n1. То есть нужен возврат результата `next(action)`, чтобы мидлвара могла увидеть результат следующей мидлвары? Формулировка немного сбивает с толку. \n2. Если на первый вопрос ответ - да. То как и зачем мидлваре видеть результат следующей мидлвары, если цепочка перешла на следующую мидлвару. Ведь предыдущая мидлвара уже никак не может повлиять на выполнение следующей? ","plain_title":"Добрый день! Пожалуйста, прокомментируйте вот эту формулировку. Если просто вызывать next() без возврата результата, то в нашем примере на самом деле ничего не сломается. Логирование по-прежнему будет выводить все данные и будет добавляться имя пользователя. Но такой возврат нужен, чтобы мидлвара могла видеть результат следующей мидлвары. Например, это позволяет строить цепочки мидлвар, использующие промисы, когда одна мидлвара должна дождаться завершения промиса другой мидлвары. Хорошей практикой считается всегда возвращать результат next() из мидлвары. То есть нужен возврат результата next(action), чтобы мидлвара могла увидеть результат следующей мидлвары? Формулировка немного сбивает с толку. Если на первый вопрос ответ - да. То как и зачем мидлваре видеть результат следующей мидлвары, если цепочка перешла на следующую мидлвару. Ведь предыдущая мидлвара уже никак не может повлиять на выполнение следующей? ","creator":{"public_name":"Konstantin Kolmakov","id":437968,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":187639,"body":"**Konstantin Kolmakov**, здравствуйте. Ей и не нужно влиять на выполнение следующей мидлвары. Если взять пример из теории и поменять мидлвару addFinishText, добавить в нее асинхронный код без возврата промиса, то мидлвара logger не сможет дождаться выполнения мидлвары addFinishText при вызове next. Поэтому мидлваре addFinishText нужно будет возвращать промис, который будет получать мидлвара logger при вызове next.","topic_id":96684}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":94637,"title":"Очень удивился, что учитель в решении пишет так, меняя action напрямую:\n\n`action.payload.task.text = ...`\n\nВ теории рассматривалось изменение **result**, а тут изменения вносятся прямо в **action**. То есть в теории сначала выполнялась **next**, и получался result, а тут сначала вносятся изменения в **action** и только потом выполняется **next**. \n\nИ подход в решении кажется более логичным, чем то, что написано в теории: мы сначала меняем **ОБЪЕКТ** **action** (почему-то мне тяжело признать, что *action* это объект, а *не функция*) и уже потом передаем его дальше. \nВ теории к уроку если мы делаем так: `const result = next(action)`;, разве мы уже на этом этапе не передаем дальше **action** без изменений? Мы же вызываем **next**, когда объявляем константу **result**.\n\nТеория невероятно сложна для понимания, не знаю как другим, но мне не было так тяжело со времен архитектуры фронтенда.","plain_title":"Очень удивился, что учитель в решении пишет так, меняя action напрямую: action.payload.task.text = ... В теории рассматривалось изменение result, а тут изменения вносятся прямо в action. То есть в теории сначала выполнялась next, и получался result, а тут сначала вносятся изменения в action и только потом выполняется next. И подход в решении кажется более логичным, чем то, что написано в теории: мы сначала меняем ОБЪЕКТ action (почему-то мне тяжело признать, что action это объект, а не функция) и уже потом передаем его дальше. В теории к уроку если мы делаем так: const result = next(action);, разве мы уже на этом этапе не передаем дальше action без изменений? Мы же вызываем next, когда объявляем константу result. Теория невероятно сложна для понимания, не знаю как другим, но мне не было так тяжело со времен архитектуры фронтенда. ","creator":{"public_name":"Андрей Требухов","id":459124,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":185255,"body":"**Саша Шляпик**, вы правы, оба варианта рабочие. Я подумаю как доработать урок, чтобы материал был более понятен. тема действительно сложная. Но самое главное тут понимать - это что мидлвары нужны для выполнения дополнительного кода при диспатче действия.","topic_id":94637},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":185133,"body":"**Саша Шляпик**, здравствуйте. В примере в теории `next()` вызывается, чтобы получить результат действия и вывести его в логах. В действие передаются оригинальные данные, без изменений. В упражнении же сначала меняются изначальные данные, и далее измененные данные передаются в действие.","topic_id":94637},{"creator":{"public_name":"Андрей Требухов","id":459124,"is_tutor":false},"id":185154,"body":"**Ivan Gagarinov**, так это же тоже пример из теории: \n\n```\nconst addFinishText = (store) => (next) => async (action) => {\n const result = next(action);\n if (action.type !== 'TASK_FINISH') {\n return result;\n }\n\n const user = await axios.get(`/users/${result.payload.userId}`);\n result.payload.task.text = [result.payload.task.text, `Задачу завершил ${user.name}`].join('. ');\n return result;\n};\n```\n\nВ этом примере изменения вносятся в результат **result**, а не в изначальные данные **action**. Поэтому у меня и возникает диссонанс. \n\nПочему тогда в теории не сделано так:\n\n```\nconst addFinishText = (store) => (next) => async (action) => {\n if (action.type !== 'TASK_FINISH') {\n return next(action) // возвращаем результат вызова next без обновления данных\n }\n\n const user = await axios.get(`/users/${result.payload.userId}`);\n action.payload.task.text = [action.payload.task.text, `Задачу завершил ${user.name}`].join('. '); // обновляем данные, а не результат\n return next(action); // возвращаем результат вызова next с обновленными данными\n};\n```","topic_id":94637}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":95183,"title":"Добрый день!\nВ дереве слева скрыт файл с редьюсерами, хотя он подключается в src/reducers/index.jsx\n`import reducers from './reducers/index.js';`\n\nБаг или фича? \n\nБыло бы здорово видеть его при разборе исходников задания\n","plain_title":"Добрый день! В дереве слева скрыт файл с редьюсерами, хотя он подключается в src/reducers/index.jsx import reducers from './reducers/index.js'; Баг или фича? Было бы здорово видеть его при разборе исходников задания ","creator":{"public_name":"Денис","id":682763,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":185868,"body":"**Денис**, здравствуйте. _src/reducers/index.jsx_ — такого файла не существует. В _src/reducers_ находится файл _index.js_, который подключается в _src/index.jsx_. Никакие файлы не скрыты, скорее всего вы не раскрыли директорию и перепутали файлы.","topic_id":95183},{"creator":{"public_name":"Денис","id":682763,"is_tutor":false},"id":185879,"body":"После ответа стало еще запутаннее, может мы смотрим с вами на другие исходники в задании...\nСсылка на упражнение https://ru.hexlet.io/courses/js-redux-toolkit/lessons/middlewares/exercise_unit\n\nНе могу прикрепить скриншот, чтобы показать, что src/reducers/index.jsx - это соседний файл к middlewares.js \nЕсть какие-то ограничение к прикрепляемым изображениям в данном редакторе/чате?\n","topic_id":95183},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":185908,"body":"**Денис**, мы смотрим одну и ту же практику. Вы не раскрыли директорию `reducers`. В ней находится файл index.js, а не index.jsx \n\n\n\nВ обсуждениях нет функции загрузки изображений. Для этого вам нужно воспользоваться любым сервисом хранения изображений и прикрепить ссылку на изображение в сообщении.","topic_id":95183},{"creator":{"public_name":"Денис","id":682763,"is_tutor":false},"id":185917,"body":"Со скрином стало понятнее. Из-за визуального смещения в несколько пикселей названий папок и файлов одного уровня вложенности не раскрыл директорию reducers( \nИван, спасибо за желание помочь и докопаться до сути!","topic_id":95183}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":96258,"title":"Добрый день. Пара вопросов.\n\nПервый даже не совсем вопрос. Линтер ругается на тесты, точнее на \"неожиданные\" переносы строк:\n```\n/usr/src/app/__tests__/test.jsx\n 18:7 error Expected no linebreak before this expression implicit-arrow-linebreak\n```\n\nИ второй уже по теории, конкретно по разделу про [next()](https://ru.hexlet.io/courses/js-redux-toolkit/lessons/middlewares/theory_unit#funktsiya-next). В мидлваре addFinishText() `action.payload.task.text` изменяется через массив и последующую его склейку:\n```\naction.payload.task.text = [action.payload.task.text, `Задачу завершил ${user.name}`].join('. ');\n```\nВ этом есть какая-то задумка/\"тайная практика\", или просто для примера? Вроде одной шаблонной строкой проще:\n\n```\naction.payload.task.text = `${action.payload.task.text}. Задачу завершил ${user.name}`;\n```\nИли не проще?","plain_title":"Добрый день. Пара вопросов. Первый даже не совсем вопрос. Линтер ругается на тесты, точнее на \"неожиданные\" переносы строк: /usr/src/app/__tests__/test.jsx 18:7 error Expected no linebreak before this expression implicit-arrow-linebreak И второй уже по теории, конкретно по разделу про next() (https://ru.hexlet.io/courses/js-redux-toolkit/lessons/middlewares/theory_unit#funktsiya-next). В мидлваре addFinishText() action.payload.task.text изменяется через массив и последующую его склейку: action.payload.task.text = [action.payload.task.text, `Задачу завершил ${user.name}`].join('. '); В этом есть какая-то задумка/\"тайная практика\", или просто для примера? Вроде одной шаблонной строкой проще: action.payload.task.text = `${action.payload.task.text}. Задачу завершил ${user.name}`; Или не проще? ","creator":{"public_name":"Анатолий Катрулин","id":474410,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":187036,"body":"**Анатолий Катрулин**, здравствуйте. Ошибку линтера поправил, спасибо!\n\nПо вопросу из примера, разницы никакой нет. В обоих случаях в свойство объекта присваивается строка.","topic_id":96258}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":96439,"title":"Здравствуйте! После прочтения теории появилось несколько вопросов.\n\n1. В теории урока говорится:\n\n> Для подключения мидлвары, достаточно вызвать applyMiddleware(), передав в него мидлвару, а результат передать вторым параметром в createStore():\n\nОднако, в уроке Redux говорится, что вторым параметром в createStore() передается начальное состояние.\nRedux как-то сам понимает, что именно передается в createStore или дело в чем-то другом?\n\n2. В примерах кода в уроке показано несколько вариантов получения и возврата next(action):\n```js\nconst result = next(action);\nconsole.log('next state', store.getState());\nconsole.groupEnd();\nreturn result;\n```\nи просто возврат в конце мидлвары:\n```js\nreturn next(action);\n```\nЕсть ли какая-то принципиальная разница в этих вариантах? Почему в первом варианте result получен в середине мидлвары? Выглядит немного странно.\n\n","plain_title":"Здравствуйте! После прочтения теории появилось несколько вопросов. В теории урока говорится: Для подключения мидлвары, достаточно вызвать applyMiddleware(), передав в него мидлвару, а результат передать вторым параметром в createStore(): Однако, в уроке Redux говорится, что вторым параметром в createStore() передается начальное состояние. Redux как-то сам понимает, что именно передается в createStore или дело в чем-то другом? В примерах кода в уроке показано несколько вариантов получения и возврата next(action): js const result = next(action); console.log('next state', store.getState()); console.groupEnd(); return result; и просто возврат в конце мидлвары: js return next(action); Есть ли какая-то принципиальная разница в этих вариантах? Почему в первом варианте result получен в середине мидлвары? Выглядит немного странно. ","creator":{"public_name":"Nikita Kuznetsov","id":441300,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":187280,"body":"**Nikita Kuznetsov**, здравствуйте.\n\n1. Да, функция умеет принимать разные параметры и в зависимости от них менять поведение.\n2. Здесь нет никакой разницы. В первом случае результат сохраняется в переменную и затем возвращается. Во втором случае происходит возврат сразу. Это обычная практика, когда внутри функции нужно выполнить какую-то дополнительную логику и только затем вернуть результат.","topic_id":96439}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":98058,"title":"На сайте есть возможность скачивать директорию с файлами из контейнера с задачей? \nУдобнее делать в личной IDE а не в WEB, а копировать долго","plain_title":"На сайте есть возможность скачивать директорию с файлами из контейнера с задачей? Удобнее делать в личной IDE а не в WEB, а копировать долго ","creator":{"public_name":"Vladimir","id":592120,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":189168,"body":"**Vladimir**, здравствуйте. Нет, такого фукнционала в наших упражнениях нет.","topic_id":98058}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}},{"id":98219,"title":"Лучше бы по эффектору курс был. Редакс какая-то дичь. Ужаснейшая сложночитаемая структура от начала до конца. Такое чувство как будто его создавал Кадзима. А Кадзима как известно гений.","plain_title":"Лучше бы по эффектору курс был. Редакс какая-то дичь. Ужаснейшая сложночитаемая структура от начала до конца. Такое чувство как будто его создавал Кадзима. А Кадзима как известно гений. ","creator":{"public_name":"Mikhail","id":472919,"is_tutor":false},"comments":[{"creator":{"public_name":"Elena Gromova","id":548102,"is_tutor":true},"id":189376,"body":"**Mikhail**, эффектор или какой-нибудь MobX сами по себе проще в понимании, с ними получится разобраться и без курса. К тому же Redux всё еще очень популярен и вероятнее встретится в каких-то проектах, поэтому даем именно его. Над упрощением курса периодически работаем, но сложность обусловлена еще и самим Redux-ом.\n\nА Кодзима правда гений.","topic_id":98219}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Мидлвары","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":1652,"slug":"js_redux_tookit_middlewares_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"## src/middlewares.js\n\nРеализуйте мидлвару `addDate()`, которая будет добавлять к названию задачи текущую дату. Например, при вводе имени задачи `Новая задача` должна сформироваться задача с именем `Задача на 14.02.2022: Новая задача`.\n\n### Подсказки\n\n* Для формирования даты подойдет метод [toLocaleDateString()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString) и локаль 'ru-RU'\n* Изменение имени должно происходить только при добавлении таска\n* Структура `payload` при добавлении задачи выглядит так: `{ task: { text: 'task name', id: '1', state: 'active' } }`\n","prepared_readme":"## src/middlewares.js\n\nРеализуйте мидлвару `addDate()`, которая будет добавлять к названию задачи текущую дату. Например, при вводе имени задачи `Новая задача` должна сформироваться задача с именем `Задача на 14.02.2022: Новая задача`.\n\n### Подсказки\n\n* Для формирования даты подойдет метод [toLocaleDateString()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString) и локаль 'ru-RU'\n* Изменение имени должно происходить только при добавлении таска\n* Структура `payload` при добавлении задачи выглядит так: `{ task: { text: 'task name', id: '1', state: 'active' } }`\n","has_solution":true,"entity_name":"Мидлвары"},"units":[{"id":9827,"name":"theory","url":"/courses/js-redux-toolkit/lessons/middlewares/theory_unit"},{"id":9828,"name":"quiz","url":"/courses/js-redux-toolkit/lessons/middlewares/quiz_unit"},{"id":9850,"name":"exercise","url":"/courses/js-redux-toolkit/lessons/middlewares/exercise_unit"}],"links":[{"id":422443,"name":"Официальная документация Middleware","url":"https://redux.js.org/understanding/history-and-design/middleware"}],"ordered_units":[{"id":9827,"name":"theory","url":"/courses/js-redux-toolkit/lessons/middlewares/theory_unit"},{"id":9828,"name":"quiz","url":"/courses/js-redux-toolkit/lessons/middlewares/quiz_unit"},{"id":9850,"name":"exercise","url":"/courses/js-redux-toolkit/lessons/middlewares/exercise_unit"}],"id":4283,"slug":"middlewares","state":"approved","name":"Мидлвары","course_order":160,"goal":"Учимся подключать и использовать мидлвары","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"**Мидлвары** (_middlewares_) — это функции, которые последовательно вызываются во время обновления данных в хранилище. Они относятся к продвинутым техникам использования Redux. В этом уроке мы разберем, как подключать и использовать мидлвары.\n\n## Для чего нужны мидлвары\n\nДля начала разберемся, какие задачи можно решить с помощью мидлвар.\n\nПредставим, что мы хотим добавить какую-то логику при выполнении любого действия в redux. Самый простой пример — это логирование. Для этого воспользуемся тремя методами:\n\n* `console.info()`\n* `console.groupEnd()`\n* `console.log()`\n\nДля решения такой задачи, мы могли бы создать функцию-обертку для диспатча действия:\n\n```javascript\nconst logger = (store, action) => {\n console.group(action.type)\n console.info('Action: ', action)\n store.dispatch(action)\n console.log('Next state', store.getState()) // Выводим новое состояние в консоль\n console.groupEnd()\n}\n\n// Используем вместо обычного store.dispatch()\nlogger(store, increment({ type: 'INCREMENT' }))\n```\n\nЭто рабочий код, но в нем есть значительные недостатки. Представим, что мы хотим добавить еще какую-то логику — например, дополнительное логирование или запросы на сервер. В таком случае нам пришлось бы расширять эту функцию. Такой подход не очень удобен, потому что со временем код функции становится слишком большим, и вся логика смешивается.\n\nПоэтому для таких задач используются мидлвары — это отдельные изолированные функции, которые встраиваются в работу и вызываются по цепочке. Весь процесс похож на конвейер, по которому движется состояние: каждый обработчик-мидлвара выполняет какую-то свою работу и изменяет состояние при необходимости. \n\nПри этом в работе мидлвар есть некоторые особенности, которые отличают работу мидлвар от классического конвейера. О них мы поговорим позже.\n\n## Как работают мидлвары\n\nОбщий принцип такой:\n\n* Сначала мидлвары встраиваются в хранилище при его создании\n* Затем начинается отправка действий (диспатчинга)\n* В этот момент данные проходят через мидлвары и затем попадают в редьюсер\n\nСхематично этот принцип можно показать так:\n\n\n\nБлагодаря такой организации программисты могут расширять библиотеки новой функциональностью, не переписывая исходный код под конкретную задачу.\n\nМидлвары используются в таких задачах:\n\n* Логирование\n* Оповещение об ошибках\n* Работа с асинхронным API\n* Маршрутизация\n\n## Как устроены мидлвары\n\nРассмотрим типичную структуру мидлвары. Любая мидлвара в Redux состоит из нескольких вложенных функций:\n\n```javascript\nconst logger = (store) => {\n return (next) => {\n return (action) => {\n // Код мидлвары\n }\n }\n}\n```\n\nРазберем каждую функцию:\n\n1. **Внешняя функция**\n\n Эта функция нужна для подключения мидлвары к хранилищу. Она передается в Redux-метод `applyMiddleware()`. Функция получает на вход объект `store`, который содержит методы `dispatch()` и `getState()` для работы с Redux внутри мидлвары.\n\n2. **Первая вложенная функция**\n\n Ее аргументом будет функция `next()`. Вызов этой функции в теле мидлвары с действием в качестве аргумента может прокидывать действие дальше по цепочке мидлвар. Если функция `next()` вызвана в последней мидлваре цепочки, то она передает действие в редьюсер и вызывает обновление состояния. Чуть позже мы более подробно это разберем.\n\n3. **Вторая вложенная функция**\n\n В качестве аргумента эта функция принимает действие `action` при его отправке. Мидлвара перехватит любое действие в приложении, отправленное в редьюсер.\n\nТакая вложенность функций может смутить, но так устроены мидлвары и это остается только принять. Рекомендуется использовать короткую форму объявления функций:\n\n```javascript\nconst logger = store => next => (action) => {\n // Код мидлвары\n}\n```\n\nТакой код более наглядно показывает, что хоть мидлвара и состоит из трех вложенных функций, но нам не нужно думать об описании всех этих функций. По факту мы описываем одну функцию, которой доступно три параметра: \n\n* `store`\n* `next`\n* `action`\n\n## Подключение мидлвары\n\nДля подключения мидлвары, достаточно вызвать `applyMiddleware()`, передав в него мидлвару, а результат передать вторым параметром в `createStore()`:\n\n```javascript\nimport { createStore, applyMiddleware } from 'redux'\n\nconst logger = store => next => (action) => {\n // Код мидлвары\n}\n\nconst store = createStore(\n reducer,\n applyMiddleware(logger),\n)\n```\n\nЕсли нужно добавить несколько мидлвар, то нужно использовать дополнительный метод `compose()`:\n\n```javascript\nimport { createStore, applyMiddleware, compose } from 'redux'\n\nconst logger = store => next => (action) => {\n // Код мидлвары\n}\n\nconst updater = store => next => (action) => {\n // Код мидлвары\n}\n\nconst store = createStore(\n reducer,\n compose(\n applyMiddleware(logger),\n applyMiddleware(updater),\n ),\n)\n```\n\nПри диспатче экшена мидлвары будут выполняться в том порядке, в котором были добавлены. Например, выше в примере, мидлвары будут выполняться в следующем порядке `logger -> updater -> reducer`.\n\n## Как использовать мидлвары\n\nНачнем с применения одной мидлвары. Для примера возьмем мидлвару, которая отвечает за логирование. Здесь понадобится три метода:\n\n* `console.info()`\n* `console.groupEnd()`\n* `console.log()`\n\nРассмотрим сам код:\n\n```javascript\n// Мидлвара со вложенными стрелочными функциями\nconst logger = store => next => (action) => {\n console.group(action.type)\n console.info('dispatching', action)\n const result = next(action)\n console.log('next state', store.getState()) // Выводим новое состояние в консоль\n console.groupEnd()\n return result\n}\n```\n\nПодключение мидлвары:\n\n```javascript\nimport { createStore, applyMiddleware, compose } from 'redux'\n\nimport { logger } from './middlewares.js'\n\nconst store = createStore(\n reducer,\n applyMiddleware(logger),\n)\n```\n\nТеперь при каждом диспатче действия будет вызываться мидлвара с логированием:\n\n```javascript\nstore.dispatch({ type: 'INCREMENT' })\n/*\nINCREMENT\ndispatching Object { type: \"INCREMENT\", payload: {…} }\nnext state 1\n*/\n```\n\n## Мидлвары с асинхронными вызовами\n\nМидлвара может содержать асинхронную логику, например:\n\n```javascript\nconst fetchNewData = store => next => (action) => {\n if (action.type === 'UPDATE') {\n axios.get('/data').then((data) => {\n store.dispatch({ type: 'dataLoaded', payload: data })\n })\n }\n\n return next(action)\n}\n```\n\nВ примере выше мы добавили мидлвару, которая загружает новые данные, если передано действие `UPDATE`. Мидлвара не блокирует выполнение текущего действия и вызывает `dispatch()`, когда данные будут загружены. Такой диспатч запустит новую цепочку мидлвар и выполнение действия. С диспатчем внутри мидлвар нужно быть аккуратнее, потому что диспатч запускает новую цепочку, из-за чего могут возникнуть рекурсивные вызовы мидлвары.\n\n## Функция `next()`\n\nВы уже могли заметить вызовы функции `next()` в примерах выше. Как и следует из ее названия, эта функция вызывает следующую в цепочке мидлвару. Если текущая мидлвара уже последняя, то вызывается редьюсер.\n\nТут может возникнуть вопрос: «Как может вызываться следующая в цепочке мидлвара, если текущая еще не завершила работу?». Технически, это возможно. Мидлвары мы подключаем заранее, поэтому на момент срабатывания экшена в Redux уже хранятся все мидлвары. Redux может запустить одну мидлвару внутри другой, ведь это обычные функции.\n\nНа самом деле именно вызов `next()` и обеспечивает цепочку вызовов мидлвар. Если хотя бы в одной мидлваре не будет вызова `next()`, то вся цепочка на этой мидлваре оборвется, и конечный редьюс не вызовется.\n\nРазберем работу `next()` на работе нескольких мидлвар в цепочке:\n\n```javascript\nconst getCurrentUser = () => ({ name: 'Ivan' })\n\nconst logger = store => next => (action) => {\n console.group(action.type)\n console.info('dispatching', action)\n const result = next(action)\n console.log('next state', store.getState())\n console.groupEnd()\n return result\n}\n\nconst addFinishText = store => next => (action) => {\n if (action.type !== 'TASK_FINISH') {\n return next(action)\n }\n\n const user = getCurrentUser()\n action.payload.task.text = [action.payload.task.text, `Задачу завершил ${user.name}`].join('. ')\n return next(action)\n}\n\nconst store = createStore(\n reducer,\n compose(\n applyMiddleware(logger),\n applyMiddleware(addFinishText),\n ),\n)\n```\n\nВыше пример из двух мидлвар. Первая делает логирование действий, а вторая — изменяет входящие данные. В ней используется функция `getCurrentUser()`, которая получает текущего пользователя. Для примера нам не важно, как работает эта функция, поэтому она всегда возвращает один и тот же объект представляющего пользователя.\n\nМидлвара `addFinishText()` добавляет в `payload` имя текущего пользователя. Благодаря этому в редьюсер попадет уже измененный текст с именем пользователя.\n\nВо всех примерах мы возвращаем результат из мидлвары:\n\n```javascript\nreturn next(action)\n```\n\nЕсли просто вызывать `next()` без возврата результата, то в нашем примере на самом деле ничего не сломается. Логирование по-прежнему будет выводить все данные и будет добавляться имя пользователя. Но такой возврат нужен, чтобы мидлвара могла видеть результат следующей мидлвары. Например, это позволяет строить цепочки мидлвар, использующие промисы, когда одна мидлвара должна дождаться завершения промиса другой мидлвары. Хорошей практикой считается всегда возвращать результат `next()` из мидлвары.\n\n## Выводы\n\nМы разобрались, для чего нужны мидлвары, как они подключаются и как с ними работать. Так же мы изучили, как подключать несколько мидлвар, как изменять данные перед их попаданием в редьюсер и получить итоговое состояние в мидлварах.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":4934,"name":"theory","url":"/courses/js-redux-toolkit/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":4934,"name":"theory","url":"/courses/js-redux-toolkit/lessons/intro/theory_unit"}],"id":2212,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с курсом и готовим окружение","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"\nReact — это мощный инструмент, но все равно в нем появляются некоторые неудобства при работе с большими приложениями.\n\nОдно из самых раздражающих — подъем состояния наверх через колбеки, которые нужно прокидывать вниз с самого верхнего уровня. Прокидывать приходится не только колбеки, но и любые данные. Получается, что множество промежуточных компонентов выступают в качестве прокси — они пропускают сквозь себя данные, которыми не пользуются.\n\nВторой раздражающий фактор — рендеринг и логика смешаны в одном месте, что быстро раздувает компоненты и усложняет понимание. Сюда же добавляются неконтролируемые побочные эффекты вперемешку с обновлением данных.\n\nДля решения этих проблем появились **менеджеры состояния**, в том числе Redux — официальный менеджер, который поддерживает сама компания Facebook.\n\n## Как работают Redux и Toolkit\n\n**Redux** — это очень простая библиотека, предназначенная исключительно для управления состоянием. Ее разрабатывали под использование в React, но на самом деле от React она не зависит — можно использовать ее с чем угодно.\n\nДля связи Redux с React понадобится **Redux Toolkit**, через который мы произведем всю необходимую интеграцию. Redux Toolkit не просто склеивает Redux и React. Он меняет способ работы с Redux до неузнаваемости, привносит множество полезных абстракций — например, широко используемые мидлвары.\n\nЦель этой библиотеки — сделать хранение, извлечение и использование данных максимально удобными, убрав шаблонный код, характерный для приложений на Redux.\n\nRedux Toolkit вводит много новых понятий, которые тяжело рассматривать в отрыве от общей картины. Поэтому сначала мы без погружения рассмотрим пример, в котором все собрано в одном месте, а уже в следующих уроках разберем по косточкам каждый элемент.\n\n## Зачем нужны классы, функции, хуки и Redux Toolkit\n\nЗачем так усложнять эту тему? Сначала мы изучаем React на классах, потом на функциональных компонентах и хуках, затем в связке с Redux. В итоге мы заменяем Redux на Redux Toolkit. К счастью, это финальная точка в этом путешествии.\n\nReact развивался именно так: сначала одно, потом другое, потом третье, а теперь пришел к Toolkit. Все это нужно знать, потому что под капотом у Toolkit все еще работает Redux. Да и хуки не заменяют классы — они останутся навсегда.\n"},"id":248,"slug":"js-redux-toolkit","challenges_count":5,"name":"React: Redux Toolkit","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите Redux Toolkit. Вы узнаете больше об организации и управлении сложным состоянием в React. В итоге вы научитесь проектировать React-приложения, которые будут просты в поддержке.","kind":"advanced","updated_at":"2026-01-20T11:37:21.979Z","language":"javascript","duration_cache":48120,"skills":["Строить фронтенд-приложения, которые легко поддерживать","Организовывать данные и доступ к ним внутри хранилища","Автоматизировать работу с HTTP-запросами"],"keywords":[],"lessons_count":11,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTY3OCwicHVyIjoiYmxvYl9pZCJ9fQ==--efe2b18fa5374934980a581ecd8ecf7b3f1d03ed/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[{"stack":{"id":12,"slug":"frontend","title":"Фронтенд-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":20,"duration_in_months":10},"id":17,"slug":"frontend","title":"Фронтенд-разработчик","subtitle":"Изучите HTML, CSS, JavaScript и React","subtitle_for_lists":"Изучите HTML, CSS, JavaScript и React","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"frontend","price_text":"от 6 792 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNywicHVyIjoiYmxvYl9pZCJ9fQ==--2d5cbbf5c3b4a73ae4b2c50632305d78f5872e4d/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"},{"stack":{"id":23,"slug":"js-react-development","title":"React","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":350,"duration_in_months":2},"id":34,"slug":"js-react-developer","title":"React","subtitle":"Навык разрабатывать быстрые и удобные интерфейсы, открывающий доступ к интересным вакансиям в крупных компаниях","subtitle_for_lists":"Освоите React и создание быстрых интерфейсов","locale":"ru","current":true,"duration_in_months_text":"2 месяца","stack_slug":"js-react-development","price_text":"от 3 900 ₽","duration_text":"2 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0OSwicHVyIjoiYmxvYl9pZCJ9fQ==--a6531362dd1f3afb65f5b269e1a23113df7171b1/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Devices-amico.png"},{"stack":{"id":43,"slug":"fullstack-javascript","title":"Fullstack-разработчик на Node.js","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":140,"duration_in_months":12},"id":74,"slug":"fullstack-javascript","title":"Fullstack-разработчик на Node.js","subtitle":"Освоите JavaScript, Node.js, Fastify и React для фронтенда и бэкенда.","subtitle_for_lists":"Освоите JavaScript, Node.js, Fastify и React для фронтенда и бэкенда.","locale":"ru","current":true,"duration_in_months_text":"12 месяцев","stack_slug":"fullstack-javascript","price_text":"от 7 934 ₽","duration_text":"12 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0MywicHVyIjoiYmxvYl9pZCJ9fQ==--e2c6c0775e2308e42fbc5dc592ba2db0470632ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png"},{"stack":{"id":421,"slug":"js-redux-toolkit","title":"Redux Toolkit","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"not_finished","order":null,"duration_in_months":1},"id":584,"slug":"js-redux-toolkit","title":"Redux Toolkit","subtitle":"Изучите Redux Toolkit, слайсы, нормализацию данных, асинхронные запросы и RTK Query","subtitle_for_lists":"Изучите Redux Toolkit, слайсы, нормализацию данных, асинхронные запросы и RTK Query","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"js-redux-toolkit","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDc3MywicHVyIjoiYmxvYl9pZCJ9fQ==--5c7e03f5c0b090edfd085c95a269f9996b8bb090/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Source%20code-amico.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/js-redux-toolkit/lessons/middlewares/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">React: Redux Toolkit</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Мидлвары</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Мидлвары","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"React: Redux Toolkit"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p><strong>Мидлвары</strong> (<em>middlewares</em>) — это функции, которые последовательно вызываются во время обновления данных в хранилище. Они относятся к продвинутым техникам использования Redux. В этом уроке мы разберем, как подключать и использовать мидлвары.</p>
<h2 id="heading-2-1">Для чего нужны мидлвары</h2>
<p>Для начала разберемся, какие задачи можно решить с помощью мидлвар.</p>
<p>Представим, что мы хотим добавить какую-то логику при выполнении любого действия в redux. Самый простой пример — это логирование. Для этого воспользуемся тремя методами:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">console.info()</code></li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">console.groupEnd()</code></li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">console.log()</code></li>
</ul>
<p>Для решения такой задачи, мы могли бы создать функцию-обертку для диспатча действия:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const logger = (store, action) => {
console.group(action.type)
console.info('Action: ', action)
store.dispatch(action)
console.log('Next state', store.getState()) // Выводим новое состояние в консоль
console.groupEnd()
}
// Используем вместо обычного store.dispatch()
logger(store, increment({ type: 'INCREMENT' }))</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Это рабочий код, но в нем есть значительные недостатки. Представим, что мы хотим добавить еще какую-то логику — например, дополнительное логирование или запросы на сервер. В таком случае нам пришлось бы расширять эту функцию. Такой подход не очень удобен, потому что со временем код функции становится слишком большим, и вся логика смешивается.</p>
<p>Поэтому для таких задач используются мидлвары — это отдельные изолированные функции, которые встраиваются в работу и вызываются по цепочке. Весь процесс похож на конвейер, по которому движется состояние: каждый обработчик-мидлвара выполняет какую-то свою работу и изменяет состояние при необходимости.</p>
<p>При этом в работе мидлвар есть некоторые особенности, которые отличают работу мидлвар от классического конвейера. О них мы поговорим позже.</p>
<h2 id="heading-2-2">Как работают мидлвары</h2>
<p>Общий принцип такой:</p>
<ul>
<li>Сначала мидлвары встраиваются в хранилище при его создании</li>
<li>Затем начинается отправка действий (диспатчинга)</li>
<li>В этот момент данные проходят через мидлвары и затем попадают в редьюсер</li>
</ul>
<p>Схематично этот принцип можно показать так:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTY4NSwicHVyIjoiYmxvYl9pZCJ9fQ==--b4f3ac6ff8465771d6b1d2b41c336401d9765843/redux-middlewares-flow.png" alt="Работа мидлваров в Redux" loading="lazy"/></p>
<p>Благодаря такой организации программисты могут расширять библиотеки новой функциональностью, не переписывая исходный код под конкретную задачу.</p>
<p>Мидлвары используются в таких задачах:</p>
<ul>
<li>Логирование</li>
<li>Оповещение об ошибках</li>
<li>Работа с асинхронным API</li>
<li>Маршрутизация</li>
</ul>
<h2 id="heading-2-3">Как устроены мидлвары</h2>
<p>Рассмотрим типичную структуру мидлвары. Любая мидлвара в Redux состоит из нескольких вложенных функций:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const logger = (store) => {
return (next) => {
return (action) => {
// Код мидлвары
}
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Разберем каждую функцию:</p>
<ol>
<li>
<p><strong>Внешняя функция</strong></p>
<p>Эта функция нужна для подключения мидлвары к хранилищу. Она передается в Redux-метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">applyMiddleware()</code>. Функция получает на вход объект <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">store</code>, который содержит методы <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">dispatch()</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getState()</code> для работы с Redux внутри мидлвары.</p>
</li>
<li>
<p><strong>Первая вложенная функция</strong></p>
<p>Ее аргументом будет функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code>. Вызов этой функции в теле мидлвары с действием в качестве аргумента может прокидывать действие дальше по цепочке мидлвар. Если функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code> вызвана в последней мидлваре цепочки, то она передает действие в редьюсер и вызывает обновление состояния. Чуть позже мы более подробно это разберем.</p>
</li>
<li>
<p><strong>Вторая вложенная функция</strong></p>
<p>В качестве аргумента эта функция принимает действие <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">action</code> при его отправке. Мидлвара перехватит любое действие в приложении, отправленное в редьюсер.</p>
</li>
</ol>
<p>Такая вложенность функций может смутить, но так устроены мидлвары и это остается только принять. Рекомендуется использовать короткую форму объявления функций:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const logger = store => next => (action) => {
// Код мидлвары
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Такой код более наглядно показывает, что хоть мидлвара и состоит из трех вложенных функций, но нам не нужно думать об описании всех этих функций. По факту мы описываем одну функцию, которой доступно три параметра:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">store</code></li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next</code></li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">action</code></li>
</ul>
<h2 id="heading-2-4">Подключение мидлвары</h2>
<p>Для подключения мидлвары, достаточно вызвать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">applyMiddleware()</code>, передав в него мидлвару, а результат передать вторым параметром в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">createStore()</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { createStore, applyMiddleware } from 'redux'
const logger = store => next => (action) => {
// Код мидлвары
}
const store = createStore(
reducer,
applyMiddleware(logger),
)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если нужно добавить несколько мидлвар, то нужно использовать дополнительный метод <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">compose()</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { createStore, applyMiddleware, compose } from 'redux'
const logger = store => next => (action) => {
// Код мидлвары
}
const updater = store => next => (action) => {
// Код мидлвары
}
const store = createStore(
reducer,
compose(
applyMiddleware(logger),
applyMiddleware(updater),
),
)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>При диспатче экшена мидлвары будут выполняться в том порядке, в котором были добавлены. Например, выше в примере, мидлвары будут выполняться в следующем порядке <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">logger -> updater -> reducer</code>.</p>
<h2 id="heading-2-5">Как использовать мидлвары</h2>
<p>Начнем с применения одной мидлвары. Для примера возьмем мидлвару, которая отвечает за логирование. Здесь понадобится три метода:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">console.info()</code></li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">console.groupEnd()</code></li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">console.log()</code></li>
</ul>
<p>Рассмотрим сам код:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// Мидлвара со вложенными стрелочными функциями
const logger = store => next => (action) => {
console.group(action.type)
console.info('dispatching', action)
const result = next(action)
console.log('next state', store.getState()) // Выводим новое состояние в консоль
console.groupEnd()
return result
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Подключение мидлвары:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { createStore, applyMiddleware, compose } from 'redux'
import { logger } from './middlewares.js'
const store = createStore(
reducer,
applyMiddleware(logger),
)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Теперь при каждом диспатче действия будет вызываться мидлвара с логированием:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">store.dispatch({ type: 'INCREMENT' })
/*
INCREMENT
dispatching Object { type: "INCREMENT", payload: {…} }
next state 1
*/</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-6">Мидлвары с асинхронными вызовами</h2>
<p>Мидлвара может содержать асинхронную логику, например:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const fetchNewData = store => next => (action) => {
if (action.type === 'UPDATE') {
axios.get('/data').then((data) => {
store.dispatch({ type: 'dataLoaded', payload: data })
})
}
return next(action)
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В примере выше мы добавили мидлвару, которая загружает новые данные, если передано действие <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UPDATE</code>. Мидлвара не блокирует выполнение текущего действия и вызывает <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">dispatch()</code>, когда данные будут загружены. Такой диспатч запустит новую цепочку мидлвар и выполнение действия. С диспатчем внутри мидлвар нужно быть аккуратнее, потому что диспатч запускает новую цепочку, из-за чего могут возникнуть рекурсивные вызовы мидлвары.</p>
<h2 id="heading-2-7">Функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code></h2>
<p>Вы уже могли заметить вызовы функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code> в примерах выше. Как и следует из ее названия, эта функция вызывает следующую в цепочке мидлвару. Если текущая мидлвара уже последняя, то вызывается редьюсер.</p>
<p>Тут может возникнуть вопрос: «Как может вызываться следующая в цепочке мидлвара, если текущая еще не завершила работу?». Технически, это возможно. Мидлвары мы подключаем заранее, поэтому на момент срабатывания экшена в Redux уже хранятся все мидлвары. Redux может запустить одну мидлвару внутри другой, ведь это обычные функции.</p>
<p>На самом деле именно вызов <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code> и обеспечивает цепочку вызовов мидлвар. Если хотя бы в одной мидлваре не будет вызова <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code>, то вся цепочка на этой мидлваре оборвется, и конечный редьюс не вызовется.</p>
<p>Разберем работу <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code> на работе нескольких мидлвар в цепочке:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">const getCurrentUser = () => ({ name: 'Ivan' })
const logger = store => next => (action) => {
console.group(action.type)
console.info('dispatching', action)
const result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
const addFinishText = store => next => (action) => {
if (action.type !== 'TASK_FINISH') {
return next(action)
}
const user = getCurrentUser()
action.payload.task.text = [action.payload.task.text, `Задачу завершил ${user.name}`].join('. ')
return next(action)
}
const store = createStore(
reducer,
compose(
applyMiddleware(logger),
applyMiddleware(addFinishText),
),
)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Выше пример из двух мидлвар. Первая делает логирование действий, а вторая — изменяет входящие данные. В ней используется функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getCurrentUser()</code>, которая получает текущего пользователя. Для примера нам не важно, как работает эта функция, поэтому она всегда возвращает один и тот же объект представляющего пользователя.</p>
<p>Мидлвара <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">addFinishText()</code> добавляет в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">payload</code> имя текущего пользователя. Благодаря этому в редьюсер попадет уже измененный текст с именем пользователя.</p>
<p>Во всех примерах мы возвращаем результат из мидлвары:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">return next(action)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если просто вызывать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code> без возврата результата, то в нашем примере на самом деле ничего не сломается. Логирование по-прежнему будет выводить все данные и будет добавляться имя пользователя. Но такой возврат нужен, чтобы мидлвара могла видеть результат следующей мидлвары. Например, это позволяет строить цепочки мидлвар, использующие промисы, когда одна мидлвара должна дождаться завершения промиса другой мидлвары. Хорошей практикой считается всегда возвращать результат <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">next()</code> из мидлвары.</p>
<h2 id="heading-2-8">Выводы</h2>
<p>Мы разобрались, для чего нужны мидлвары, как они подключаются и как с ними работать. Так же мы изучили, как подключать несколько мидлвар, как изменять данные перед их попаданием в редьюсер и получить итоговое состояние в мидлварах.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/frontend?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Фронтенд-разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите HTML, CSS, JavaScript и React</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzcyNywicHVyIjoiYmxvYl9pZCJ9fQ==--2d5cbbf5c3b4a73ae4b2c50632305d78f5872e4d/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png" alt="Фронтенд-разработчик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 6 792 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/js-react-developer?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">2 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">React</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Освоите React и создание быстрых интерфейсов</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0OSwicHVyIjoiYmxvYl9pZCJ9fQ==--a6531362dd1f3afb65f5b269e1a23113df7171b1/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Devices-amico.png" alt="React" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/fullstack-javascript?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">12 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Fullstack-разработчик на Node.js</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Освоите JavaScript, Node.js, Fastify и React для фронтенда и бэкенда.</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0MywicHVyIjoiYmxvYl9pZCJ9fQ==--e2c6c0775e2308e42fbc5dc592ba2db0470632ca/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programmer-rafiki.png" alt="Fullstack-разработчик на Node.js" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 7 934 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/js-redux-toolkit?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">1 месяц</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Redux Toolkit</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Redux Toolkit, слайсы, нормализацию данных, асинхронные запросы и RTK Query</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDc3MywicHVyIjoiYmxvYl9pZCJ9fQ==--5c7e03f5c0b090edfd085c95a269f9996b8bb090/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Source%20code-amico.png" alt="Redux Toolkit" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/js-redux-toolkit/lessons/middlewares/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 11</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/js-redux-toolkit/lessons/middlewares/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>