React.createElement
, но с jsx получится гораздо лаконичнее, что повышает читаемость. И всё замечательно до появления первой необходимости вывести данные в цикле. В jsx циклы не предусмотрены. Зато предусмотрена вставка js-кода. И тут вновь возникает вопрос читаемости, но теперь она значительно ухудшается. Возникает ситуация, когда в js-коде пишется html, в котором пишется js-код с html. Конечно, можно выделить html в отдельную функцию. Но тогда html будет появляться в коде то тут, то там. А хотелось бы локализовать всё в одном месте. К счастью, в современном javascript почти для любой проблемы, есть решение в виде библиотеки или плагина. Выше обозначенную проблему легко решает плагин для babel transform-react-statements.Цикл For
Принцип действия плагина прост. Плагин преобразует jsx-компоненты, такие как
<For/>
, в js-код. Допустим есть вот такой компонент:const MyComponent = props =>
<ul>
<For each="item" in={props.items}>
<li key={item.id}>
{item.text}
</li>
</For>
</ul>
После обработки плагином, получим:
var _this = this;
const MyComponent = props =>
<ul>
{Array.prototype.map.call(props.items, function (item, index) {
return <li key={item.id}>
{item.text}
</li>;
}, _this)}
</ul>;
Теперь подробнее о
For
. В первую очередь атрибут in
. Это обязательный атрибут, в котором обозначается способ получения итерируемого объекта (например, переменная). Значение должно быть выражением, т.е. заключено в фигурные скобки.Атрибут
each
задает имя переменной для каждого элемента массива. Он не является обязательным. В случае его отсутствия, элементы массива будут передаваться в качестве spread-атрибута.<div>
<For in={items}>
<Item />
</For>
</div>
Преобразуется в:
<div>
{Array.prototype.map.call(items, function (value, index) {
return <Item {...value} />;
}, this)}
</div>
Также, как видно из примеров выше, в цикле доступен номер элемента в переменной
index
. Переменную можно переименовать с помощью атрибута counter
:<For each="row" counter="rowIndex" in={rows}>
<div key={`row-${rowIndex}` className="row">
<For each="cell" counter="cellIndex" in="row">
<div key={`cell-${cellIndex}`} className="ceil">
{ cell.content }
</div>
</For>
</div>
</For>
Атрибут key
Для корректной работы React, каждый элемент в массивах должен иметь атрибут
key
. Его можно указать очевидно, как в примере выше. Другой способ — использовать атрибут key-is
. Это может немного улучшить читаемость. Также можно указать keyIs
в параметрах плагина. Тогда key
не нужно будет писать в шаблоне — логика его получения уходит в бизнес-логику..babelrc
{
plugins: [["transform-react-statements", { keyIs: "id" }]]
}
<div>
<For each="item" in={array}>
<div>{ item.value }</div>
</For>
<For each="item" in={array} key-is="someKey">
<div>{ item.value }</div>
</For>
<For each="item" in={array}>
<div key={item.getKey()}>{ item.value }</div>
</For>
</div>
Будет преобразовано в:
<div>
{Array.prototype.map.call(array, function (item) {
// key берется из параметров плагина
return <div key={item.id}>{item.value}</div>;
}, this)}
{Array.prototype.map.call(array, function (item) {
// key - из атрибута <For />
return <div key={item.someKey}>{item.value}</div>;
}, this)}
{Array.prototype.map.call(array, function (item) {
// стандартный для React способ
return <div key={item.getKey()} key={item.id}>{item.value}</div>;
}, this)}
</div>;
Условие If
Это просто альтернатива для синтаксиса:
<div>
{ condition && <Component /> }
</div>
Основная задача сделать всё в едином, html-подобном стиле. Есть два атрибута: либо
true
, либо false
, проверяющие условие на истинность или ложность соответственно. Для нескольких дочерних элементов, условие будет применяться к каждому из них:<div>
<If false={someCondition}>
<div> Текст 1 </div>
<div> Текст 2 </div>
</If>
</div>
Преобразуется в:
<div>
{ !someCondition && <div> Текст 1 </div> }
{ !someCondition && <div> Текст 2 </div> }
</div>
Switch..Case..Default
Switch
ведёт себя также, как и в javascript. У компонента Switch
есть атрибут value
, значение которого должно быть выражением в фигурных скобках, и дочерние компоненты Case
, со своими атрибутами value
. Если значение не соответствует ни одному из значений Case
, выводится блок Default
. Если блок Default
отсутствует, возвращается null
.<div>
<Switch value={x}>
<Case value={“foo”}>
<div> Text 1 </div>
</Case>
<Case value="bar">
<div> Text 2 </div>
</Case>
<Case value={1}>
<div> Text 3 </div>
</Case>
<Default>
<div> Default text </div>
</Default>
</Switch>
</div>
Component
Довольно специфическое выражение. Превращает содержимое в стрелочную функцию:
<Component>
<div> text </div>
</Component>
Преобразуется в:
props => <div> text </div>;
Соответственно, внутри
<Component/>
доступна переменная props
, которую можно переопределить через атрибут props
:<Component props="item">
<div {...item} />
</Component>
На выходе получим:
item => <div {...item} />;
Автоматическое обертывание
Предположим, есть такой компонент:
class MyComponent extends React.Component {
render() {
return <For each="item" in={props.items}>
<div key={item.id}>
{item.text}
</div>
</For>
}
}
For будет преобразован в выражение, возвращающее массив. Однако метод
render
должен вернуть React-элемент. Для того, чтобы использовать такой компонент, цикл нужно обернуть в элемент. Например так:class MyComponent extends React.Component {
render() {
return <div>
<For each="item" in={props.items}>
<div key={item.id}>
{item.text}
</div>
</For>
</div>
}
}
Но делать это не обязательно. Так как плагин сам обернёт массив в react-элемент. По умолчанию, это
<span />
. Такое поведение можно изменить, указав параметр wrapper
в настройках плагина:{
plugins: [["transform-react-statements", { wrapper: '<div class=”wrapper” />' }]]
}
Также можно отключить автоматическое обёртывание, используя значение параметра
no-wrap
:{
plugins: [["transform-react-statements", { wrapper: "no-wrap" }]]
}
Отключение и переименование выражений
Допустим, что в проекте уже есть компонент
<If />
, который вполне справляется со своей задачей. Тогда его можно отключить с помощью параметра disabled
:{
plugins: [["transform-react-statements", { disabled: ["If"]}]]
}
Или можно переименовать выражение, чтобы в jsx использовать другое имя, например,
IfStatement
:{
plugins: [["transform-react-statements", { rename: { "If": "IfStatement" } }]]
}
Код пишется для людей, поэтому так важна его читаемость. И читаемость является главной проблемой jsx, а конкретно — перемешивание двух языков. Как видно, эта проблема решается, и решается она благодаря гибкости jsx в возможности вставлять javascript-код.
P.S. Автору было бы приятно получить звездочек на github, в качестве благодарности за работу.
Комментарии (31)
x07
06.06.2017 09:28-2Не понимаю в чем сила реакта? Зачем нужен реакт со своим JSX, когда в JS есть шаблонные строки, благодаря которым можно писать на чистом JS и не изобретать велосипед с костылями в виде плагина который преобразует JS в HTML, а потом обратно в JS. Любое приложение на реакте выглядит, как камень обросший мхом и лишайниками в виде плагинов, без которых приложение не будет работать.
Такое ощущение, что Реакт стал наследником JQuery. Такая же популярность и такой же огород плагинов…Veikedo
06.06.2017 09:40+8Вы, видимо, только издалека знакомы с реактом
x07
06.06.2017 10:08-2ну так может расскажешь, в чем же, все-таки сила реакта?
CyberSoft
06.06.2017 10:26-1Почитайте о flux, который есть data flow для React, а также про virtual dom. JSX уже есть в статье
x07
06.06.2017 10:46-1а шаблонные строки вставленные в DOM это не Virtual dom? flux это подход, который абсолютно так же реализуется в простом JS.
Veikedo Вы, видимо, только издалека знакомы с JS
Почитайте что такое реактVeikedo
06.06.2017 10:59+3Не стоит бомбить.
Реакт это инструмент и инструмент хороший.
Вы, конечно, можете сделать всё вышеупомянутыми шаблонными строками.
Но реакт всё же предоставляет удобные способы компоновки/композиции компонент, удобные способы параметризации компонент, lifecycle-хуки в конце концов.
Не думаю, что было бы так просто и легко оперировать обычными (пусть и шаблонными) строками.
Ну а то, что описано в статье — костыли и велосипеды и, если бы было оформлено как just for fun — был бы успех.
Veikedo
06.06.2017 11:04+1Ну и уже шаблонная фраза — react это V в mvc, с удобной инкапсуляцией логики отображения (и толькое её) в компоненте, без необходимости тащить лишнее во viewModel.
Хотите сделать всё на шаблонных строках — пожалуйста. Но в итоге вы всё равно изобретёте реакт/ангулар/велосипед_нейм
jMas
07.06.2017 09:03+2Вы можете конечно использовать традиционные шаблоны, но как говорят, и на Си писать можно, но очень легко отстрелить себе ногу (поэтому и придумывают Rust...). С реактом то же самое — можно без него, но с ним сложнее наломать дров и сделать совсем уж тормознутый код. Как то так.
Danakt
06.06.2017 17:44-3Вы, видимо, не только плохо знакомы с реактом, но и с веб-разработкой в целом.
На счет плагинов: Каждая задача требует индивидуальный поход и набор технологий. Не бывает так, что бы всё было из коробки и подходило ко всем задачам. И «плагины» — логическая необходимость. Или вы из тех людей, которые сплошь и рядом делают велосипеды?x07
06.06.2017 21:01-2а вы видимо написав hello world на реакте чувствуете себя богом веб разработки.
justboris
06.06.2017 11:09+8ИМХО, плагин зайдет только тем, кто перешел с Ангуляра и хочет найти замену привычным ngRepeat и ngIf. Так-то и
array.map()
в JSX нормально смотрится.
Кроме того, при использовании этого babel-плагина, возникнут проблемы с линтингом:
<For each="item" in={array}> <div>{ item.value }</div> <!-- no-undef: item is not defined --> </For>
faiwer
07.06.2017 13:35В принципе можно сделать по аналогии с eslint-plugin-jsx-control-statements. Главное потом не забывать обновляться под новые реалии
babel
-я, но это беда любых плагинов.
vasIvas
06.06.2017 12:07+1Возникает ситуация, когда в js-коде пишется html, в котором пишется js-код с html.
Но тогда html будет появляться в коде то тут, то там.
В корне не верно. jsx, это синтаксический сахар для написания js в декларативном стиле, выполненный в xml стиле.
Общего у «тегов» .jsx с .html лишь то, что они оба похожи на .xml
Более корректно сравнивать .jsx с typescript или coffeescript, то есть это очередной препроцессор.raveclassic
07.06.2017 17:03+1Вот именно, я устал уже повторять как попугай «jsx !== html».
Мне все больше и больше становится понятно, почему разработчики elm, purescript и компании пошли другим путем, оставив обычные функции для компоновки интерфейса.
PavelDymkov
06.06.2017 19:33-2В общем, я сделал настолько фигню, что некоторые даже не смогли пройти мимо и не заминусовать статью, чтобы она не попала на главную и её никто не увидел. Содержание статьи-то нормальное, это то, что она описывает — минусовали. Обидно, но я переживу.
vasIvas
06.06.2017 19:54+1Какой бы статья не была, она не заслужила того абсурда, который происходил в комментариях.
На хабре спрашивать «чо такое реакт» вверх глупости, ровно как и плюсовать только риторические фразы.
8bitjoey
07.06.2017 07:54+2Статья то нормальная, минусуют, думаю из-за неодобрения самого подхода. Как уже отметили выше, будут проблемы с eslint. Кроме того, управляющие теги затеряются среди разметочных, будет сложнее читать. Ну и, мое мнение, суть JSX как раз в том, чтобы свободно перемежать JS и HTML и чтобы это не выглядело плохо. И оно не выглядит.
faiwer
07.06.2017 08:13+1Уже прочитав заголовок я подумал про
jsx-control-statements
. Посмотрев содержимое всё ожидал увидеть сравнение. Не увидел. Вы рассматривали этот уже давно существующий плагин? Для него даже eslint-плагин есть.justboris
07.06.2017 11:24Сегодня я узнал о
jsx-control-statements
, день прошел не зря.
В оправдание автора могу заметить, что это решение по наивным запросам типа "forEach for React" или "If component for React" так просто не находится. Разве что нашел его упоминание где-то в issue к React
c0ba
07.06.2017 13:10+1Есть предположение, что автор тоже не смог нагуглить. Иначе зачем повторять функционал — непонятно.
faiwer
07.06.2017 13:32+1По правде говоря, синтаксис плагина мог быть и повкуснее. Скажем зачем слово
condition
? 8 символов на пустом месте. Зачем "задепрекейтили"<Else/>
? В тернарном операторе оно есть. Даже вXSLT
оно есть. Почему<Choise/>
устроен именно так? Мне бы больше подошёл какой-нибудь<Switch/>
с возможностью единожды указать, что мы сверяем, дабы не плодить копипасту. И т.д…
Но даже такой плагин мне показался куда приятнее глазу, чем адская смесь декларативного HTML и императивного JS, с примесью функциональщины (все эти
.map
-ы). Видимо годы XSLT меня избаловали.mayorovp
07.06.2017 13:33Где это вы в XSLT слово тэг
<else/>
видели?..
А
<Choise/>
, видимо, прямо из XSLT в эту библиотеку и пришелfaiwer
07.06.2017 13:36Где это вы в XSLT слово тэг <else/> видели?..
Он там называется иначе:
<xsl:otherwise>
.
А
<Choise/>
, видимо, прямо из XSLT в эту библиотеку и пришелДа, вы правы. Похоже всё именно так. В нём
<Choise/>
есть<Otherwise/>
. Тем более не понимаю, нафига<Else/>
отменили.mayorovp
07.06.2017 13:38В обсуждаемой тут jsx-control-statements конструкция Otherwise также присутствует.
faiwer
07.06.2017 13:40Угу, я поправился выше. Но использовать связку
choose -> when + otherwise
вместо?:
, либо жеif condition=... + if condition=!...
? я бы не стал. Слишком громоздко и ещё 1 уровень вложенности на пустом месте.
PavelDymkov
07.06.2017 13:50+1Спасибо, я не знал о существовании этого плагина. Ну изобрел велосипед — с кем не бывает =) Статистика установок
jsx-control-statements
показывает, что достаточно большое количество людей используют это решение. Это меня порадовало.
http2
React — придумывание проблем и их героическое решение.
PavelDymkov
«Героическое решение проблем» — это называется эволюцией. Ни одна технология не появляется идеальной, но очень важно, чтобы в ней был заложен потенциал для развития. Тогда технология дорабатывается и становится лучше.
jeje
Но это все мы уже видели в первом ангуляре. Переменные задаются через параметры, не трекаются редакторами по человечески, при более сложной логике приходится все так же возвращаться к обычному map.