Віртуальний DOM (VDOM) — це концепція програмування, в якій ідеальне або «віртуальне» представлення інтерфейсу користувача зберігається в пам'яті і синхронізується з «справжнім» DOM за допомогою бібліотеки, такої як ReactDOM. Цей процес називається узгодженням.
Такий підхід і робить API React декларативним: ви вказуєте, в якому стані повинен знаходитися інтерфейс користувача, а React домагається, щоб DOM відповідав цьому стану. Це абстрагує маніпуляції з атрибутами, обробку подій та ручне оновлення DOM, які інакше довелося б використовувати для розробки програми.
Оскільки «віртуальний DOM» — це радше патерн, ніж конкретна технологія, цим терміном іноді позначають різні поняття. У світі React «віртуальний DOM» зазвичай асоціюється з React-елементами, оскільки вони є об'єктами, що представляють інтерфейс користувача. Тим не менш, React також використовує внутрішні об'єкти, які називаються «волокнами» (fibers), щоб зберігати додаткову інформацію про дерево компонентів. Їх також можна вважати частиною реалізації «віртуального DOM» у React.
Тіньовий DOM схожий на віртуальний DOM?
Ні, вони зовсім різні. Тіньовий DOM (Shadow DOM) – це браузерна технологія, призначена в основному для визначення області видимості змінних та CSS у веб-компонентах. Віртуальний DOM – це концепція, реалізована бібліотеками JavaScript під API браузера.
Що таке React Fiber?
Fiber — новий механізм узгодження React 16, основна мета якого зробити рендеринг віртуального DOM інкрементним. Дізнатися більше про це.
Об'єктна модель документа, або DOM, є програмним інтерфейсом доступу до елементів веб-сторінок.По суті, це API сторінки, що дозволяє читати та маніпулювати вмістом, структурою та стилями сторінки. Давайте розберемося як це влаштовано та як це працює.
– Як будується веб-сторінка?
Процес перетворення вихідного HTML-документа в стилізовану та інтерактивну сторінку, що відображається, називається "критичним шляхом рендерингу" (Critical Rendering Path). Хоча цей процес можна розбити на кілька етапів, ці етапи можна умовно згрупувати у два етапи. У першому браузер аналізує документ, щоб визначити, що зрештою відображатиметься на сторінці, а в другому браузер виконує рендеринг.
Результатом першого етапу є те, що називається "render tree" (дерево рендерінгу). Дерево рендерингу – це представлення елементів HTML, які відображатимуться на сторінці та пов'язаних з ними стилів. Щоб збудувати це дерево, браузеру потрібні дві речі:
- CSSOM, представлення стилів, пов'язаних з елементами
- DOM, представлення елементів
– З чого складається DOM?
DOM – це об'єктне уявлення вихідного HTML-документа. Він має деякі відмінності, як ми побачимо нижче, але насправді це спроба перетворити структуру і зміст документа HTML в об'єктну модель, яка може використовуватися різними програмами.
Структура об'єктів DOM представлена тим, що називається "деревом вузлів". Воно так називається, тому що його можна розглядати як дерево з одним батьківським елементом, що розгалужується на кілька дочірніх гілок, кожна з яких може мати листя. І тут батьківський " елемент " – це кореневий елемент, дочірні " гілки " – це вкладені елементи, а " листя " – це вміст усередині елементів.
Давайте розглянемо цей HTML-документ як приклад:
Віртуальний DOM та деталі його реалізації у React - Dosvid.v.ua Віртуальний DOM та деталі його реалізації у React - Dosvid.v.ua How are you?
Цей документ може бути поданий у вигляді наступного дерева вузлів:
– Чим DOM не є
У наведеному прикладі здається, що DOM є відображенням 1:1 вихідного HTML-документа. Однак, як я вже казав, є різницю. Щоб повністю зрозуміти, що таке DOM, нам потрібно поглянути на те, чим він не є.
– DOM не є копією вихідного HTML
Хоча DOM створений з HTML-документа, він не завжди такий самий. Є два випадки, коли DOM може відрізнятися від вихідного HTML.
– 1. Коли HTML містить помилки розмітки
DOM – це інтерфейс доступу до дійсних (тобто вже відображається) елементів документа HTML. У процесі створення DOM браузер сам може виправити деякі помилки в коді HTML.
Розглянемо як приклад цей HTML-документ:
У документі відсутні елементи та , що є обов'язковою вимогою для HTML. Але якщо ми подивимося на дерево, що вийшло DOM, то побачимо, що це було виправлено:
– 2. Коли DOM модифікується кодом JavaScript
Крім того, що DOM є інтерфейсом для перегляду вмісту документа HTML, він також може бути змінений.
Ми, наприклад, можемо створити додаткові вузли для DOM, використовуючи Javascript.
var newParagraph = document.createElement("p"); var paragraphContent = document.createTextNode("I'm new!"); newParagraph.appendChild(paragraphContent); document.body.appendChild(newParagraph);Цей код змінить DOM, але зміни не відобразяться у документі HTML.
– DOM – це не те, що ви бачите у браузері (тобто дерево рендерингу)
У вікні перегляду браузера ви бачите дерево рендерингу, яке, як я вже казав, є комбінацією DOM та CSSOM. Чим відрізняється DOM від дерева рендерингу, так це те, що останнє складається тільки з того, що зрештою буде відображено на екрані.
Оскільки дерево рендерингу стосується лише того, що відображається, воно виключає елементи, які візуально приховані. Наприклад, елементи, які мають стилі з display: none.
Віртуальний DOM та деталі його реалізації у React - Dosvid.v.ua
DOM включатиме елемент
:
Однак дерево рендерингу і, отже, те, що видно у вікні перегляду, не буде включено до цього елемента.
– DOM – це не те, що відображається у DevTools
Ця різниця трохи менша, тому що інспектор елементів DevTools забезпечує найближче наближення до DOM, яке ми маємо в браузері. Однак інспектор DevTools містить додаткову інформацію, якої немає у DOM.
Найкращий приклад цього – псевдоелементи CSS. Псевдоелементи, створені з використанням селекторів ::before та ::after, є частиною CSSOM та дерева рендерингу, але технічно не є частиною DOM. Це пов'язано з тим, що DOM створюється лише з вихідного документа HTML, не включаючи стилі, застосовані до елемента.
Незважаючи на те, що псевдоелементи не є частиною DOM, вони є в нашому інспекторі елементів devtools.
– Резюме
DOM – це інтерфейс до HTML-документу. Він використовується браузерами як перший крок до визначення того, що візуалізувати у вікні перегляду, та кодом JavaScript для зміни вмісту, структури чи стилю сторінки.
Хоча DOM схожий на інші форми вихідного документа HTML, він відрізняється з низки причин:
- Це завжди вірний (валідний) HTML код
- Це модель, яка може бути змінена за допомогою JavaScript
- До нього не входять псевдоелементи (такі як :: after)
- До нього входять приховані елементи (такі як display: none)
Що таке Shadow DOM?
Всі елементи та стилі в HTML-документі і, отже, в DOM, знаходяться в одній великій глобальній області. Будь-який елемент на сторінці може бути доступний за допомогою document.querySelector(), незалежно від того, наскільки глибоко він вкладений у документ або де він знаходиться. Так само CSS, застосований до документа, може вибрати будь-який елемент, незалежно від того, де він знаходиться.
Така поведінка може бути дуже зручною, якщо хочемо застосувати стилі до всього документа.Неймовірно корисно мати можливість вибирати кожен окремий елемент на сторінці та встановлювати, наприклад, їх розміри одним рядком.
З іншого боку, трапляються випадки, коли елемент вимагає повної ізоляції, і ми не хочемо, щоб на нього впливали навіть глобальні стилі. Хорошим прикладом цього є сторонні віджети, такі як кнопка Follow для Twitter на деяких сторінках. Така кнопка може відображатись як звичайне посилання.
Але якщо ви вивчите цей елемент у DevTools, ви помітите, що кнопка є елементом, який завантажує невеликий документ зі стилізованою кнопкою, яку ви насправді бачите.
Це єдиний спосіб, яким Twitter може гарантувати, що передбачуваний стиль їхнього віджету залишиться незайманим будь-яким CSS в документі. Хоча існують способи використання каскаду стилів для досягнення того ж результату, але жоден інший метод не дасть такої ж гарантії, як , хоч і він не ідеальний.
Shadow DOM був створений для забезпечення можливості ізоляції та компонентизації безпосередньо на веб-платформі без необхідності покладатися на такі інструменти як .
– DOM усередині DOM
Ви можете думати про тіньовий DOM як про "DOM всередині DOM". Це власне ізольоване дерево DOM зі своїми елементами та стилями, повністю ізольоване від вихідного DOM.
Хоча тільки недавно його стали використовувати програмісти, тіньовий DOM роками використовувався користувачами агентами для створення та оформлення складних компонентів, таких як елементи форми. Наприклад візьмемо елемент введення діапазону. Щоб створити його на сторінці, все, що нам потрібно зробити, це додати наступний елемент:
Якщо ми подивимося глибше, побачимо, що цей один елемент фактично складається з кількох менших елементів, що управляють доріжкою та повзунком.
Це робиться за допомогою тіньового DOM.Елемент, який надається HTML-документу як простий, але насправді він складається з декількох елементів і стилів, пов'язаних з компонентом, які не є частиною глобальної області видимості DOM.
– Як працює shadow DOM
Щоб проілюструвати, як працює тіньовий DOM, відтворюємо кнопку Twitter "Follow", використовуючи тіньовий DOM замість .
Спочатку ми почнемо з shadow host (тіньового хоста). Це звичайний елемент HTML у вихідному DOM, якого ми хочемо приєднати новий тіньовий DOM. Для такого компонента, як кнопка "Follow", він також може містити запасний елемент, який ми хотіли б відобразити, якщо JavaScript не був включений на сторінці або тіньовий DOM не підтримується.
Щоб прикріпити тіньовий DOM до нашого хоста, ми використовуємо метод attachShadow().
const shadowEl = document.querySelector(".shadow-host"); const shadow = shadowEl.attachShadow();Це створить порожній shadow root (тіньовий корінь) як дочірній елемент нашого тіньового хоста. Тіньовий корінь – це початок нового тіньового DOM, так само як елемент є початком вихідного DOM. Ми можемо побачити наш тіньовий корінь в інспекторі devtools за допомогою #shadow-root.
Хоча тепер дочірні елементи HTML видно в інспекторі, вони більше не видно на сторінці, оскільки заробив тіньовий корінь.
Далі ми хочемо створити контент для формування нашого нового тіньового дерева. Щоб створити нашу кнопку "Follow", нам потрібен тільки новий елемент, який буде майже таким же, як і вже наявне резервне посилання, але зі значком.
const link = document.createElement("a"); link.href = shadowEl.querySelector("a").href; link.innerHTML = `$`;Далі ми додамо цей новий елемент до нашої тіньової DOM так само, як додаємо будь-який елемент як дочірній елемент до іншого за допомогою методу appendChild().
shadow.appendChild(link);
Нарешті ми можемо додати кілька стилів, створивши елемент і додавши його до тіньового кореня.
const styles = document.createElement("style"); styles.textContent = `a, span < vertical-align: top; display: inline-block; box-sizing: border-box; >a < height: 20px; padding: 1px 8px 1px 6px; background-color: #1b95e0; color: #fff; border-radius: 3px; font-weight: 500; font-size: 11px; font-family: 'Helvetica Neue', Arial, sans-serif; line-height: 18px; text-decoration: none; >a:hover < background-color: #0c7abf; >span <position: relative; top: 2px; width: 14px; height: 14px; margin-right: 3px; background: transparent 0 0 no-repeat; background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2072%2072%22%3E %3Cpath%20fill%3D%22none%22%20d%3D%22M0%200h72v72H0z%22%2F%3E%3Cpath%20class%3D%22icon%22%20fill%3D%22%23fff%22%20 .812%2015.14c-2.348%201.04 -4.87%201.744-7.52%202.06%202.704-1.62%204.78-4.186%205.757-7.243-2.53%201.5-5.33%202.592-8.314520 .948%209%2049 .182%209c-7.23%200-13.092%205.86-13.092%2013.093%200%201.026.118%202.02.338%202.98C25.543%2024452 %2011.396c-1.1 25%201.936-1.77%204.184-1.77%206.58%200%204.543%202.312%208.552%205.824%2010.9-2.146-.07-4.165.6.5.6. 02.11-.002.163 %200%206.345%204.513%2011.638%2010.504%2012.84-1.1.298-2.256.457-3.45.457-.845%200-1.666-.078-2.6.2 5%208.985%201 2.23%209.09-4.482%203.51-10.13%205.605-16.26%205.605-1.055%200-2.096-.06-3.122-.184%205.794%228.2 .067%205.882%2 024.083%200%2037.25-19.95%2037.25-37.25%200-.565-.013-1.133-.038-1.693%202.558-1.847%204.778-6.5 %3C%2Fsvg% 3E); >`; shadow.appendChild(styles);– DOM проти shadow DOM
У певному сенсі, тіньовий DOM є "полегшеною" версією DOM. Як і DOM, він є поданням елементів HTML, яке використовується для визначення того, що потрібно відображати на сторінці, і дозволяє змінювати елементи.Але на відміну від DOM, тіньовий DOM не використовує глобальних стилів документа. Тіньовий DOM, як випливає з назви, завжди приєднаний до елемента у звичайному DOM. Без базових елементів DOM, тіньової DOM немає.
Що таке Virtual DOM?
Document Object Model (Об'єктна Модель Документу) – це об'єктне подання HTML документа та інтерфейс для управління цим об'єктом. Shadow DOM можна розглядати як полегшену версію DOM. Це також об'єктно-орієнтоване представлення елементів HTML, але Shadow DOM дозволяє нам розділити основний DOM на менші ізольовані частини, які можна використовувати у документах HTML.
Інший схожий термін, це "Virtual DOM". Хоча ця концепція існує вже кілька років, вона стала більш популярною завдяки використанню її у різних фреймворках, таких як React, Vuejs тощо.
– Навіщо нам потрібний Virtual DOM?
Щоб зрозуміти, чому виникла концепція віртуального DOM, повернімося до DOM. Як я вже згадував, у DOM є дві частини – об'єктне представлення документа HTML та API для керування цим об'єктом.
Наприклад, давайте візьмемо як приклад простий HTML-документ з неупорядкованим списком та одним елементом списку.
Цей документ може бути представлений як DOM дерево:
html head lang="en" body ul li "List item"
Допустимо, ми хочемо змінити вміст першого елемента списку на "List item one", а також додати другий елемент списку. Для цього нам потрібно буде використовувати API DOM, щоб знайти елементи, які ми хочемо оновити, створити нові елементи, додати атрибути та контент, а потім нарешті оновити самі елементи DOM.
const listItemOne = document.getElementsByClassName("list__item")[0]; listItemOne.textContent = "List item one"; const list = document.getElementsByClassName("list")[0]; const listItemTwo = document.createElement("li"); listItemTwo.classList.add("list__item"); listItemTwo.textContent = "List item two"; list.appendChild(listItemTwo);– DOM не було зроблено для цього.
Коли в 1998 році була випущена перша специфікація для DOM, ми створювали та керували веб-сторінками по-іншому. API DOM використовувався для створення та оновлення вмісту сторінок набагато рідше, ніж це робиться сьогодні.
Прості методи, такі як document.getElementsByClassName() підходять для невеликої кількості змін, але якщо ми оновлюємо кілька елементів на сторінці кожні кілька секунд, це може стати дуже дорогим, щоб постійно запитувати та оновлювати DOM.
Більше того, через спосіб налаштування API-інтерфейсів зазвичай простіше виконувати більш дорогі операції, коли ми оновлюємо більші частини документа, ніж знаходити та оновлювати конкретні елементи. Повертаючись до нашого прикладу зі списком, у певному сенсі простіше замінити весь невпорядкований список на новий, ніж модифікувати певні елементи.
У цьому конкретному прикладі різниця у продуктивності між методами, ймовірно, незначна. Однак у міру зростання розміру веб-сторінки стає все важливішим вибирати та оновлювати лише те, що необхідно.
– . але до того ж тут віртуальний DOM!
Віртуальний DOM був створений для вирішення цих проблем, пов'язаних із необхідністю частого оновлення DOM більш продуктивним способом. На відміну від DOM або Shadow DOM, Virtual DOM не є офіційною специфікацією, а є новим методом взаємодії з DOM.
Віртуальний DOM можна розглядати як копію вихідного DOM. Цю копію можна часто маніпулювати та оновлювати, не використовуючи API DOM.Після того, як всі оновлення були внесені до віртуального DOM, ми можемо подивитися, які конкретні зміни необхідно внести до вихідного DOM, і зробити їх цільовим та оптимізованим способом.
– Який вигляд має віртуальний DOM?
Слово віртуальний має тенденцію додавати певну загадковість там, де її насправді немає. Фактично, віртуальний DOM – це просто звичайний об'єкт JavaScript.
Повернімося до дерева DOM, яке ми створили раніше:
html head lang="en" body ul li "List item"
Дерево може бути представлене як об'єкт JavaScript:
const vdom = < tagName: "html", children: [ < tagName: "head" >, < tagName: "body", children: [ < tagName: "ul", attributes: < "class": "list" >, children: [ < tagName: "li", attributes: < "class": "list__item" >, textContent: "List item" > // end li ] > // end ul ] > // end body ] > // end html
Ми можемо думати про цей об'єкт як про наш віртуальний DOM. Як і вихідний DOM, це об'єктне уявлення нашого HTML-документа. Але так як це простий об'єкт JavaScript, ми можемо вільно і часто маніпулювати ним, не торкаючись реального DOM, поки це нам не знадобиться.
Замість того, щоб використовувати один об'єкт для всього об'єкта, найпоширенішою є робота з невеликими розділами віртуального DOM. Наприклад, ми можемо працювати з компонентом списку, який буде прив'язаний до нашого невпорядкованого елемента.
const list = < tagName: "ul", attributes: < "class": "list" >, children: [ < tagName: "li", attributes: < "class": "list__item" >, textContent: "List item" > ] >;
– Як працює віртуальний DOM
Тепер, коли ми побачили, як виглядає віртуальний DOM, як він працює для вирішення проблем продуктивності та зручності використання DOM?
Як я вже згадував, ми можемо використовувати віртуальний DOM, щоб виділити конкретні зміни, які необхідно внести до DOM, і зробити ці конкретні оновлення окремо.Повернімося до нашого неупорядкованого списку і внесемо ті самі зміни, що й у DOM API.
Перше, що ми зробимо, це зробимо копію віртуального DOM, який містить зміни, які хочемо зробити. Оскільки нам не потрібно використовувати API DOM, ми можемо просто створити новий об'єкт повністю.
const copy = < tagName: "ul", attributes: < "class": "list" >, children: [ < tagName: "li", attributes: < "class": "list__item" >, textContent: "List item one " >, < tagName: "li", атрибути: < "class": "list__item" >, textContent: "List item two" > ] >;
Ця копія використовується для створення того, що називається "diff" між вихідним та віртуальним DOM, у цьому випадку вихідним списком, та оновленим списком. Diff може виглядати приблизно так:
const diffs = [ < NewNode: < /* New version of list item one */ >, oldNode: < /* original version of list item one */ >, index: /* Index element parent's list of child nodes */ >, < newNode: < /* list item two */ >, index: < /* */ >> ];
У цьому розділі наведено вказівки щодо оновлення фактичного DOM. Як тільки всі відмінності зібрані, ми можемо пакетно вносити зміни до DOM, роблячи лише необхідні оновлення.
Наприклад, ми могли б перебрати кожен diff і додати нового нащадка чи оновити старого залежно від цього, що вказано в diff.
const domElement = document.getElementsByClassName("list")[0]; diffs.forEach((diff) => < const newElement = document.createElement(diff.newNode.tagName); /* Add attributes . */ if (diff.oldNode) < // the new version domElement.replaceChild(diff.newNode, diff.index); >else < // If no old version exists;Зауважте, що це дійсно спрощена версія того, як реально працює віртуальний DOM, і є багато випадків, які я тут не описав.
– Віртуальний DOM та фреймворки
Насправді з віртуальним DOM частіше працюють через фреймворки, а не взаємодіють з ним безпосередньо, як я показав у прикладі вище.
Фреймворки, такі як React та Vue, використовують концепцію віртуального DOM для більш продуктивних оновлень DOM. Наприклад, наш компонент списку може бути написаний у React так.
import React from 'react'; import ReactDOM від 'react-dom'; const list = React.createElement("ul", < className: "list" >, React.createElement("li", < className: "list__item" >, "List item") ); ReactDOM.render(list, document.body);Якщо ми хотіли б оновити наш список, ми могли б просто переписати весь шаблон списку і знову викликати ReactDOM.render(), передавши новий список.
const newList = React.createElement("ul", < className: "list" >, React.createElement("li", < className: "list__item" >, "List item one"), React.createElement("li", < className: "list__item" >, "List item two"); setTimeout(() => ReactDOM.render(newList, document.body), 5000);Оскільки React використовує віртуальний DOM, навіть якщо ми перемальовуємо весь шаблон, оновлюються ті частини, які дійсно змінюються. Якщо ми подивимося на наші інструменти розробника, коли відбудуться зміни, ми побачимо конкретні елементи та конкретні частини змінюваних елементів.
– DOM проти Virtual DOM
Нагадаємо, що віртуальний DOM – це інструмент, який дозволяє нам взаємодіяти з елементами DOM більш простим та продуктивним способом. Це JavaScript-об'єктна вистава DOM, яку ми можемо змінювати так часто, як нам потрібно. Зміни, внесені до цього об'єкта, потім зіставляються, а зміни в реальному DOM проводяться набагато рідше.
В одному з попередніх курсів вперше розглядалася зміна DOM у процесі взаємодії зі сторінкою. Цей спосіб різко відрізняється від того, що використовувався в курсі JS: DOM API. Найважливіша відмінність пов'язана з тим, як відбувається зміна стану екрана. При прямому маніпулюванні DOM потрібно зробити таке:
- Видалити з DOM те, що стало неактуальним для наступного стану.
- Змінити, якщо треба, ті елементи, які є на екрані і повинні залишитися.
- Додати нові елементи на сторінку (точково).
Іншими словами, щоб перейти у новий стан, потрібно змінити старе. Значить про нього треба знати.
У React все зовсім інакше. Після будь-якої зміни та виклику setState React створює новий стан і малює всі компоненти так, ніби це відбувається з нуля. Насправді малювання справді відбувається з нуля. Неважливо, що було до цього моменту на екрані і як воно було. Будь-яка зміна в React призводить до того, що програма малюється заново.
Творці React називають цей підхід one-way data flow:
- Дії користувача призводять до зміни стану програми (через setState).
- React запускає цикл відображення. Починаючи від компонента, в якому було змінено стан (як правило, кореневий компонент), через пропси дані поступово поширюються від компонентів вищого рівня до самих глибинних компонентів.
- html, що вийшов, інтегрується в сторінку.
Ті, хто добре знайомий із функціональним підходом, можуть побачити прямий зв'язок. React справді робить світ незмінним (immutable).Найпростіший спосіб реалізувати подібну поведінку – використовувати mountElement.innerHTML, який замінює html повністю після виклику setState. Хоча на практиці цей підхід пов'язаний з купою складнощів (приклад реалізації дивіться у додаткових матеріалах), він дозволяє у 200 рядків побудувати бібліотеку, яка працюватиме як React.
Головна проблема при використанні innerHTML пов'язана з продуктивністю. Сказати, що це повільно нічого не сказати. Тому творці React пішли іншим шляхом.
Дерево віртуального DOM
Раніше в курсі йшлося про те, що компоненти малюються, і це трохи не так. Насправді після того, як відпрацює їх рендеринг (виклик функції render для всього дерева компонентів), створюється так званий віртуальний DOM (virtual DOM). Це просто JS-об'єкт певної структури, що відбиває стан екрану. Далі React порівнює нове дерево віртуального DOM зі старим і будує різницю між ними (дифф, або об'єкт, що описує різницю між старим і новим станом). І тільки в цей момент починається малювання нового стану в реальний DOM. Тут уже має бути зрозуміло, що React розумніший, ніж здається на перший погляд, і вносить зміни в реальний DOM настільки ефективно, наскільки це можливо, адже він знає, як його треба змінити.
З наведеного вище є важливе слідство. Той реальний DOM, який перебуває під контролем React (це всі нащадки елемента, в який рендерсується кореневий компонент), не може змінюватися ніким зовні програми React. Якщо таке станеться, то React не зможе нормально функціонувати, адже йому доводиться відстежувати поточний стан DOM для того, щоб проводити обчислення диффа. Коли подібне відбувається, React лається і каже, що йому заважають працювати.
Відкрити доступ
Курси програмування для новачків та досвідчених розробників. Почніть навчання безкоштовно
- 130 курсів, 2000+ годин теорії
- 1000 практичних завдань у браузері
- 360 000 студентів