Если вы используете библиотеку 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, в качестве благодарности за работу.
Поделиться с друзьями
-->

Комментарии (31)


  1. http2
    06.06.2017 09:03
    +4

    React — придумывание проблем и их героическое решение.


    1. PavelDymkov
      06.06.2017 12:48

      «Героическое решение проблем» — это называется эволюцией. Ни одна технология не появляется идеальной, но очень важно, чтобы в ней был заложен потенциал для развития. Тогда технология дорабатывается и становится лучше.


      1. jeje
        09.06.2017 12:54
        +1

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


  1. x07
    06.06.2017 09:28
    -2

    Не понимаю в чем сила реакта? Зачем нужен реакт со своим JSX, когда в JS есть шаблонные строки, благодаря которым можно писать на чистом JS и не изобретать велосипед с костылями в виде плагина который преобразует JS в HTML, а потом обратно в JS. Любое приложение на реакте выглядит, как камень обросший мхом и лишайниками в виде плагинов, без которых приложение не будет работать.
    Такое ощущение, что Реакт стал наследником JQuery. Такая же популярность и такой же огород плагинов…


    1. Veikedo
      06.06.2017 09:40
      +8

      Вы, видимо, только издалека знакомы с реактом


      1. x07
        06.06.2017 10:08
        -2

        ну так может расскажешь, в чем же, все-таки сила реакта?


        1. CyberSoft
          06.06.2017 10:26
          -1

          Почитайте о flux, который есть data flow для React, а также про virtual dom. JSX уже есть в статье


          1. x07
            06.06.2017 10:46
            -1

            а шаблонные строки вставленные в DOM это не Virtual dom? flux это подход, который абсолютно так же реализуется в простом JS.
            Veikedo Вы, видимо, только издалека знакомы с JS
            Почитайте что такое реакт


            1. Veikedo
              06.06.2017 10:54
              +4

              Шаблонные строки это не virtual dom


            1. Veikedo
              06.06.2017 10:59
              +3

              Не стоит бомбить.
              Реакт это инструмент и инструмент хороший.
              Вы, конечно, можете сделать всё вышеупомянутыми шаблонными строками.
              Но реакт всё же предоставляет удобные способы компоновки/композиции компонент, удобные способы параметризации компонент, lifecycle-хуки в конце концов.


              Не думаю, что было бы так просто и легко оперировать обычными (пусть и шаблонными) строками.


              Ну а то, что описано в статье — костыли и велосипеды и, если бы было оформлено как just for fun — был бы успех.


              1. Veikedo
                06.06.2017 11:04
                +1

                Ну и уже шаблонная фраза — react это V в mvc, с удобной инкапсуляцией логики отображения (и толькое её) в компоненте, без необходимости тащить лишнее во viewModel.


                Хотите сделать всё на шаблонных строках — пожалуйста. Но в итоге вы всё равно изобретёте реакт/ангулар/велосипед_нейм


            1. jMas
              07.06.2017 09:03
              +2

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


    1. Hazrat
      06.06.2017 11:21
      -2

      Ну так почитайте, это очень глупый вопрос


    1. Danakt
      06.06.2017 17:44
      -3

      Вы, видимо, не только плохо знакомы с реактом, но и с веб-разработкой в целом.
      На счет плагинов: Каждая задача требует индивидуальный поход и набор технологий. Не бывает так, что бы всё было из коробки и подходило ко всем задачам. И «плагины» — логическая необходимость. Или вы из тех людей, которые сплошь и рядом делают велосипеды?


      1. x07
        06.06.2017 21:01
        -2

        а вы видимо написав hello world на реакте чувствуете себя богом веб разработки.


  1. 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>


    1. faiwer
      07.06.2017 13:35

      В принципе можно сделать по аналогии с eslint-plugin-jsx-control-statements. Главное потом не забывать обновляться под новые реалии babel-я, но это беда любых плагинов.


  1. vasIvas
    06.06.2017 12:07
    +1

    Возникает ситуация, когда в js-коде пишется html, в котором пишется js-код с html.

    Но тогда html будет появляться в коде то тут, то там.

    В корне не верно. jsx, это синтаксический сахар для написания js в декларативном стиле, выполненный в xml стиле.
    Общего у «тегов» .jsx с .html лишь то, что они оба похожи на .xml
    Более корректно сравнивать .jsx с typescript или coffeescript, то есть это очередной препроцессор.


    1. raveclassic
      07.06.2017 17:03
      +1

      Вот именно, я устал уже повторять как попугай «jsx !== html».
      Мне все больше и больше становится понятно, почему разработчики elm, purescript и компании пошли другим путем, оставив обычные функции для компоновки интерфейса.


  1. PavelDymkov
    06.06.2017 19:33
    -2

    В общем, я сделал настолько фигню, что некоторые даже не смогли пройти мимо и не заминусовать статью, чтобы она не попала на главную и её никто не увидел. Содержание статьи-то нормальное, это то, что она описывает — минусовали. Обидно, но я переживу.


    1. vasIvas
      06.06.2017 19:54
      +1

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


    1. 8bitjoey
      07.06.2017 07:54
      +2

      Статья то нормальная, минусуют, думаю из-за неодобрения самого подхода. Как уже отметили выше, будут проблемы с eslint. Кроме того, управляющие теги затеряются среди разметочных, будет сложнее читать. Ну и, мое мнение, суть JSX как раз в том, чтобы свободно перемежать JS и HTML и чтобы это не выглядело плохо. И оно не выглядит.


  1. faiwer
    07.06.2017 08:13
    +1

    Уже прочитав заголовок я подумал про jsx-control-statements. Посмотрев содержимое всё ожидал увидеть сравнение. Не увидел. Вы рассматривали этот уже давно существующий плагин? Для него даже eslint-плагин есть.


    1. justboris
      07.06.2017 11:24

      Сегодня я узнал о jsx-control-statements, день прошел не зря.


      В оправдание автора могу заметить, что это решение по наивным запросам типа "forEach for React" или "If component for React" так просто не находится. Разве что нашел его упоминание где-то в issue к React


      1. c0ba
        07.06.2017 13:10
        +1

        Есть предположение, что автор тоже не смог нагуглить. Иначе зачем повторять функционал — непонятно.


        1. faiwer
          07.06.2017 13:32
          +1

          По правде говоря, синтаксис плагина мог быть и повкуснее. Скажем зачем слово condition? 8 символов на пустом месте. Зачем "задепрекейтили" <Else/>? В тернарном операторе оно есть. Даже в XSLT оно есть. Почему <Choise/> устроен именно так? Мне бы больше подошёл какой-нибудь <Switch/> с возможностью единожды указать, что мы сверяем, дабы не плодить копипасту. И т.д…


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


          1. mayorovp
            07.06.2017 13:33

            Где это вы в XSLT слово тэг <else/> видели?..


            А <Choise/>, видимо, прямо из XSLT в эту библиотеку и пришел


            1. faiwer
              07.06.2017 13:36

              Где это вы в XSLT слово тэг <else/> видели?..

              Он там называется иначе: <xsl:otherwise>.


              А <Choise/>, видимо, прямо из XSLT в эту библиотеку и пришел

              Да, вы правы. Похоже всё именно так. В нём <Choise/> есть <Otherwise/>. Тем более не понимаю, нафига <Else/> отменили.


              1. mayorovp
                07.06.2017 13:38

                В обсуждаемой тут jsx-control-statements конструкция Otherwise также присутствует.


                1. faiwer
                  07.06.2017 13:40

                  Угу, я поправился выше. Но использовать связку choose -> when + otherwise вместо ?:, либо же if condition=... + if condition=!... ? я бы не стал. Слишком громоздко и ещё 1 уровень вложенности на пустом месте.


    1. PavelDymkov
      07.06.2017 13:50
      +1

      Спасибо, я не знал о существовании этого плагина. Ну изобрел велосипед — с кем не бывает =) Статистика установок jsx-control-statements показывает, что достаточно большое количество людей используют это решение. Это меня порадовало.