company_banner

Почему JavaScript пожирает HTML: примеры кода

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



Код веб-страницы традиционно состоит из трех разделов, каждый из которых выполняет свои обязанности: 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 наилучшим решением? Нет. У него уже сейчас есть сильные конкуренты. А потом появятся еще и еще… Но декларативное программирование никуда не денется, и в каком-нибудь новом фреймворке, вероятно, этот подход будет переосмыслен и реализован еще лучше.
RUVDS.com
RUVDS – хостинг VDS/VPS серверов

Comments 22

    +1
    Неплохая статья для тех, кто не «в танке». Отличная вводная часть и её продолжение. Но портит впечатление лишь самая ключевая часть перед концовкой — где, собственно, показан пример кода на REACT. Код, который, при первом и даже втором прочтении очень сильно похож на расширенный диалект JavaScript(да по сути больше синтаксически им им и является), который просто возвращает в результате код на HTML. Но прямо перед этим кодом говорится фраза «JSX позволяет использовать похожий на HTML синтаксис для описания структуры интерфейса, а также обычный JavaScript» — которая, в купе с преамбулой, что REACT проповедует декларативный стиль программирования и HTML (в отличии от JavaScript) является декларативным, настраивает на то, что далее будет код, больше похожий на декларативный стиль программирования HTML — чем на JavaScript — и с первых же строчек кода примера мы видим почти чистый JavaScript. Да и даллеее — не сразу поймёшь — что это не JavaScipr — который генгерит и взаразает HTML код по шаблону — а именно декларация такой генерации и изменения по событиям. Отсутствие правильной подсветки синтаксиса (особенно в возвращаемых функциями HTML-подобных структурах) только усугубляет восприятие этого кода не как просто программы на расширенном JavaScript. Вот такое восприятия у меня, как у человека, который видит REACT листинг в первый раз в жизни!

    Тут не хватает прослойки — когда Вы говорите о декларировании намерений — типа
    Существует список значений true / false, называемый checkboxValues, который показывает, какие поля помечены.
    Пример: checkboxValues ​​\u003d [false, false, true, false]


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

    Правильная раскраска синтаксиса для новичков тоже очень важна — понятно, что Habr не поддерживает данный синтаксис. И если нет, возможности показать просто раскрашенный текст — то привели бы хотя бы скриншоты листинга — как делали в начале статьи.

    Ну и для полноты — было бы неплохо показать — аналогичное исполнение на других декларативных фреймворках — том же Vue — хотя бы финальный полный пример
      0
      привет! сейчас добавим в статью, спасибо за развернутый комментарий
        0
        Поддержу. Меня листинг JSX вообще отпугивает сильно. Из всех реактивных фрэймворков я в результате остановился на Vue, поскольку он позволил начать с малого.

        На Vue на мой взгляд, получается намного удобней сохранить разделение, при этом добавив в html декларативной логики — появляются по сути просто новые аттрибуты у тегов, с которыми будет взаимодействовать js(можно и через jsx, но если мы говорим о первом знакомстве — vue даёт выбор. Реакт — нет). Так же Vue намного дружелюбней для начинающих — его можно просто подключить на страницу олдскульным методом, без всяких сборок.
          0
          Отсутствие правильной подсветки синтаксиса (особенно в возвращаемых функциями HTML-подобных структурах) только усугубляет восприятие этого кода не как просто программы на расширенном JavaScript.

          Ну это на Хабре так. В редакторах и IDE всё правильно раскрашивается.

          +3
          у Vue и Svelte, HTML и JS прекрасно разделяются. И CSS тоже.
          Код компонента делиться на три части, три тега:
            +2
            У Angular и Ember тоже. Это только Реакт со своими тараканами.
              +1

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

                0
                это не мешает подсветке синтаксиса и его валидации
                  0
                  все как в любом template шаблоне, придумаете что-то новое, будете миллиардером
                +1
                Сегодня все три слоя и HTML и CSS и JS (в общем web) — это результат эволюции.
                И как в процессе всякой эволюции, что-то там отвалилось за ненадобностью, что-то осталось
                но жить не мешает, что-то новенькое появляется.
                Наши клиенты — браузеры, сегодня очень сложные машины.
                W3C как-то потихоньку удается что-то стандартизировать, для HTML и CSS.
                ECMA пишет свои стандарты, а вендоры придумывают свои фичи, и свои реализации
                стандартов. Собственно и вся эволюция web — просто история конкуренции и компромиссов.
                Иногда появляются инициативы типа Web Components, ShadowDOM,
                но так же исчезают, или становятся тупиковой веткой. Как-то не очень сегодня мы делаем
                проекты на Web Components или WebAssembly. Но мы хотим продавать онлайн, и экономика
                по большей части — экономика сервиса. И мы хотим писать ПО быстро и что бы это приносило деньги.
                И тогда появляется киллер-react или киллер-vue или еще какой киллер.
                Менеджеры нанимают кучу программеров, те учатся быстро писать ПО на киллер-frameworke.
                Запутываются, выпутываются, продают продукт. Придумать еще один киллер — не штука.
                И переделать его до полной неузнаваемости, тоже не штука. Штука — увидеть во всей этой мешанине
                образ нового веба, где будет все красиво, логично и по отдельности. Но чего-то пока таких визионеров
                нет (инициатива и самого создателя веб как-то мне не очень). Понятно, что тот кто пишет код на любимом
                киллере, смотрит на других немного свысока. Потому, что они пишут код на плохом киллере.
                Но похоже, правда в том, что пока не появится новая концепция веб, нам придется
                довольствоваться тем, что есть. И это не завит ни от HTML, CSS, javascript или reactа.
                И ни от способа смешать это все в коктейль и попробовать продать.
                  0
                  Много слов, но ни о чём. Лично я на 100% поддерживаю идею декларативного программирования и сверхвысокоуровневых языков. Просто HTML (а ещё ранее SQL) очень популяризовали эту парадигму в своё время (хотя тогда ещё люди и не осознали всей мощи в данной парадигме в целом, а не только в кругу её узкого применения в то время). JavaScript эту парадигму, наоборот — подпортил — попытавшись вернуть разработку обратно в лоно императивного программирования.

                  Я не уменьшаю заслуг JavaScriprt- он в своё время очень много дал (в основном за счёт фреймворков, созданных для него) для области web-разработки, да и вообще — для очень многих областей разработки в целом! Но сейчас пришла пора программистам больше обращаться именно к возможностям декларативного программирования. Опускаясь до императивного только для описания отдельных небольших подзадач, или для разработки «нижней» прослойки фреймворков.

                  И пусть REACT, на мой взгляд, не шибко хорошо демонстрирует эту идеологию, зато, в своё время, он был одним из первых, кто решил вернуть программистов сайтов с императивного пути массового внедрения JavaScript на путь декларативного программирования — который в своё время заложили стандарт HTML, затем CSS, затем XML и активно развивающиеся, с начала нулевых, движки сайтов.

                  Суть не в киллерах — суть в наставниках — проповедниках — гидах — которые смогут вывести программирование на качественно новый уровень — когда не нужно будет задумываться над тем как это сделать и не нарушить миллион связей — а нужно будет думать о том, что нужно сделать и какие связи настроить!
                    0
                    Я плохо написал, если вам не понятно. Язык разметки документа (HTML) — это где заголовок, а где абзац. CSS — это какого цвета заголовок или размер шрифта абзаца.
                    Со временем в HTML тэгах появилась возможности намешать и стилей и javascript.
                    В css тоже можно намешать разметки, там до или после, первый потомок div и т.д.
                    Javascript это универсальный язык программирования, и в него можно намешать чего угодно, благо, что есть DOM и DOM API. А также уже есть и websocket и webRTC и т.д.
                    В HTML без разницы корзина, пользователь или телефон на странице, css и подавно (нет там селектора телефон). И никто в самом начале не собирался продавать телефоны или показывать кино с помощью HTML или CSS. Поэтому, мы сегодня имеем то, что имеем. И сегодня нет возможности писать приложение, так, что вот это у нас разметка, это стили, а это код реакции на добавить в корзину. Хотя многие пытаются.
                      0
                      Отделить дизайн (а это и разметка и стили) от алгоритмов сейчас да — очень сложно, да и не нужно (по крайней мере на данном этапе развития индустрии). Но — можно постараться максимально их разнести — чтобы дизайнер макета сайта как можно меньше думал о программировании (тем более бизнес-логики), а больше о логике работы этого дизайна (как это, скажем, позволяет сделать CSS — заменив на описание стилей много рутинного программного кода) и его отображении. А программист бизнес-логики как можно меньше думал о дизайне. Но исключи одно из другого невозможно. Только минимизировать. И тем и другим так или иначе нужно и программировать и заниматься дизайном. Но их области деятельности должны быть максимально разграничены друг от друга — и работа не должна мешать друг другу — всё взаимодействие — через чётко обозначенные границы (способы туту могут быть разными). У REACT, на мой взгляд, это вообще не получается! Но есть другие фреймворки. Своё виденье — я описал в посте ниже. Но я тут полный профан. Просто верю в великое будущее декларативного программирования и в появление ещё более мощных и универсальных сверхвысокоуровневых языков разработки (чего бы то ни было), которые будут в больше степени декларативными, но и императивный и функциональные стили программирования так же будут поддерживать
                        0

                        Вот красиво это вы пишите. Правда, только что нет никаких таких универсальных для веба языков, пока во во всяком случае. Сегодня есть вообще только два типа языков, вполне изомофных типа Тьюринга и типа Черча. Как писать — это ваш личный выбор. Я люблю писать на языках типа Черч. И в принципе не против и мешать все это дело. Просто я смотрю на так называемые ООП штуки как просто на определения типов. Как вы с ними обходитесь, ваше дело, смотрите на них как на моноиды если удобно, или как на аппликативные функторы или ещё как. Но сегодня как не смотри, в вебе фронтэнда нет выбора. Если хотите заработать пишите на том, что есть. А есть не много, react, нормальный способ, только без JSX .Vue, тоже неплохо, если это не куски кода которые не понятно как друг с другом соотносятся. Ну и дальше по списку. Никак вы сегодня от этой мешанины не избавитесь. Ну хотите чистоты, тогда вам не к нам.

                          0

                          Всё разделено. Дизайнер занимается дизайном, а фронтенд-разработчик реализует дизайн с помощью вёрстки и скриптов. Классическое разделение на «верстальщика» и JS-программиста — порочная практика, которая очень плохо работает при разработке сложных интерфейсов.

                    0
                    То, что вы написали на реакт — является просто ООП подходом. Весь ООП — он про переиспользование кода и упрощение разработки в больших проектах. Вы и получили от него эти преимущества.

                    Не думаю, что отдельно подход HTML-in-JS добавил каких-то преимуществ. Мне кажется, что там из js делается html, что был бы просто какой-нибудь template engine с html — примерно одно и то же на выходе получилось бы.

                    А вот работу дизайнеров это способно усложнить. Ранее они могли одним взглядом пройти по HTML — увидеть полную структуру страницы и по ней писать CSS. Теперь придется проводить полный тест приложения, ведь реакт может внезапно по какому-то условию показать новый блок html. Но если js-разработчик и дизайнер — это одно лицо, то вообще проблемы не будет. Думаю, рынок идет в этом направлении.
                      0
                      То, что вы написали на реакт — является просто ООП подходом. Весь ООП — он про переиспользование кода и упрощение разработки в больших проектах. Вы и получили от него эти преимущества.

                      Не думаю, что отдельно подход HTML-in-JS добавил каких-то преимуществ. Мне кажется, что там из js делается html, что был бы просто какой-нибудь template engine с html — примерно одно и то же на выходе получилось бы.

                      А вот работу дизайнеров это способно усложнить. Ранее они могли одним взглядом пройти по HTML — увидеть полную структуру страницы и по ней писать CSS. Теперь придется проводить полный тест приложения, ведь реакт может внезапно по какому-то условию показать новый блок html. Но если js-разработчик и дизайнер — это одно лицо, то вообще проблемы не будет. Думаю, рынок идет в этом направлении.
                        0
                        Вот, тут я с Вами согласен почти на 100% (замечания будут в конце данного поста) — мне подход структуры кода REACT тоже не понравился — по этой же причине. Но, есть же другие фреймфорки — может там по-другому.
                        Но тут интересна сама идея — вывести программирование сложной логики как можно более на декларативный уровень. И это можно делать по-разному.

                        Лично я, вот, считаю, что дизайн-макет сайта всё-таки нужно стараться описывать как можно более целостно — при этом все эти связи стараться описывать в этой структуре. И тут да — дизайнер всё-таки отчасти должен быть программистом — чтобы суметь прямо в макете описывать простые условия и простые обработчики — которые влияют ИМЕННО на дизайн.
                        При этом вся более сложная логика — и уж тем более связанная с взаимодействием с бизнес-приложением (где должна быть сосредоточена бизнес-логика) — должна быть полностью вынесена из макета — и, желательно, создаваться другими людьми, в т.ч. с активным применением императивного стиля программирования — это уже вне компетенции дизайнера. Он должен только написать либо прямые вызовы этого API, либо подготовить события — на которые сможет подписаться эта внешняя логика уже без его участия (при интеграции).

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

                        Замечание1: Пример выше на REACT это не совсем ООП подход — (объектов то там практически нет) — это больше шаблонный подход с кодогенерацией. Но при этом с автоматической генерацией событий изменений в данных к их обработчикам — обновляющим эти данные. Такого нет даже в классическом ООП. Даже АОП не обеспечивает такой гибкости (хоть и немного похож на данный стиль). И, кстати, в ООП таких возможностей очень не хватает даже в обычном программировании! Это именно декларативный стиль программирования.

                        Замечание2: Применение классического подхода с размещением всей логики в JavaScript алгоритмах дизайнерам не поможет тем более — т.к. там может быть спрятано много логики дизайна именно в императивном стиле кода JavaScript — который дизайнерам воспринимать будет очень сложно. Плюс, сейчас JavaScript активно занимается генерацией и модификацией HTML страниц — что совершенно не добавляет прозрачности для анализа HTML макета дизайнеру. Так что REACT (и, надо полагать, другие фреймворки в большей степени) тут наоборот — повышает ясность и прозрачность, а не снижает её. Понятно — что для сайтов с простой логикой применять такие фреймворки нет смысла (ну разве те, которые работают по принципу, что я описал в начале данного поста) — они лишь усложнят сайт. Но если сайту требуется много JavaScript логики — то плюсы фреймворков будут очевидны, по сравнения с кодированием практически всего сайта на JavaScript).

                          0

                          Дизайнер не должен расставлять CSS, это задача фронтенд-разработчика. Дизайнер должен только проектировать интерфейс продукта и его поведение исходя из функциональных требований, не вдаваясь в конкретную реализацию, но, разумеется, с учётом особенностей и технических ограничений платформ.

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

                              Если честно, даже 10 лет назад макеты дизайнера обычно отдавали верстальщику, а затем уже «бэкендер» натягивал эту вёрстку на движок.


                              Область дизайнеров сужается до концепции интерфейса.

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

                          0
                          В последние годы JavaScript-разработчики стали определять структуру страницы в JavaScript, а не в HTML
                          Это вполне логичный архитектурный тренд. Думаю причины этому чисто когнитивного характера. Разработчику легче работать с одним типом логики, чем с несколькими. Таким образом всеми слоями можно управлять используя одну «точку опоры».

                          За счет этого снижается сложность управления, не сложность приложения, заметьте, а именно «бюрократическая» сложность. Вобщем, простое стремление к гибкости, независимости, и доступности контроля.

                          Only users with full accounts can post comments. Log in, please.