<!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:24:27 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="2rfNJWZmgM6WHgHMvSIYFBFbAc9pWXAW35jNU-9Wzxc1ZgYSlBgtriBdJVSxLehj0VIsZWFujrRieFcHvVEoeQ";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>Virtual Dom | JS: React</title>
<meta name="description" content="Virtual Dom / JS: React: Познакомиться с устройством React">
<link rel="canonical" href="https://ru.hexlet.io/courses/js-react/lessons/virtual-dom/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Virtual Dom">
<meta property="og:title" content="JS: React">
<meta property="og:description" content="Virtual Dom / JS: React: Познакомиться с устройством React">
<meta property="og:url" content="https://ru.hexlet.io/courses/js-react/lessons/virtual-dom/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="jDNVFMstkYCWzTcfUjJw0h5fj4ICIJaEwC6ouu-D7NRj4p4jOVM84CCOE4dePYCl3laiKAoXaCZ9zjLuvYQLug" />
<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/eyJfcmFpbHMiOnsiZGF0YSI6NTIyNSwicHVyIjoiYmxvYl9pZCJ9fQ==--3c9f823d2a682639c9e5cb055e85378898a46a22/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.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:24:27.436Z","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":"A3HGbmRTQnu0Mq3wtyz3F1W0Sc5tAkLJCX0VCuKghEvsoA1Zli3vGwJxiWi7Iwdglb1kZGU1vGu0nY9esKdjJQ","topics":[{"id":31544,"title":"А зачем в решении учителя строка\n```\n static defaultProps = {\n isOpen: false,\n };\n\n```\nПредполагается, что параметры могут быть не переданы? \nБез дефолтного пропса тесты проходят.","plain_title":"А зачем в решении учителя строка ``` static defaultProps = { isOpen: false, }; Предполагается, что параметры могут быть не переданы? Без дефолтного пропса тесты проходят. ","creator":{"public_name":"Roman Serikov","id":142522,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikita Mikhaylov","id":186965,"is_tutor":true},"id":68608,"body":"Здравствуйте\n\nЭто не более, чем защита нас же от ошибок. В данном случае `isOpen` всегда передаётся, но вполне возможна ситуация в реальном проекте, что что-то пойдёт не так, поэтому иметь по дефолту ключевое свойство очень важно.\n\nТесты не смотрят на наличие `defaultProps` и решение вполне должно проходить и без него. Если что — вы можете отправить решение на ревью и прикрепить ссылку сюда","topic_id":31544}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":48835,"title":"Здравствуйте! Посмотрите пожалуйста моё ревью. Я правильно понимаю, что мне нужно создавать компоненты, читающие пропсы из Component? Если да, то вот посмотрите пожалуйста ревью. У меня только onClick не работает, хотя передаю туда пропс. Видимо неправильно. \nhttps://ru.hexlet.io/code_reviews/332831","plain_title":"Здравствуйте! Посмотрите пожалуйста моё ревью. Я правильно понимаю, что мне нужно создавать компоненты, читающие пропсы из Component? Если да, то вот например функциональный компонент Footer в виде пропса в него передается содержание onClick={this.toggle} const Footer = (onClick) => { return ( <p class=\"modal-footer\"> <button onClick={onClick} type=\"button\" class=\"modal-close-button btn btn-default\">Cancel</button> </p> ) } А что тогда должно быть описано в onClick? Метод toggle переданный через пропс или свой метод какой-то? Подскажите пожалуйста. https://ru.hexlet.io/code_reviews/332831 ","creator":{"public_name":"Алексей Мерзляков","id":242901,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":104900,"body":"**Алексей Мерзляков**, приветствую.\n\nОбратите внимание на вывод тестов - вы деструктуризируете пропсы, а потом обращаетесь к другому объекту, в результате чего появляется ошибка:\n\n ReferenceError: props is not defined\n\n 39 | \n 40 | const modalClass = cn('modal', {\n > 41 | 'fade' : props.isOpen,\n | ^\n\nНасколько я вижу, шесто решение было рабочим: https://ru.hexlet.io/code_reviews/332831?submission_id=421670\n\nЗначит, вам уже должно быть доступно решение учителя. Можете сравнить своё решение с ним.","topic_id":48835},{"creator":{"public_name":"Алексей Мерзляков","id":242901,"is_tutor":false},"id":104964,"body":"**Сергей Мелодин**, да, я решил в итоге. ","topic_id":48835}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":27405,"title":"В Header на крестик забыл повесить onClick, но тесты прошли все равно","plain_title":"В Header на крестик забыл повесить onClick, но тесты прошли все равно ","creator":{"public_name":"Максим Бодрус","id":132306,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":58802,"body":"А можешь тест накинуть?","topic_id":27405}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":51504,"title":"[enter link description here](https://hkar.ru/13Pii)\n\nЕсли в узле поменялся только родитель(красный круг), а ребенок остался неизменным (зеленый круг), реакт все равно перерисует детей занаво?","plain_title":"screen https://ibb.co/HTFBpV5 Если в узле поменялся только родитель(красный круг), а ребенок остался неизменным (зеленый круг), реакт все равно перерисует детей занаво? ","creator":{"public_name":"Daniyar Zhanakhmetov","id":239134,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Makarov","id":181967,"is_tutor":false},"id":110123,"body":"**Daniyar Zhanakhmetov**, да, если только метод `shouldComponentUpdate` детей не возвращает `false`","topic_id":51504}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":40870,"title":"Решил почти один в один с учителем, это радует))\nТолько вопрос, зачем дефолтный пропс сделан ?","plain_title":"Решил почти один в один с учителем. Только вопрос, зачем дефолтный пропс сделан ? ","creator":{"public_name":"Sorax Goys","id":254859,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikita Mikhaylov","id":186965,"is_tutor":true},"id":89243,"body":"Здравствуйте\n\nЗадел в стиле «а сегодня в завтрашний день не все могут смотреть» :) Если не передать _isOpen_, то могут возникнуть проблемы, так как это ключевая часть класса. Добавив дефолтное состояние мы исключаем неоднозначного поведения и ошибок","topic_id":40870}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":18994,"title":"> В реальности после того, как отработает их рендеринг (вызов функции render для всего дерева компонентов), создается так называемый виртуальный DOM. Это просто js-объект определенной структуры, который отражает состояние экрана. Далее React сравнивает новый virtual dom tree со старым и строит дифф (объект, описывающий разницу между старым и новым состоянием). И только в этот момент начинается отрисовка нового состояния в реальный DOM. Здесь уже должно быть понятно, что Реакт умнее, чем кажется на первый взгляд, и вносит изменения в реальный DOM настолько эффективно, насколько это возможно, ведь он знает КАК его надо изменить.\n\nСколько раз читал про вот эту фичу реакта, так и не могу понять одного — сказано, что реакт вычисляет дифф между старым и новым виртуал-домом и затем заменяет нужные узлы **в реальном доме**. То есть все равно происходит обращение в дом (понятное дело, что без этого никак). \n\nСуть в том, что дом (реальный) отрисовывается не целиком а кусками? Так это ведь тоже самое, что обращаться, скажем, через `id` к конкретному дом-узлу и перерисовывать его детей, как в проекте рсс-ридер например - список каналов и список постов?\n\nУчитывая, что реакт написан на том же чистом жсе - очевидно, что можно реализовать подобный подход самостоятельно. Много сложного кода?","plain_title":"В реальности после того, как отработает их рендеринг (вызов функции render для всего дерева компонентов), создается так называемый виртуальный DOM. Это просто js-объект определенной структуры, который отражает состояние экрана. Далее React сравнивает новый virtual dom tree со старым и строит дифф (объект, описывающий разницу между старым и новым состоянием). И только в этот момент начинается отрисовка нового состояния в реальный DOM. Здесь уже должно быть понятно, что Реакт умнее, чем кажется на первый взгляд, и вносит изменения в реальный DOM настолько эффективно, насколько это возможно, ведь он знает КАК его надо изменить. Сколько раз читал про вот эту фичу реакта, так и не могу понять одного — сказано, что реакт вычисляет дифф между старым и новым виртуал-домом и затем заменяет нужные узлы в реальном доме. То есть все равно происходит обращение в дом (понятное дело, что без этого никак). Суть в том, что дом (реальный) отрисовывается не целиком а кусками? Так это ведь тоже самое, что обращаться, скажем, через id к конкретному дом-узлу и перерисовывать его детей, как в проекте рсс-ридер например - список каналов и список постов? Учитывая, что реакт написан на том же чистом жсе - очевидно, что можно реализовать подобный подход самостоятельно. Много сложного кода? ","creator":{"public_name":"More Mone","id":160242,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":40194,"body":"> Учитывая, что реакт написан на том же чистом жсе - очевидно, что можно реализовать подобный подход самостоятельно. Много сложного кода?\n\nПервая часть уркоа отвечает именно на этот вопрос. Рассказывает о том что мы это делали руками и почему так делать плохо и тому подобное. Плюс подробно об этом есть в видео https://www.youtube.com/watch?v=DCeNCr2tKOI","topic_id":18994}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":50111,"title":"Добрый вечер \n\nЯ правильно понимаю, что при таких манипуляциях с DOM:\n\nбыло =====> **<div><h1>Привет</h1></div>** \n\nстало ====> **<div><h1>Привет</h1><h2>Мир</h2></div>**\n\nC React: дорисует только элемент **h2** \n\nБез React: будет заново отрисован весь **div**\n\nТо есть когда мы на чистом JS работаем и вносим какое-то изменение то браузер тут же начинает перерисовывать все DOM элементы без разбора, тогда не понимаю как Реакту удается точечно изменять он же внутри себя все равно те же нативные методы будет вызывать для работы с DOM, на чистом JS я бы использовал что-то типо:\n\n**const div = document.querySelector('div');**\n\n**div.appendChild('h2')**, реакт ведь анаглогично бы поступил?\n","plain_title":"Добрый вечер Я правильно понимаю, что при таких манипуляциях с DOM: было =====> Привет ** стало ====> **ПриветМир ","creator":{"public_name":"K R","id":228341,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":107392,"body":"**K R**, приветствую.\n\n> Без React: будет заново отрисован весь div\n\nВы можете заменять и содержимое конкретного тега, выбрав его из dom и заменив точечно. Только вам для этого понадобится писать собственную библиотеку, которая будет заниматься поиском элементов, сравнением их с сохранённым снимком и запускать перерендеринг. Вы можете посмотреть это видео: https://www.youtube.com/watch?v=DCeNCr2tKOI чтобы немного лучше понять данную тему.\n","topic_id":50111}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":51628,"title":"Здравствуйте! Подскажите, что не так с моим решением - https://ru.hexlet.io/code_reviews/367325\n\nВ веб доступе появляется кнопка Open и по клику открывается модал. Почему-то не работает скрытие по крестику, хотя я передаю туглер в отклик. И судя по ошибкам, у меня не определяются правильно стили.","plain_title":"Здравствуйте! Подскажите, что не так с моим решением - https://ru.hexlet.io/code_reviews/367325 В веб доступе появляется кнопка Open и по клику открывается модал. Почему-то не работает скрытие по крестику, хотя я передаю туглер в отклик. И судя по ошибкам, у меня не определяются правильно стили. ","creator":{"public_name":"Artem Glushaev","id":176516,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Makarov","id":181967,"is_tutor":false},"id":110341,"body":"**Artem Glushaev**, приветствую!\n\nОбратите внимание на название колбека.","topic_id":51628}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":48101,"title":"> В предыдущем курсе мы впервые столкнулись c изменением DOM в процессе взаимодействия со страницей. \n\nПредыдущий курс был полиморфизм\n\n+=====================================================================+\n\n2. картинка в теории, где сверху виртуалДом, а снизу браузерДом, а так же из теории можно сделать вывод, что Реакт не отрисовывает заново весь Дом, а только где надо подставляет разницу(дифф)?\n3. `<span aria-hidden=\"true\">&times;</span>` почему выводится как крестик, по-умолчанию JSX же все экранирует, должен же быть по идеи такая строка `&times;`, разве нет?\n","plain_title":"В предыдущем курсе мы впервые столкнулись c изменением DOM в процессе взаимодействия со страницей. ? ","creator":{"public_name":"Daniyar Zhanakhmetov","id":239134,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":103721,"body":"**Daniyar Zhanakhmetov**, eval выполняет код из строки.\n\nЕсли честно, я не могу дать какую-то аналитику по логике экранирования в реакте, но с точки зрения логики полное экранирование с запретом спецсимволов ни к чему хорошему не приведут и создатели реакта это учли.","topic_id":48101},{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":103509,"body":"**Daniyar Zhanakhmetov**, приветствую.\n\n1. Исправим, спасибо.\n2. Да, в этом и суть и его главная оптимизация.\n3. JSX - это же как язык, он не запрещает писать в строках код, который потом выполнится через `eval`. Там есть некоторая безопасность с экранированием, но она не мешает жить и использовать спецсимволы.\n","topic_id":48101},{"creator":{"public_name":"Daniyar Zhanakhmetov","id":239134,"is_tutor":false},"id":103715,"body":"**Сергей Мелодин**, извиняюсь, подзабыл, напомните, что такое eval ? :-)\n\nПросто ранее в теории было сказано, что все сразу экранируется, и чтобы какие-то символы не экранировались нужно использовать определенную команду, здесь я ее не вижу. Поэтому и вопрос такой. Команды нет, а символ все равно не экранировался.","topic_id":48101},{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":103737,"body":"**Daniyar Zhanakhmetov**, супер, спасибо, что поделились!","topic_id":48101},{"creator":{"public_name":"Daniyar Zhanakhmetov","id":239134,"is_tutor":false},"id":103735,"body":"**Сергей Мелодин**, извиняюсь вопрос был поставлен неправильно. Но я уже разобрался.\n\nДело было вот в чем:\n1) есть функция, возвращающая строку, в которой находится спец.символ.\n2) есть просто тег, внутри которого есть спецсимвол.\n\nТак вот: jsx автоматически все значение строки экранирует(на всякий случай), а если это это символ в теге без строки, то он не экранируется.\n\nhttps://codepen.io/f1eeman/pen/PoNMPwg?editors=1010","topic_id":48101}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}},{"id":16465,"title":"Мда, скакать между компонентами и смотреть куда какие свойства передаются то еще удовольствие, даже когда все на одном экране..","plain_title":"Мда, скакать между компонентами и смотреть куда какие свойства передаются то еще удовольствие, даже когда все на одном экране.. ","creator":{"public_name":"Артем Лебедев","id":22488,"is_tutor":false},"comments":[{"creator":{"public_name":"Константин Бочинин","id":43218,"is_tutor":false},"id":36123,"body":"Именно. Я так перескакал, что мне показалась в `Component.jsx` кода не хватает - забыли `Modal.Title`. Я дописал его туда. И со своим `Modal.jsx` тесты прошел :)","topic_id":16465}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Virtual Dom","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":629,"slug":"js_react_virtual_dom_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"### src/Modal.jsx\n\nРеализуйте компонент `<Modal>` (Модальное окно)\n\n\n\nИспользование:\n\n```javascript\nexport default class Component extends React.Component {\n state = { modal: false };\n\n toggle = (e) => {\n this.setState({\n modal: !this.state.modal,\n });\n }\n\n render() {\n return (\n <div>\n <button type=\"button\" className=\"modal-open-button btn btn-danger\" onClick={this.toggle}>Open</button>\n <Modal isOpen={this.state.modal}>\n <Modal.Header toggle={this.toggle}>Modal title</Modal.Header>\n <Modal.Body>\n Lorem ipsum dolor sit amet, consectetur adipisicing elit\n </Modal.Body>\n <Modal.Footer>\n <button type=\"button\" className=\"modal-close-button btn btn-secondary\" onClick={this.toggle}>Cancel</button>\n </Modal.Footer>\n </Modal>\n </div>\n );\n }\n}\n```\n\nHTML закрытого состояния:\n\n```html\n<div>\n <button type=\"button\" class=\"modal-open-button btn btn-danger\">Open</button>\n <div class=\"modal\" style=\"display: none;\" role=\"dialog\">\n <div class=\"modal-dialog\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <div class=\"modal-title\">Modal title</div>\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\">\n </button>\n </div>\n <div class=\"modal-body\">Lorem ipsum dolor sit amet, consectetur adipisicing elit</div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"modal-close-button btn btn-default\">Cancel</button>\n </div>\n </div>\n </div>\n </div>\n</div>\n```\n\nВ открытом состоянии строчка: `<div class=\"modal\" style=\"display: none;\">` заменяется на `<div class=\"modal fade show\" style=\"display: block;\">`\n\nУ открытого модального окна две кнопки закрывающие его: крестик справа вверху и кнопка `Cancel` справа внизу.\n\n### Подсказка\n\n* [Modal](https://getbootstrap.com/docs/5.1/components/modal/)\n","prepared_readme":"### src/Modal.jsx\n\nРеализуйте компонент `<Modal>` (Модальное окно)\n\n\n\nИспользование:\n\n```javascript\nexport default class Component extends React.Component {\n state = { modal: false };\n\n toggle = (e) => {\n this.setState({\n modal: !this.state.modal,\n });\n }\n\n render() {\n return (\n <div>\n <button type=\"button\" className=\"modal-open-button btn btn-danger\" onClick={this.toggle}>Open</button>\n <Modal isOpen={this.state.modal}>\n <Modal.Header toggle={this.toggle}>Modal title</Modal.Header>\n <Modal.Body>\n Lorem ipsum dolor sit amet, consectetur adipisicing elit\n </Modal.Body>\n <Modal.Footer>\n <button type=\"button\" className=\"modal-close-button btn btn-secondary\" onClick={this.toggle}>Cancel</button>\n </Modal.Footer>\n </Modal>\n </div>\n );\n }\n}\n```\n\nHTML закрытого состояния:\n\n```html\n<div>\n <button type=\"button\" class=\"modal-open-button btn btn-danger\">Open</button>\n <div class=\"modal\" style=\"display: none;\" role=\"dialog\">\n <div class=\"modal-dialog\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <div class=\"modal-title\">Modal title</div>\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\">\n </button>\n </div>\n <div class=\"modal-body\">Lorem ipsum dolor sit amet, consectetur adipisicing elit</div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"modal-close-button btn btn-default\">Cancel</button>\n </div>\n </div>\n </div>\n </div>\n</div>\n```\n\nВ открытом состоянии строчка: `<div class=\"modal\" style=\"display: none;\">` заменяется на `<div class=\"modal fade show\" style=\"display: block;\">`\n\nУ открытого модального окна две кнопки закрывающие его: крестик справа вверху и кнопка `Cancel` справа внизу.\n\n### Подсказка\n\n* [Modal](https://getbootstrap.com/docs/5.1/components/modal/)\n","has_solution":true,"entity_name":"Virtual Dom"},"units":[{"id":1986,"name":"theory","url":"/courses/js-react/lessons/virtual-dom/theory_unit"},{"id":2043,"name":"quiz","url":"/courses/js-react/lessons/virtual-dom/quiz_unit"},{"id":2005,"name":"exercise","url":"/courses/js-react/lessons/virtual-dom/exercise_unit"}],"links":[{"id":427879,"name":"Почему сложно программировать UI (Кирилл Мокевнин)","url":"https://youtu.be/DCeNCr2tKOI"}],"ordered_units":[{"id":1986,"name":"theory","url":"/courses/js-react/lessons/virtual-dom/theory_unit"},{"id":2043,"name":"quiz","url":"/courses/js-react/lessons/virtual-dom/quiz_unit"},{"id":2005,"name":"exercise","url":"/courses/js-react/lessons/virtual-dom/exercise_unit"}],"id":968,"slug":"virtual-dom","state":"approved","name":"Virtual Dom","course_order":260,"goal":"Познакомиться с устройством React","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"В одном из предыдущих курсов впервые рассматривалось изменение DOM в процессе взаимодействия со страницей. Этот способ резко отличается от того, который использовался в курсе [JS: DOM API](https://ru.hexlet.io/courses/js-dom). Важнейшее отличие связано с тем, как происходит изменение состояния отрисованного экрана. При прямом манипулировании DOM нужно сделать следующее:\n\n1. Удалить из DOM то, что стало неактуально для следующего состояния.\n1. Изменить, если надо, те элементы, которые присутствуют на экране и должны остаться.\n1. Добавить новые элементы на страницу (точечно).\n\nДругими словами, чтобы перейти в новое состояние, нужно изменить старое. Значит про него надо знать.\n\nВ React всё совсем по-другому. После любого изменения и вызова `setState` React создаёт новое состояние и отрисовывает все компоненты так, как будто это происходит с нуля. На самом деле отрисовка действительно происходит с нуля. Неважно, что было до этого момента на экране и как оно располагалось. Любое изменение в React приводит к тому, что приложение отрисовывается заново.\n\nСоздатели React называют этот подход _one-way data flow_:\n\n1. Действия пользователя приводят к изменению состояния приложения (через `setState`).\n1. React запускает цикл отрисовки. Начиная от того компонента, в котором было изменено состояние (как правило, корневой компонент), через пропсы данные постепенно распространяются от компонентов более высокого уровня до самых глубинных компонентов.\n1. Получившийся `html` интегрируется в страницу.\n\n\n\nТе, кто хорошо знаком с функциональным подходом, могут увидеть прямую связь. React действительно делает мир неизменяемым (immutable). Самый простой способ реализовать подобное поведение — использовать `mountElement.innerHTML`, который заменяет `html` целиком после вызова `setState`. Хотя на практике этот подход сопряжён с кучей сложностей (пример реализации смотрите в дополнительных материалах), он позволяет в 200 строк построить библиотеку, которая будет работать как React.\n\nГлавная проблема при использовании `innerHTML` связана с производительностью. Сказать, что это медленно — ничего не сказать. Поэтому создатели React пошли другим путём.\n\n## Дерево виртуального DOM\n\nРанее в курсе говорилось, что компоненты отрисовываются, и это немного не так. В реальности после того, как отработает их рендеринг (вызов функции `render` для всего дерева компонентов), создаётся так называемый _виртуальный DOM_ (virtual DOM). Это просто JS-объект определённой структуры, который отражает состояние экрана. Далее React сравнивает новое дерево виртуального DOM со старым и строит разницу между ними («дифф», или объект, описывающий разницу между старым и новым состоянием). И только в этот момент начинается отрисовка нового состояния в реальный DOM. Здесь уже должно быть понятно, что React умнее, чем кажется на первый взгляд, и вносит изменения в реальный DOM настолько эффективно, насколько это возможно, ведь он знает КАК его надо изменить.\n\n\n\nИз описанного выше есть важное следствие. Тот реальный DOM, который находится под контролем React (это все потомки элемента, в который рендерится корневой компонент), не может изменяться никем снаружи React-приложения. Если подобное произойдёт, то React не сможет нормально функционировать, ведь ему приходится отслеживать текущее состояние DOM для того, чтобы производить вычисления «диффа». Когда подобное происходит, React ругается и говорит, что ему мешают работать.\n\nВажно: виртуальный DOM — не самоцель React, как многие думают. Это просто эффективный способ реализовать идею _one-way data flow_. Если бы работал вариант с `innerHTML`, то никто бы не делал виртуальный DOM.\n\nИ хотя построение JS-объекта — это гораздо более дешёвая операция, чем работа с реальным DOM, все равно могут возникать ситуации, когда процесс вычисления занимает много времени, и это тормозит приложение. Этой теме посвящён один из следующих уроков.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1980,"name":"theory","url":"/courses/js-react/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":1980,"name":"theory","url":"/courses/js-react/lessons/intro/theory_unit"}],"id":962,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Познакомиться с курсом и подготовить окружение.","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"> React — A JavaScript library for building user interfaces\n\nТак характеризуют React его создатели, разработчики компании Facebook. Появившись в 2013 году, React быстро стал набирать обороты и получил широчайшее распространение. На момент создания курса на [GitHub](https://github.com/facebook/react) у проекта более 200 тысяч звёзд.\n\nСекрет успеха в том, что React позволил под другим углом посмотреть на процесс создания интерфейсов. Он резко снизил порог входа и сложность получаемых решений. Причём не только по сравнению с ручной работой с DOM, но и по сравнению со многими фреймворками.\n\nИ хотя React как библиотеку для отрисовки можно встраивать в существующий технологический стек там, где это имеет смысл, он также способен взять на себя полное управление фронтендом. Правда, в данном случае для эффективной работы придётся подключить еще некоторые ключевые дополнения, такие как `redux` и `react-router`.\n\nФундаментальная идея, лежащая в основе работы React, оказалась настолько мощной, что её расширили далеко за рамки браузера. С React можно работать как на сервере (server-side rendering), так и на мобильных платформах (React Native). Вы не ослышались: сейчас на языке JavaScript можно создавать приложения под мобильные платформы, которые работают почти так же эффективно, как и нативные приложения. Такую ситуацию, когда один подход используется для реализации разных задач (сайт, мобильные приложения) называется **«Learn once, Write anywhere»**.\n\n## Практика\n\nВ этом курсе вам предстоит плотно пройтись по возможностям React и хорошенько с ними разобраться. Однако это не отменяет необходимости работать с React и вне среды Hexlet.\n\n### CodePen\n\nСамый простой способ попрактиковаться с React — это сервис [CodePen](https://codepen.io). После регистрации вы сможете создать **pen** (пен) — изолированную среду разработки, подключив туда React. Результаты кода отображаются там же, в соседней панели.\n\nCodePen позволяет вставлять пены прямо в свой сайт, эта возможность будет использоваться для демонстрации. Вы можете не только проанализировать такой код, но запустить и даже поправить его.\n\n```html\n<div id=\"result\">0</div>\n<button id=\"increment\">+</button>\n<button id=\"decrement\">-</button>\n```\n\n```javascript\nlet state = 0\n\nconst result = document.getElementById('result');\nconst process = (newState) => {\n state = newState;\n result.textContent = newState;\n};\n\nconst inc = document.getElementById('increment');\ninc.addEventListener('click', () => process(state + 1));\n\nconst dec = document.getElementById('decrement');\ndec.addEventListener('click', () => process(state - 1));\n```\n\n[Попрактиковаться](https://codepen.io/hexlet/pen/eEyJeZ/)\n\n### create-vite\n\nРазработчики, понимая как сложно настроить с нуля экосистему для старта фронтенд-проектов, создали проект под названием [create-vite](https://www.npmjs.com/package/create-vite). Это npm-библиотека, которая позволяет стартануть с нулевой конфигурацией. Для создания проекта нужно выполнить команду:\n\n```bash\nnpm create vite@latest\n```\n\nПри выполнении команды, библиотека предлагает выбрать нужные настройки. Среди них можно выбрать фреймворк React. Ниже пример выбора настроек, подходящих под наши нужды:\n\n```bash\nNeed to install the following packages:\n create-vite@5.2.3\nOk to proceed? (y) y\n✔ Project name: … vite-project\n✔ Select a framework: › React\n✔ Select a variant: › JavaScript\n\nScaffolding project in /tmp/vite-project...\n\nDone. Now run:\n\n cd vite-project\n npm install\n npm run dev\n\n```\n\nКогда утилита завершит работу, она предложит выполнить команды. Выполняем:\n\n```bash\ncd vite-project\nnpm install\nnpm run dev\n```\n\nДальше просто открывайте [http://localhost:5173/](http://localhost:5173/) и наслаждайтесь.\n\n### babel-preset-react\n\nЕсли вы всё же решитесь делать всё самостоятельно, то не забудьте подключить пресет (preset) `@babel/preset-react` к вашей конфигурации Babel. React расширяет JS, и Babel не может работать с кодом без этого пресета.\n\n## Курс\n\nНа протяжении всего курса вам предстоит создавать маленькие и не очень маленькие компоненты Bootstrap. Если вы еще не знакомы с ним, то прочитайте [руководство по Бутстрапу](https://guides.hexlet.io/ru/bootstrap/). В любом случае в каждой задаче будет подробно описано, какой компонент использовать и как он должен выглядеть.\n\nКак и в курсе [JS: DOM API](https://ru.hexlet.io/courses/js-dom), часть тестов основана на снепшот-тестировании, а это значит, что важно использовать вёрстку именно так, как указано в задании.\n\nКроме этого, практически все задания визуализированы. Перед тем, как запускать тесты убедитесь, что всё работает в веб-доступе.\n\n## Классовые компоненты актуальны\n\nС момента создания React претерпел значительные изменения. Сначала основное внимание уделялось классовым компонентам, которые предоставили разработчикам возможность создавать компоненты с состоянием и жизненным циклом. Однако с появлением функциональных компонентов и хуков в версии React 16.8 в 2019 году акцент сместился. Хуки позволили разработчикам использовать состояние и другие возможности React без необходимости использовать классы.\n\nСтоит отметить, что классовые компоненты никуда не ушли и по-прежнему широко используются во многих приложениях. А некоторые задачи просто не возможно решить без них (например, [ErrorBoundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)). Поэтому классовые компоненты остаются актуальными и по сей день, а в чем-то даже превосходят функциональные. Любой функциональный компонент можно заменить классовым, но не любой классовый можно заменить функциональным.\n\nКлассовые компоненты имеют более четкую и понятную структуру, особенно для тех, кто уже знаком с объектно-ориентированным программированием. Они используют синтаксис классов и методов, что может быть более интуитивно понятно для разработчиков. Кроме того, классы предоставляют более явные методы жизненного цикла и управления состоянием.\n\nПо этим причинам изучение React лучше всего начинать с классовых компонентов. Это позволит комфортно погрузиться в изучение основ. Далее вы перейдете к изучению функциональных компонентов и хуков уже имея необходимый набор знаний как работают компоненты React. Работа с хуками — это отдельный пласт знаний, требующий базового понимания концепций React. Уроки в курсе построены таким образом, чтобы максимально комфортно изучить все аспекты работы с React.\n\n## Отладка\n\nТак как React отрабатывает на фронтенде, то и ошибки будут появляться там же. Не забывайте всегда держать открытой консоль (например, в инструментах разработки Chrome) и внимательно читать всё, что там написано. Большая часть ошибок будет выводиться именно там.\n\nТакже не забудьте поставить **React Developer Tools**. Это расширение для браузера, которое даёт очень удобную панель для анализа происходящего с React в вашем приложении. Начните его использовать с первого урока.\n"},"id":150,"slug":"js-react","challenges_count":11,"name":"JS: React","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите React. Вы узнаете больше о создании компонентов, состоянии и жизненном цикле компонентов, а также о работе с событиями. В итоге вы научитесь создавать интерактивные пользовательские интерфейсы и манипулировать состоянием приложения. Изучение React пригодится, если вы решите создавать современные веб-приложения. Знания из этого курса помогают программистам создавать интерактивные интерфейсы и управлять состоянием приложения. Так же вы узнаете, как создавать приложения на функциональных компонентах. Вы научитесь использовать хуки для управления состоянием, доступа к DOM-элементам и других функций.","kind":"basic","updated_at":"2026-02-13T18:01:20.953Z","language":"javascript","duration_cache":122460,"skills":["Создавать полноценные приложения на React","Грамотно организовывать состояние приложения","Взаимодействовать с бекендом по API","Интегрировать React со сторонними библиотеками","Использовать встроенные хуки","Создать приложение, состоящее только из функциональных компонентов","Внедрить готовые хуки для решения типовых задач"],"keywords":["состояние","JSX","компоненты","производительность"],"lessons_count":30,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQ1NjAsInB1ciI6ImJsb2JfaWQifX0=--7a44c11be035358543b87dbcc241650aa935b0a4/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":64,"slug":"middle-frontend","title":"Middle-фронтенд разработчик","audience":"for_programmers","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":2006,"duration_in_months":5},"id":115,"slug":"middle-frontend","title":"Middle-фронтенд разработчик","subtitle":"Научитесь строить сложные интерфейсы и оптимизировать веб-приложения","subtitle_for_lists":"Научитесь строить сложные интерфейсы и оптимизировать веб-приложения","locale":"ru","current":true,"duration_in_months_text":"5 месяцев","stack_slug":"middle-frontend","price_text":"от 4 050 ₽","duration_text":"5 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTIyNSwicHVyIjoiYmxvYl9pZCJ9fQ==--3c9f823d2a682639c9e5cb055e85378898a46a22/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/js-react/lessons/virtual-dom/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">JS: React</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">Теория: Virtual Dom</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Virtual Dom","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"JS: React"},"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>В одном из предыдущих курсов впервые рассматривалось изменение DOM в процессе взаимодействия со страницей. Этот способ резко отличается от того, который использовался в курсе <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.hexlet.io/courses/js-dom" rel="noopener noreferrer" target="_blank">JS: DOM API</a>. Важнейшее отличие связано с тем, как происходит изменение состояния отрисованного экрана. При прямом манипулировании DOM нужно сделать следующее:</p>
<ol>
<li>Удалить из DOM то, что стало неактуально для следующего состояния.</li>
<li>Изменить, если надо, те элементы, которые присутствуют на экране и должны остаться.</li>
<li>Добавить новые элементы на страницу (точечно).</li>
</ol>
<p>Другими словами, чтобы перейти в новое состояние, нужно изменить старое. Значит про него надо знать.</p>
<p>В React всё совсем по-другому. После любого изменения и вызова <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">setState</code> React создаёт новое состояние и отрисовывает все компоненты так, как будто это происходит с нуля. На самом деле отрисовка действительно происходит с нуля. Неважно, что было до этого момента на экране и как оно располагалось. Любое изменение в React приводит к тому, что приложение отрисовывается заново.</p>
<p>Создатели React называют этот подход <em>one-way data flow</em>:</p>
<ol>
<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">setState</code>).</li>
<li>React запускает цикл отрисовки. Начиная от того компонента, в котором было изменено состояние (как правило, корневой компонент), через пропсы данные постепенно распространяются от компонентов более высокого уровня до самых глубинных компонентов.</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">html</code> интегрируется в страницу.</li>
</ol>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQ1NjEsInB1ciI6ImJsb2JfaWQifX0=--af73592a18b8200c2306b1fa157b4e56a594c480/one-way-data-flow.png" alt="One-Way Data Flow" loading="lazy"/></p>
<p>Те, кто хорошо знаком с функциональным подходом, могут увидеть прямую связь. React действительно делает мир неизменяемым (immutable). Самый простой способ реализовать подобное поведение — использовать <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">mountElement.innerHTML</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">html</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">setState</code>. Хотя на практике этот подход сопряжён с кучей сложностей (пример реализации смотрите в дополнительных материалах), он позволяет в 200 строк построить библиотеку, которая будет работать как React.</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">innerHTML</code> связана с производительностью. Сказать, что это медленно — ничего не сказать. Поэтому создатели React пошли другим путём.</p>
<h2 id="heading-2-1">Дерево виртуального DOM</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">render</code> для всего дерева компонентов), создаётся так называемый <em>виртуальный DOM</em> (virtual DOM). Это просто JS-объект определённой структуры, который отражает состояние экрана. Далее React сравнивает новое дерево виртуального DOM со старым и строит разницу между ними («дифф», или объект, описывающий разницу между старым и новым состоянием). И только в этот момент начинается отрисовка нового состояния в реальный DOM. Здесь уже должно быть понятно, что React умнее, чем кажется на первый взгляд, и вносит изменения в реальный DOM настолько эффективно, насколько это возможно, ведь он знает КАК его надо изменить.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTQ1NjIsInB1ciI6ImJsb2JfaWQifX0=--b5ddd5439d4f77a7c81e1f0524d52dbdf55851c7/virtual-dom.png" alt="Виртуальный DOM" loading="lazy"/></p>
<p>Из описанного выше есть важное следствие. Тот реальный DOM, который находится под контролем React (это все потомки элемента, в который рендерится корневой компонент), не может изменяться никем снаружи React-приложения. Если подобное произойдёт, то React не сможет нормально функционировать, ведь ему приходится отслеживать текущее состояние DOM для того, чтобы производить вычисления «диффа». Когда подобное происходит, React ругается и говорит, что ему мешают работать.</p>
<p>Важно: виртуальный DOM — не самоцель React, как многие думают. Это просто эффективный способ реализовать идею <em>one-way data flow</em>. Если бы работал вариант с <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">innerHTML</code>, то никто бы не делал виртуальный DOM.</p>
<p>И хотя построение JS-объекта — это гораздо более дешёвая операция, чем работа с реальным DOM, все равно могут возникать ситуации, когда процесс вычисления занимает много времени, и это тормозит приложение. Этой теме посвящён один из следующих уроков.</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/middle-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">5 месяцев</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">Middle-фронтенд разработчик</p><p 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="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NTIyNSwicHVyIjoiYmxvYl9pZCJ9fQ==--3c9f823d2a682639c9e5cb055e85378898a46a22/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png" alt="Middle-фронтенд разработчик" 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">от 4 050 ₽</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-react/lessons/virtual-dom/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 / 30</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-react/lessons/virtual-dom/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>