https://habrahabr.ru/post/330172/Если вы используете библиотеку React, то наверняка используете и jsx. Конечно, это не обязательно, и можно писать только js, используя
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, в качестве благодарности за работу.