Веб-разработка постоянно развивается. В последнее время стал популярным один тренд, который в основном противоречит общепринятому представлению о том, как нужно разрабатывать веб-приложения. Некоторые возлагают на него большие надежды, а другие испытывают разочарование. У каждого на это есть свои причины, которые в двух словах объяснить достаточно трудно.



Код веб-страницы традиционно состоит из трех разделов, каждый из которых выполняет свои обязанности: HTML-код определяет структуру и семантику, CSS-код определяет внешний вид, а JavaScript-код определяет его поведение. В командах с участием дизайнеров, HTML / CSS разработчиков и JavaScript-разработчиков это разделение получается естественно: дизайнеры определяют визуальные элементы и пользовательский интерфейс, разработчики HTML и CSS размещают эти визуальные элементы на странице в браузере, а JavaScript-разработчики добавляют взаимодействие с пользователем, чтобы связать все вместе и «заставить это работать». Каждый может работать над своими задачами, не вмешиваясь в код остальных двух категорий разработчиков. Но все это справедливо для так называемого «старого стиля».

В последние годы JavaScript-разработчики стали определять структуру страницы в JavaScript, а не в HTML (например, используя js-фреймворк React), что помогает упростить создание и сопровождение кода взаимодействия с пользователем. Без js-фреймворков разрабатывать современные веб-сайты гораздо сложнее. Конечно, когда вы говорите кому-то, что написанный им HTML-код необходимо разбить на части и смешать с JavaScript, с которым он знаком очень плохо, это (по понятным причинам) может быть воспринято в штыки. Как минимум собеседник спросит, зачем нам этого вообще надо, и как мы выиграем от этого.

Как JavaScript-разработчику в межфункциональной команде, мне иногда задают этот вопрос, и часто мне трудно ответить на него. Все материалы, которые я нашел по этой теме, написаны для аудитории, которая уже знакома с JavaScript. А это не очень хорошо для тех, кто специализируется исключительно на HTML и CSS. Но паттерн HTML-in-JS (или что-то еще, что обеспечивает те же преимущества), вероятно, будет еще некоторое время востребован, поэтому я думаю, это важная вещь, которую должны понимать все, кто занимается веб-разработкой.

В этой статье я приведу примеры кода для тех, кому интересно, но моя цель — объяснить этот подход так, чтобы его можно было понять и без них.

Ликбез: HTML, CSS и JavaScript


Чтобы максимально расширить аудиторию этой статьи, я хочу кратко рассказать о том, какую роль эти языки играют в создании веб-страниц, и какое разделение между ними существовало изначально. Если вы все это знаете, можете пропустить этот раздел.

HTML: для структуры и семантики


Код на HTML (HyperText Markup Language) задает структуру и семантику контента, который будет размещен на странице. Например, HTML-код этой статьи содержит текст, который вы сейчас читаете, и в соответствии с заданной структурой, этот текст размещен в отдельном абзаце, после заголовка и перед вставкой CodePen.

Создадим, к примеру, простую веб-страницу со списком покупок:



Мы можем сохранить этот код в файле, открыть его в веб-браузере, и браузер отобразит полученный результат. Как видите, код HTML в этом примере представляет собой страницу, которая содержит заголовок «Список покупок» (Shopping List (2 items)), текстовое поле ввода, кнопку «Добавить товар» (Add Item) и список из двух пунктов («Яйца» и «Масло»). Пользователь введет адрес в своем веб-браузере, затем браузер запросит этот HTML-код с сервера, загрузит его и отобразит. Если в списке уже есть элементы, сервер может прислать HTML с уже готовыми элементами, как в этом примере.

Попробуйте набрать что-нибудь в поле ввода и нажать кнопку «Добавить элемент». Вы увидите, что ничего не происходит. Кнопка не связана ни с каким кодом, который мог бы изменить HTML, а HTML не может ничего изменить сам. Мы вернемся к этому через минуту.

CSS: для изменения внешнего вида


Код CSS (Cascading Style Sheets, Каскадные Таблицы Стилей) определяет внешний вид страницы. Например, CSS этой статьи задает шрифт, интервал и цвет текста, который вы читаете.

Возможно, вы заметили, что наш пример списка покупок выглядит очень просто. HTML не позволяет задать такие вещи, как интервалы, размеры шрифтов и цвета. Поэтому мы и используем CSS. Мы могли бы добавить CSS-код для этой страницы, чтобы немного украсить ее:



Как видите, этот CSS-код изменил размер и толщину символов текста, а также цвет фона. Разработчик может по аналогии написать правила для собственного стиля, и они будут последовательно применяться к любой структуре HTML: если мы добавим на эту страницу элементы section, button или ul, для них будут действовать те же изменения шрифта.
Но кнопка Add Item по-прежнему ничего не делает: здесь нам как раз и понадобится JavaScript.

JavaScript: для реализации поведения


Код JavaScript определяет поведение интерактивных, или динамических, элементов на странице. Например, CodePen написан с использованием JavaScript.

Чтобы кнопка Add Item в нашем примере работала без JavaScript, нам потребовалось бы использовать специальный HTML-код, чтобы заставить его отправлять данные обратно на сервер (<form action \u003d '...'>, если вам вдруг интересно). Более того, браузер перезагрузит обновленную версию всего HTML-файла без сохранения текущего состояния страницы. Если бы этот список покупок был частью большой веб-страницы, все, что делал пользователь, было бы потеряно. Не важно на сколько пикселей вниз вы продвинулись, читая текст — при перезагрузке сервер вернет вас в начало, не важно сколько минут видео вы просмотрели — при перезагрузке оно начнется заново.

Вот так раньше работали все веб-приложения: каждый раз, когда пользователь взаимодействовал с веб-страницей, он как будто закрывал свой веб-браузер и снова открывал его. Это не имеет большого значения для простого учебного примера, но для большой сложной страницы, загрузка которой может занять некоторое время, такой подход не эффективен ни для браузера, ни для сервера.

Если мы хотим что-то менять на странице без полной перезагрузки этой страницы, нам нужно использовать JavaScript:



Теперь, когда мы набираем текст в поле ввода и нажимаем кнопку Add Item, наш новый элемент добавляется в список, а количество элементов в верхней части обновляется! В реальном приложении мы также добавили бы некоторый код для отправки нового элемента на сервер в фоновом режиме, чтобы он отображался при следующей загрузке страницы.

Отделение JavaScript от HTML и CSS оправданно даже в этом простом примере. Более сложные взаимодействия работают так: HTML загружается и отображается, а впоследствии запускается JavaScript, чтобы добавить что-то к нему и изменить его. Однако по мере роста сложности веб-приложения, нам нужно внимательнее следить за тем, что, где и когда меняет наш JavaScript-код.

Если бы мы продолжали развивать это приложение со списком покупок, то, возможно, добавили бы кнопки для редактирования или удаления элементов из списка. Допустим, мы напишем JavaScript-код для кнопки, которая удаляет элемент, но забудем добавить код, который обновляет информацию об общем количестве элементов в верхней части страницы. Внезапно у нас возникает ошибка: после того, как пользователь удалит элемент, указанное общее количество на странице не будет соответствовать списку!

Как только мы заметили ошибку, мы исправили ее, добавив ту же строку totalText.innerHTML из нашего кода для Add Item в код для Remove Item. Теперь у нас одинаковый код, который дублируется в нескольких местах. Позже, скажем, мы захотим изменить этот код так, чтобы вместо «(2 items)» вверху страницы он выводил «Items: 2». Мы должны убедиться, что не забудем обновить этот код во всех трех местах: в HTML, в JavaScript-коде для кнопки Add Item и в JavaScript-коде для кнопки Remove Item. Если мы этого не сделаем, у нас будет другая ошибка, из-за которой этот текст будет резко меняться после взаимодействия с пользователем.

Уже в этом простом примере мы видим, как легко запутаться в коде. Конечно. существуют подходы и практики, позволяющие упорядочить JavaScript-код, чтобы облегчить решение этой проблемы. Однако, по мере того, как все будет становиться сложнее, нам придется продолжать реструктуризацию и постоянно переписывать проект, чтобы его можно было спокойно развивать и сопровождать. Пока HTML и JavaScript хранятся отдельно, может потребоваться много усилий, чтобы обеспечить синхронизацию между ними. Это одна из причин, по которой стали популярны новые JavaScript-фреймворки, такие как React: они предназначены для обеспечения более формализованных и эффективных взаимоотношений между HTML и JavaScript. Чтобы понять, как это работает, нам сначала нужно немного углубиться в компьютерную науку.

Императивное vs декларативное программирование


Ключевая вещь, которую необходимо понять, — это разница в мышлении. Большинство языков программирования позволяют следовать только одной парадигме, хотя некоторые из них поддерживают сразу две. Важно понимать обе парадигмы, чтобы оценить по достоинству основное преимущество HTML-in-JS с точки зрения разработчика JavaScript.

  • Императивное программирование. Слово «императивное» здесь означает, что мы задаем компьютеру некую последовательность действий. Строка императивного кода во многом похожа на императив (повелительное наклонение) в английском языке: она дает компьютеру конкретную инструкцию для выполнения задачи, как говорится, по образу и подобию. В императивном программировании мы должны точно говорить компьютеру, как делать каждую мелочь, которая нам нужна. В веб-разработке такой подход скоро признают «устаревшим». Пока он еще используется при работе с Vanilla JavaScript и такими библиотеками, как jQuery. JavaScript в моем примере списка покупок следует императивной парадигме.
    • «Делай X, потом делай Y, потом делай Z».
    • Пример: когда пользователь выберет этот элемент, добавь к нему класс .selected; и когда пользователь уберет свой выбор с элемента, удали класс .selected.

  • Декларативное программирование. Его уровень абстракции выше, чем у императивного программирования. Вместо того, чтобы давать инструкции, мы вместо этого «объявляем» (декларируем), какие результаты мы хотим увидеть после того, как компьютер что-то сделает. То есть мы говорим ему не как делать, а что должно быть сделано. Языки и фреймворки (например, React), поддерживающие декларативную парадигму самостоятельно определяют, как достичь результатов, которых мы ждем от них. Правда, эти инструменты внутри тоже построены на императивном коде, но они скрывают от нас, как и в какой последовательности выполняют действия, направленные на достижение результатов. Так что, мы не должны думать об этом:
    • «Результат должен быть XYZ. Делай все, что тебе нужно, чтобы это произошло.
    • Пример: этот элемент имеет класс .selected, если пользователь его выбрал.


HTML — декларативный язык


Забудьте о JavaScript на мгновение. Вот важный факт: HTML является декларативным языком. В HTML-файле вы можете объявить что-то вроде:

<section>
  <h1>Hello</h1>
  <p>My name is Mike.</p>
</section>

Когда веб-браузер прочитает этот HTML-код, он самостоятельно определит необходимые шаги и выполнит их:

  1. Создай элемент section.
  2. Создай элемент-заголовок уровня 1 (h1).
  3. Установи для текста элемента-заголовка значение «Hello».
  4. Помести заголовочный элемент в элемент section.
  5. Создай элемент абзаца (p).
  6. Установи текст элемента абзаца на «My name is Mike».
  7. Помести элемент абзаца в элемент section.
  8. Помести элемент section в HTML-документ.
  9. Выведи документ на экран.

Для веб-разработчика то, как именно браузер делает эти вещи, не имеет значения: все, что имеет значение, — это то, что он делает их. Это прекрасный пример, иллюстрирующий разницу между этими двумя парадигмами программирования. Короче говоря, HTML — это декларативная абстракция, обернутая вокруг императивно реализованного механизма отображения веб-браузера. Он заботится о том, «как», поэтому вам нужно беспокоиться только о том, «что». Вы можете наслаждаться жизнью при написании декларативного HTML, потому что добрые люди из Mozilla, Google и Apple написали для вас императивный код, когда создавали веб-браузер.

JavaScript — императивный язык


Мы уже рассмотрели простой пример императивного JavaScript в приведенном выше примере списка покупок, и я описал, как сложность функций приложения влияет на усилия, необходимые для их реализации, и на вероятность появления ошибок в этой реализации.

Теперь давайте рассмотрим немного более сложную задачу и посмотрим, как ее можно упростить с помощью декларативного подхода. Представьте себе веб-страницу, которая содержит следующее:

  • список помеченных флажков, каждая строка которых меняется на свой цвет при выборе;
  • текст внизу, например «1 из 4 выбран», который должен обновляться при изменении флажков;
  • кнопка Select All («Выбрать все»), которую следует отключить, если все флажки уже установлены;
  • кнопка Select None («Не выбирать ничего»), которую следует отключить, если флажки не установлены.

Вот реализация на простом HTML, CSS и императивном JavaScript:



const captionText = document.getElementById('caption-text');
const checkAllButton = document.getElementById('check-all-button');
const uncheckAllButton = document.getElementById('uncheck-all-button');
const checkboxes = document.querySelectorAll('input[type="checkbox"]');

function updateSummary() {
  let numChecked = 0;
  checkboxes.forEach(function(checkbox) {
    if (checkbox.checked) {
      numChecked++;
    }
  });
  captionText.innerHTML = `${numChecked} of ${checkboxes.length} checked`;
  if (numChecked === 0) {
    checkAllButton.disabled = false;
    uncheckAllButton.disabled = true;
  } else {
    uncheckAllButton.disabled = false;
  }
  if (numChecked === checkboxes.length) {
    checkAllButton.disabled = true;
  }
}

checkAllButton.addEventListener('click', function() {
  checkboxes.forEach(function(checkbox) {
    checkbox.checked = true;
    checkbox.closest('tr').className = 'checked';
  });
  updateSummary();
});

uncheckAllButton.addEventListener('click', function() {
  checkboxes.forEach(function(checkbox) {
    checkbox.checked = false;
    checkbox.closest('tr').className = '';
  });
  updateSummary();
});

checkboxes.forEach(function(checkbox) {
  checkbox.addEventListener('change', function(event) {
    checkbox.closest('tr').className = checkbox.checked ? 'checked' : '';
    updateSummary();
  });
});


Головная боль как она есть


Чтобы реализовать эту функцию с помощью императивного JavaScript, нам нужно дать браузеру несколько подробных инструкций. Это словесное описание кода из примера выше:

  • В нашем HTML мы объявляем первоначальную структуру страницы:
    • Есть четыре элемента row (ряд таблицы, она же список), каждый из которых содержит флажок. Третий флажок помечен.
    • Есть текст «1 из 4 выбранных».
    • Есть кнопка Select All («Выбрать все»), которая включена.
    • Есть кнопка Select None («Не выбирать ничего»), которая отключена.


  • В нашем JavaScript мы пишем инструкции по поводу того, что нужно изменить, когда происходит каждое из этих событий:
    • Когда флажок меняется с непомеченного на помеченный:
      • Найди ряд, содержащий флажок, и добавь в него CSS класс .selected.
      • Найди все флажки в списке и посчитай, сколько из них помечено, а сколько нет.
      • Найди текст и обнови его, указав верное число выбранных покупок и их общее количество.
      • Найди кнопку Select None и включи ее, если она была отключена.
      • Если все флажки теперь установлены, найди кнопку Select All и отключи ее.


    • Когда флажок меняется с помеченного на непомеченный:
      • Найди ряд, содержащий флажок, и удали из него класс .selected.
      • Найди все флажки в списке и посчитай, сколько из них помечено, а сколько не помечено.
      • Найди элемент текста резюме и обновите его, указав проверенное число и общее количество.
      • Найди кнопку Select All и включи ее, если она была отключена.
      • Если все флажки сняты, найди кнопку Select None и отключи ее.

    • Когда нажата кнопка Select All:
      • Найди все флажки в списке и пометь их.
      • Найди все ряды в списке и добавь к ним класс .selected.
      • Найди текст и обновите его.
      • Найди кнопку Select All и отключи ее.
      • Найди кнопку Select None и включи ее.

    • Когда нажата кнопка Select None:
      • Найди все флажки в списке и снимите все флажки.
      • Найди все ряды в списке и удалите класс .selected из них.
      • Найди текстовый элемент резюме и обновите его.
      • Найди кнопку Select All и включи ее.
      • Найди кнопку Select None и отключи ее.

    Вау… Это много, правда? Что ж, нам лучше не забывать писать код для всех возможных ситуаций. Если мы забудем или испортим какую-либо из этих инструкций, мы получим ошибку: итоговые значения не совпадут с флажками, или будет включена кнопка, которая ничего не делает, когда вы нажимаете на нее, или выбранный ряд получит не тот цвет или что-то еще, о чем мы не подумали и не узнаем, пока пользователь не пожалуется.

    У нас тут большая проблема на самом деле: нет сущности, которая бы содержала полную информацию о состоянии нашего приложения (в данном случае это ответ на вопрос «какие флажки помечены?») и несла бы ответственность за обновление этой информации. Флажки, конечно же знают, помечены ли они, но об этом также должны знать CSS-код для рядов таблицы, текст и каждая кнопка. Пять копий этой информации хранятся отдельно по всему HTML, и когда она изменяется в любом из этих мест, JavaScript-разработчик должен отловить это и написать императивный код для синхронизации с изменениями в других местах.

    И это все еще простой пример одного небольшого компонента страницы. Если даже этот набор инструкций выглядит как головная боль, представьте, насколько сложным и хрупким становится более крупное веб-приложение, когда вам нужно реализовать все это на императивном JavaScript. Для многих сложных современных веб-приложений такое решение не масштабируется от слова «совсем».

    Ищем источник правды


    Такие инструменты, как React, позволяют декларативно использовать JavaScript. Так же как HTML является декларативной абстракцией над инструкциями по отображению в веб-браузере, так и React является декларативной абстракцией над JavaScript.

    Помните, как HTML позволяет нам сосредоточиться на структуре страницы, а не на деталях реализации, и как браузер отображает эту структуру? Точно так же, когда мы используем React, мы можем сосредоточиться на структуре, определив ее на основе данных, хранящихся в одном месте. Такой процесс называется односторонним связыванием, а место хранения данных о состоянии приложения называется «единым источником правды». Когда источник правды изменится, React автоматически обновит для нас структуру страницы. Он позаботится об обязательных шагах за кулисами, как это делает веб-браузер для HTML. Хотя в качестве примера здесь используется React, этот подход работает и для других фреймворков, например Vue.

    Давайте вернемся к нашему списку флажков из примера выше. В этом случае «правда», которую мы хотим знать, довольно лаконична: какие флажки помечены? Другие детали (например, текст, цвет строк, какие кнопки включены) — это уже информация, полученная на основе источника правды. И почему же они должны иметь свою собственную копию этой информации? Они должны просто использовать единый источник правды только для чтения, и все элементы страницы должны «просто знать», какие флажки помечены, и вести себя соответственно. Вы можете сказать, что ряды таблицы, текст и кнопки должны иметь возможность автоматически реагировать на флажок в зависимости от того, помечен он или не помечен (“видите, что там происходит?”)

    Скажи мне, чего ты хочешь (чего ты хочешь на самом деле)


    Чтобы реализовать эту страницу с помощью React, мы можем заменить список несколькими простыми описаниями фактов:

    • Существует список значений true / false, называемый checkboxValues, который показывает, какие поля помечены.
      • Пример: checkboxValues ??\u003d [false, false, true, false]
      • Этот список показывает, что у нас есть четыре флажка, установлен только третий.

    • Для каждого значения в checkboxValues в таблице ??есть ряд, который:
      • имеет CSS-класс с именем .selected, если значение равно true, и
      • содержит флажок, который проверяется, если значение истинно.

    • Существует текстовый элемент, который содержит текст «{x} of {y} selected», где {x} — это число истинных значений в checkboxValues, а {y} — общее количество значений в checkboxValues.
    • Есть кнопка Select All, которая включена, если в checkboxValues ??есть значения false.
    • Есть кнопка Select None, которая включена, если в checkboxValues ??есть значения true.
    • Когда флажок помечен, соответствующее значение изменяется в checkboxValues.
    • Когда кнопка Select All нажата, она устанавливает все значения checkboxValues ??в true.
    • При нажатии кнопки Select None все значения в checkboxValues ??устанавливаются в false.

    Вы заметите, что последние три пункта по-прежнему являются императивными инструкциями («Когда это произойдет, делайте это»), но это единственный императивный код, который нам нужно написать. Это три строки кода, и все они обновляют единый источник правды.

    Остальное — это декларативные высказывания («есть ...»), которые теперь встроены прямо в определение структуры страницы. Для этого мы пишем код для наших элементов с использованием специального синтаксического расширения JavaScript — JavaScript XML (JSX). Он напоминает HTML: JSX позволяет использовать похожий на HTML синтаксис для описания структуры интерфейса, а также обычный JavaScript. Это дает нам возможность смешивать логику JS со структурой HTML, поэтому структура может быть в любой момент времени разной. Все зависит от содержимого checkboxValues??.

    Перепишем наш пример на React:



    function ChecklistTable({ columns, rows, initialValues, ...otherProps }) {
      const [checkboxValues, setCheckboxValues] = React.useState(initialValues);
      
      function checkAllBoxes() {
        setCheckboxValues(new Array(rows.length).fill(true));
      }
      
      function uncheckAllBoxes() {
        setCheckboxValues(new Array(rows.length).fill(false));
      }
      
      function setCheckboxValue(rowIndex, checked) {
        const newValues = checkboxValues.slice();
        newValues[rowIndex] = checked;
        setCheckboxValues(newValues);
      }
      
      const numItems = checkboxValues.length;
      const numChecked = checkboxValues.filter(Boolean).length;
      
      return (
        <table className="pf-c-table pf-m-grid-lg" role="grid" {...otherProps}>
          <caption>
            <span>{numChecked} of {numItems} items checked</span>
            <button
              onClick={checkAllBoxes}
              disabled={numChecked === numItems}
              className="pf-c-button pf-m-primary"
              type="button"
            >
              Check all
            </button>
            <button
              onClick={uncheckAllBoxes}
              disabled={numChecked === 0}
              className="pf-c-button pf-m-secondary"
              type="button"
            >
              Uncheck all
            </button>
          </caption>
          <thead>
            <tr>
              <td />
              {columns.map(function(column) {
                return <th scope="col" key={column}>{column}</th>;
              })}
            </tr>
          </thead>
          <tbody>
            {rows.map(function(row, rowIndex) {
              const [firstCell, ...otherCells] = row;
              const labelId = `item-${rowIndex}-${firstCell}`;
              const isChecked = checkboxValues[rowIndex];
              return (
                <tr key={firstCell} className={isChecked ? 'checked' : ''}>
                  <td className="pf-c-table__check">
                    <input
                      type="checkbox"
                      name={firstCell}
                      aria-labelledby={labelId}
                      checked={isChecked}
                      onChange={function(event) {
                        setCheckboxValue(rowIndex, event.target.checked);
                      }}
                    />
                  </td>
                  <th data-label={columns[0]}>
                    <div id={labelId}>{firstCell}</div>
                  </th>
                  {otherCells.map(function(cell, cellIndex) {
                    return (
                      <td key={cell} data-label={columns[1 + cellIndex]}>
                        {cell}
                      </td>
                    );
                  })}
                </tr>
              );
            })}
          </tbody>
        </table>
      );
    };
    
    function ShoppingList() {
      return (
        <ChecklistTable
          aria-label="Shopping list"
          columns={['Item', 'Quantity']}
          rows={[
            ['Sugar', '1 cup'],
            ['Butter', '? cup'],
            ['Eggs', '2'],
            ['Milk', '? cup'],
          ]}
          initialValues={[false, false, true, false]}
        />
      );
    }
    
    ReactDOM.render(
      <ShoppingList />,
      document.getElementById('shopping-list')
    );
    


    JSX выглядит своеобразно. Когда я впервые столкнулся с этим, мне показалось, что так делать просто нельзя. Моей первоначальной реакцией было: «Чтоааа? HTML не может находится внутри JavaScript-кода!». И я был не один такой. Тем не менее, это не HTML, а JavaScript, одетый как HTML. На самом деле, это мощное решение.

    Помните те 20 императивных инструкций выше? Теперь у нас их три. Остальные (внутренние) императивные инструкции React сам выполняет для нас за кулисами — каждый раз, когда изменяется checkboxValues.

    С этим кодом теперь не может произойти ситуация, когда текст или цвет строки не соответствует флажкам, или когда кнопка включена, хотя она должна быть отключена. Существует целая категория ошибок, которые сейчас просто не могут возникнуть в нашем веб-приложении. Вся работа выполняется на основе единого источника правды, и мы, разработчики, можем писать меньше кода и лучше спать по ночам. Ну, JavaScript-разработчики, по крайней мере…

    JavaScript победил HTML: взял измором


    По мере усложнения веб-приложений поддержание классического разделения задач между HTML и JavaScript становится все более болезненным. Первоначально HTML был разработан для статических веб-страниц. Чтобы добавить туда более сложные интерактивные функции, необходимо реализовать на императивном JavaScript соответствующую логику, которая с каждой строчкой кода становится все более запутанной и хрупкой.

    Преимущества современного подхода: предсказуемость, возможность повторного использования и комбинирования


    Возможность использовать единый источник правды является наиболее важным преимуществом этой модели, но у нее есть и другие преимущества. Определение элементов нашей страницы в коде JavaScript означает, что мы можем повторно использовать компоненты (отдельные блоки веб-страницы), не позволяя нам копировать и вставлять один и тот же HTML-код в нескольких местах. Если нам нужно изменить компонент, достаточно изменить его код только в одном месте. При этом изменения произойдут во всех копиях компонента (внутри одного или даже во многих веб-приложениях, если мы применяем в них повторно используемые компоненты).

    Мы можем взять простые компоненты и составить их вместе, как кубики LEGO, создавая более сложные и полезные компоненты, не делая их логику слишком запутанной. А если мы используем компоненты, созданные другими разработчиками, мы можем легко накатить обновления или баг-фиксы без необходимости переписывать наш код.

    Тот же JavaScript, только в профиль


    Эти преимущества имеют свою обратную сторону. Есть веские причины, по которым люди ценят разделение HTML и JavaScript. Как я уже упоминал ранее, отказ от обычных HTML-файлов усложняет рабочий процесс для того, кто раньше не работал с JavaScript. Тот, кто ранее мог самостоятельно вносить изменения в веб-приложение, теперь должен приобрести дополнительные сложные навыки, чтобы сохранить свою автономию, или даже место в команде.

    Также есть и технические недостатки. Например, некоторые инструменты, такие как линтеры и парсеры, принимают на вход только обычный HTML, а работать вместо них со сторонними плагинами JavaScript может оказаться сложнее. Кроме того, JavaScript не самый лучший язык, но это единый стандарт, принятый для веб-браузеров. Новые инструменты и функции делают его лучше, но в нем все же есть некоторые подводные камни, о которых вам необходимо узнать, прежде чем вы будете работать с ним.

    Другая потенциальная проблема: когда семантическая структура страницы разбивается на абстрактные компоненты, разработчик может перестать думать о том, какие элементы HTML будут сгенерированы в результате. Специфические HTML-теги, такие как section и aside, имеют свою семантику, которая теряется при использовании тегов общего назначения, таких как div и span — даже если они визуально выглядят одинаково на странице. Это особенно важно для обеспечения доступности веб-приложения для разных категорий пользователей.

    Например, это повлияет на поведение программы чтения с экрана для пользователей с нарушениями зрения. Возможно, это не самые интересные для разработчика задачи, но JavaScript-разработчики всегда должны помнить, что сохранение семантики HTML в данном случае является наиболее важной задачей.

    Осознанная необходимость vs неосознанный тренд


    В последнее время использование фреймворков в каждом проекте стало трендом. Некоторые считают, что разделение HTML и JavaScript устарело, но это не так. Для простого статического веб-сайта, который не требует сложного взаимодействия с пользователем, это как раз подойдет. Более ярые поклонники React могут не согласиться со мной здесь, но если все, что делает ваш JavaScript, — это создание неинтерактивной веб-страницы, вам не следует использовать его. JavaScript загружается не так быстро, как обычный HTML. Поэтому, если вы не ставите задачу получить новый опыт разработки или повысить надежность кода, JavaScript тут принесет больше вреда, чем пользы.

    Кроме того, нет никакой необходимости писать весь свой веб-сайт на React. Или Vue! Или что там еще есть… Многие люди не знают этого, потому что все учебники в основном показывают, как использовать React для разработки сайта с нуля. Если у вас есть только один маленький сложный виджет на простом веб-сайте, вы можете использовать React для одного компонента. Вам не всегда нужно беспокоиться о webpack, Redux, Gatsby или еще о чем-то, что кто-то там рекомендует как «лучшие практики».

    Но если приложение достаточно сложное, использование современного декларативного подхода абсолютно стоит того. Является ли React наилучшим решением? Нет. У него уже сейчас есть сильные конкуренты. А потом появятся еще и еще… Но декларативное программирование никуда не денется, и в каком-нибудь новом фреймворке, вероятно, этот подход будет переосмыслен и реализован еще лучше.