Разделение проблем под другим углом

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

Я также заранее отмечу, что я не думаю, что эти два подхода являются взаимоисключающими, и что вы, безусловно, должны знать оба, философию, лежащую в основе их, и соответствующие варианты использования.

(Re) введение в JSX

На момент написания этой статьи прошло около 4 лет с тех пор, как JSX вышел вместе с React. Если вы намеренно не проводите время под камнем, вы должны быть хотя бы в некоторой степени знакомы с этой концепцией. Это синтаксис, похожий на HTML, подобный для написания JavaScript, относящегося к виртуальной модели DOM. Давайте разберемся.

HTML-подобный означает, что его внешний внешний вид подобен разметке HTML. Вот строчка JSX:

<div id="foo">Hello world</div>

Визуальной разницы между этим и похожим HTML-кодом нет, но это все еще JavaScript. Это переводится как:

someFunc("div", {id: "foo"}, "Hello world");

По сути, это синтаксический сахар, в котором вызов функции заменяется на <>, нотация объекта изменяется на key="val", а произвольное количество различных значений может быть передано в виде дочерних «элементов».

Различия становятся более очевидными, когда мы хотим передавать значения динамически.

Тот факт, что JSX выглядит как HTML, является скорее уловкой, и в этом нет особой необходимости. Вы все еще можете писать обычные функции, если хотите. См. Примеры в Hyperscript, React.createElement и т.п.

Однако у хитроумного HTML-подобного синтаксиса есть свои преимущества. Одним из таких преимуществ является то, что он удобен для разработчиков, не использующих JavaScript, и дизайнеров UX. В моей компании мы действительно пользуемся этим в полной мере, имея UX-дизайнера, работающего над HTML и CSS. Хотя я уверен, что использование синтаксиса, подобного гиперкрипту, не станет серьезным препятствием, нельзя отрицать, что JSX снижает планку для пользователей без обучения JavaScript.

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

JSX против шаблонов

Надеюсь, вы понимаете, что JSX - это не HTML и не шаблонизатор. Но как это соотносится с механизмами шаблонов, такими как используемые в Angular и Vue.js?

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

Теперь, когда у вас есть некоторое представление о том, как работают механизмы шаблонов, вот сравнение:

JSX/JS:      javascript -> vdom
Template:    string -> javascript -> vdom

Если вы внимательно посмотрите на подпись, вы заметите, что мы можем уменьшить шаблон до:

Template:    string -> JSX/JS

И это примерно то, что происходит под капотом. Конечный результат компиляции шаблона обычно очень похож на гиперкрипт. По сравнению с JSX / hyperscript, шаблоны имеют еще один уровень косвенности, но конечный результат тот же.

Что лучше? Это зависит. Технически и по производительности они должны быть идентичными. Ваш код всегда будет скомпилирован в одно и то же.

Кроме того, если выбранный вами фреймворк не поддерживает JSX и предлагает выбор между добавлением шаблона в качестве строки шаблона в код JS и тем же самым кодом в отдельном файле .html, это это совершенно другая тема, и она ничего не имеет отношения к тому, о чем я здесь говорю.

Введите разделение проблем

Большинство споров между опытными разработчиками, когда речь идет о JSX и шаблонах, вращается вокруг разделения задач.

Разделение проблем - это термин, придуманный Эриком Дейкстрой в 1970-х годах (для справки, на момент написания этой статьи ему более 30 лет). SoC - это образ мышления, который мы применяем, чтобы разбить сложные многогранные проблемы на отдельные аспекты и решать их по отдельности.

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

CAB все еще действителен?

Если вы просто не говорите о разделении HTML, CSS и JS на отдельные файлы, да, в концепции CAB все еще есть правда. Тем не менее, есть много способов ошибиться. Наличие разных технологий в отдельных файлах не означает, что вы разделяете проблемы. Вы все еще можете делать такие глупые вещи, как:

<div class="center col2 row2">Hello world</div>

В примере фрагмента мы используем классы, чтобы избежать встроенного CSS, но мы вообще не разделяем проблемы, потому что проблема внешнего вида («центр») все еще присутствует в HTML.

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

<div class="progress-container">
  <div class="progress-bar" style="width: 20%"></div>
</div>

Ширина внутреннего элемента относительно внешнего, и мы каким-то образом устанавливаем ширину с помощью JavaScript. Это приводит к более тесной связи между всеми тремя слоями виджета, поскольку JavaScript определяет свойства CSS на узле DOM.

Одним из недостатков подхода CAB является то, что обычно трудно понять назначение каждого аспекта в отдельности. Сравните это, например:

// File 1 - templates/progress.html
<div class="progress-container">
  <div class="progress-bar" data-style="width: $val"></div>
</div>
// File 2 - components/progress.js
export default {
  val: number,
}

Если вы посмотрите на код шаблона и на компонент по отдельности, как вы узнаете, откуда взялся $val?

Компонентный подход

Компонентная модель устраняет разделение CAB и идет на разделение по функциональным направлениям. Это аргумент в пользу сплоченности внутри модуля, где все, что служит одной и той же логической цели, должно жить вместе в одном модуле.

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

Этот подход был впервые представлен React и с тех пор используется в таких фреймворках, как Vue.js, Riot, Elm, Cycle.js и т. Д.

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

// File 1 -- progress
const (val) => (
  <div class="progress-container">
    <div class="progress-bar" style={{width: val}}></div>
  </div>
);

В этом примере совершенно ясно, откуда берется val.

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

HTML-шаблоны в компонентах?

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

Окончательный вердикт

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

Однако стоит отметить одну вещь: разделение проблем не означает просто разделение вещей. Это баланс относительной независимости разных слоев (слабая связь) и удержания связанных вещей вместе (сплоченность). Вы всегда должны помнить об этом, прежде чем использовать SoC в качестве основного оружия в споре.

В следующей статье мы обсудим, должны ли мы иметь модели и представления в одном файле ... Шучу, не будем вдаваться в подробности.