Всем привет! Вы читаете перевод статьи Donavon West “Conditional Rendering in React using Ternaries and Logical AND”

image

Существуют несколько способов заставить React рендерить то, что вам нужно. Вы можете использовать традиционный оператор if или switch. В этой статье мы рассмотрим этим способам альтернативу. Будьте осторожны: она несет некоторые сюрпризами, с которыми стоит быть поаккуратнее.

Тернарные операторы vs if/else


Давайте представим, что наш компонент получает свойство name. Если строка не пустая, то мы выведем приветствие, а иначе мы предложим пользователю зарегистрироваться.

Это можно сделать с помощью компонента-функции без состояний (ориг. SFC — Stateless Function Component):

const MyComponent = ({ name }) => {
  if (name) {
    return (
      <div className="hello">
        Привет, {name}
      </div>
    );
  }
  return (
    <div className="hello">
      Пожалуйста, войдите в ваш аккаунт
    </div>
  );
};

Здесь все довольно понятно. Но мы можем лучше! Вот тот же пример, написанный с тернарными операторами:

const MyComponent = ({ name }) => (
  <div className="hello">
    {name ? `Привет, ${name}` : 'Пожалуйста, войдите в ваш аккаунт'}
  </div>
);

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

Тернарные операторы vs логическое “И”


Как видите, тернарные операторы удобны в условиях if/else. Но что, если у нас только if?

Приведем другой пример. Теперь если isPro у нас true, мы выводим надпись (на картинке после — кубок). Также мы выводим количество звезд, если их больше 0. Можно сделать так:

const MyComponent = ({ name, isPro, stars}) => (
  <div className="hello">
    <div>
      Hello {name}
      {isPro ? 'Проооофи' : null}
    </div>
    {stars ? (
      <div>
        Stars:{'* '.repeat(stars)}
      </div>
    ) : null}
  </div>
);

Заметьте, что else возвращает null. Поскольку тернарный оператор ожидает else, то этого не избежать.

Для одного лишь if существует нечто более приятное, логическое “И”:

const MyComponent = ({ name, isPro, stars}) => (
  <div className="hello">
    <div>
      Hello {name}
      {isPro && 'Проооофи'}
    </div>
    {stars && (
      <div>
        Stars:{'* '.repeat(stars)}
      </div>
    )}
  </div>
);

Не сильно-то отличается, однако теперь у нас нет надоедливого : null (то есть условия else). Все, по идее, должно отрендериться как и раньше.



Что это? Что произошло с Джоном? Вместо пустоты у нас откуда-то взялся 0. Вот это и есть сюрприз, о котором говорилось раньше.

Если обратиться к MDN, то логическое “И” (&&):

expr1 && expr2
Возвращает expr1, если может быть преобразовано в false; иначе возвращает expr2. Причем, при использовании с логическими значениями (boolean), && вернет true, если оба значения true; иначе вернет false.
Ну ладно. Прежде чем вы пойдете глотать антидепрессанты, позвольте объяснить.

В нашем случае, expr1 — это stars, у которого значение 0. Поскольку ноль — ложь, то и возвращает изначальное состояние. Видите, не так уж и сложно.

Можно переписать по-человечески:

Если expr1 ложно, то вернет expr1; иначе вернет expr2.
Поэтому, когда используете логическое “И” с небулевым значением, то нужно что-то сделать с ложью, чтобы React это не рендерил.

Рассмотрим несколько решений:

{!!stars && (
  <div>
    {'* '.repeat(stars)}
  </div>
)}

Заметьте знак !! перед stars — он означает двойное отрицание. То есть, если stars — 0, то !!stars уже false. А именно это нам и нужно.

Если вам не особо по душе двойное отрицание, то вот еще пример:

{Boolean(stars) && (

Функция Boolean() банально преобразует переменную в true или false.

Можно сравнить значение переменной с 0 — результат тот же:

{stars > 0 && (

Кстати, пустые строки ждет участь чисел. Но поскольку отрендерится пустая строка как ‘’, то вы ее и не заметите. Если же вы перфекционист, можете обработать строки как числа в примерах выше.

Другое решение


Когда вы увеличите ваше веб-приложение, будет удобно разделить переменные и создать одну shouldRenderStars с условием. Тогда вы сразу будете работать с логическими значениями в логическом “И”:

const shouldRenderStars = stars > 0;
return (
  <div>
    {shouldRenderStars && (
      <div>
        {'* '.repeat(stars)}
      </div>
    )}
  </div>
);

В будущем, когда бизнес потребует проверять, скажем, на авторизацию, наличие собаки, предпочтения в пиве, нужно будет просто немного изменить shouldRenderStars, а то, что будет возвращено, не менять:

const shouldRenderStars = 
  stars > 0 && loggedIn && pet === 'dog' && beerPref === 'light`;
return (
  <div>
    {shouldRenderStars && (
      <div>
        {'* '.repeat(stars)}
      </div>
    )}
  </div>
);

Заключение


Я один из тех, кто считает, что нужно пользоваться всеми функциями вашего языка программирование. Для JavaScript это означает использование по назначению тернарных операторов, условий if/else и логических “И” для простой замены if.

Надеюсь, вам была полезна эта статья. И обязательно напишите: как вы используете описанное выше? Спасибо за внимание.

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


  1. k12th
    05.02.2018 13:20
    +1

    Использование тернарного оператора и && в JSX — костыль, вытекающий из того, что JSX компилируется в дерево вызовов React.createElement. Пользоваться ими можно, но читабельности коду они не добавляют, особенно когда растет вложенность. Лечится использованием компонентов высшего порядка, выносом «тупых» кусков в небольшие SFC и созданием методов типа renderTableHeader (когда выделение SFC потребует передавать state).


  1. faiwer
    05.02.2018 16:44
    +2

    Тем, кому не по душе использование тернарных операторов, .map и && прямо в XML-подобном коде, рекомендую попробовать этот jsx-control-statements плагин для babel. + к нему ещё есть плагин для eslint.


    1. bustEXZ
      06.02.2018 12:27

      Не понимаю почему плюсуют вас и коммент сверху. Конечно у каждого свои вкусы. Но использовать огород из `If` блоков вообще жесть. Как только понял и попробовал использовать && и || стало в 100 раз удобнее и читаемость возросла в разы. Это подчеркнул не только я, но и огромный пласт комьюнити.
      Конечно каждый делает как удобно, но имхо юзать jsx-control-statements сверх грязно.
      Конечно интересна любая критика.


      1. faiwer
        06.02.2018 13:14

        Но использовать огород из If блоков вообще жесть

        Это из XSLT. Там и похлеще конструкции. XLST это язык преобразования одних XML документов в другие по ряду правил. Декларативный. Очень многие конторы раньше его использовали в качестве шаблонизатора.


        Как только понял и попробовал использовать && и || стало в 100 раз удобнее и читаемость возросла в разы.

        Это всё вкусовщина. Кому-то нравятся .map-ы внутри XML блоков, а у кого-то глаза кровью заливаются от столько чудного перемешивания совершенно непохожих синтаксисов. Кто-то пишет вместо всех if-ов в коде (даже обычном JS) конструкции с &&. Кому-то по вкусу вложенные 10-кратно друг в друга тернарные операторы (?:). Кто-то использует ~~number для indexOf. Кому-то даже !! по глазам бьёт. Кто-то приемлет хаки с +string, а кто-то требует принудительных Number(str). Кто-то использует круглые скобки для (el) => {} стрелочных функций, а кому-то они мешают читать код. Кто-то не переносит на дух египетские кавычки, а кому-то только с ними и комфортно.


        Лично на мой вкус, использование jsx-control-statements делает код очень однородным и наглядным. Избавляет его от множества неестественных для XML символов и конструкций. Т.е. на мой вкус он как раз избавляет код от грязи. А обычный JSX код читать просто больно. Единственное, что я бы поменял, это длинное слово condition на что-нибудь попроще и убрал deprecated статус в <Else/>.


      1. faiwer
        06.02.2018 13:34
        +1

        Disclaimer: всё что ниже моё личное глубокое IMHO и я никому не навязываю свою точку зрения:


        <some>
        {
          someBoolVar ? <div>
            <span>1</span>
          </div>
          : <div>
            <span>2</span>
          </div>
        }
        </some>

        Вот скажем типовой код без ^. Точнее как бы я его написал. Опустил аттрибуты и прочую логику для простоты и наглядности. Что меня тут напрягает? Первое это символы ? и :. Они в отличие от обычного кода легко теряются на фоне разнообразного JSX синтаксиса. Их нужно прямо выискивать. Символ ? идёт в середине строки, а : вначале. Очень не наглядно. Мой внутренний перфекционизм прямо вздрагивает. Оба <div/>-а также начинаются в разных местах. Второй вообще так после :, что, имхо выглядит реально дико. Как-будто какая-то наскальная живопись. Т.к. я не использую египетских кавычек нигде, то и две строки на {} ушло. В целом я воспринимают такой код как ребус. А ведь реальный код бывает сильно сложнее. Всякие аттрибуты вычисляемые, сложное содержимое, условие может быть сложнее, чем просто bool-переменная. И начинается малочитаемый ад из нагромождения всех видов скобок и кавычек, и даже подстветка синтаксиса не выручает.


        В случае jsx-control… выглядеть это будет так:


        <some>
          <If condition={someBoolVar}>
            <div>
              <span>1</span>
            </div>
          </If>
          <If condition={!someBoolVar}>
            <div>
              <span>2</span>
            </div>
          </If>
        </some>

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


        А что бы вы сказали, на такой вид?


        <someTag
          attr1={1}
          attr2="2"
        >
          inner
        </someTag>

        Таким образом я пишу все теги где появляется проблема с читаемостью аттрибутов. Громоздно, но опрятно и легко воспринимаемо. Тоже на костёр? :) Тоже грязь?


        1. bustEXZ
          06.02.2018 14:30

          Как делает мой преттир с почти дефолтными настройками, и моим видением оформления таких операций, это код ниже.

          <some>
          { someBoolVar && <div>
              <span>1</span>
            </div> || <div>
              <span>2</span>
            </div> }
          </some>
          


          Не вижу смысла ставить перенос строки после { и переносить } на следующую.

          По поводу самого последнего кода с атрибутами, пишу точно так же, так же как и преттир подсказывает или правит автоматом)


          1. faiwer
            06.02.2018 14:39

            Не вижу смысла ставить перенос строки после { и переносить } на следующую.

            Ну дык это просто разные style-guide-ы. А ваш вариант я рассматриваю как ещё менее, читаемый, чем тот что предложил я (без jsx-control). У вас, помимо прочего, теперь начало 2-го тега и конец 1-го тега смешались в одной строке и отличаются только косой чертой. Вот как раз за подобный код я очень не люблю JSX. На мой глаз это выглядит как беспорядочное нагромождение скобок и кавычек без ясно различимой семантики. Необходимо пробежаться глазами по всему блоку, причём внимательно, чтобы понять что к чему. Т.е. "распарсить" головой всё, чтобы понять часть. И это на элементарном то примере. А почему? Потому что вложенность почти испарилась. Это чем-то сродни тому, как писать всё без отступов вовсе. Очень многим программистам на начальном этапе обучения так проще (я не шучу, мне так они объясняли).


            А что в варианте с jsx-control-ом, по вашему, уменьшает читаемость, да ещё и аж в 100 раз (вы сами так написали)? Я бы отметил следующие минусы:


            • код более громоздкий
            • условие дублируется дважды

            Не совсем понимаю, где тут разница в восприятии аж в 2 порядка.


            1. bustEXZ
              06.02.2018 15:02

              Слишком много кода ты дописываешь, прямо слишком много, в добавок ты ещё и зависимость тянешь. Ну не знаю на счет читаемости, тернарные я не использую, использую только && и ||, с ними проще и быстрее. Я ещё не сталкивался с тем чтобы у другого или у себя долго в код въезжать. Тут скорее дело привычки. Опять же все это я видел очень давно в best practice. Они тоже не из воздуха берутся (даже видел что так писал Гаерон).
              На вкус и цвет...)


              1. faiwer
                06.02.2018 15:19

                best practice

                Ну вы же понимаете, что best practice от конторы к конторе сильно различается ;) Тут не утихают жаркие дебаты пробелы-vs-табы, египетские кавычки vs на отдельной строке, а вы про JSX. Мне вот ваш код читать будет тяжело. Конечно со временем я привыкну. Но только со временем. Думаю вам мой код читать будет до приторного просто и скучно. Но в моей системе ценностей это важнее, чем кол-во строчек. Что что, а на них я не экономлю. Совсем.


                В общем подведу итоги:


                • это субъективно всё, смысла спорить нет;
                • фразу "читаемость возросла в разы" вы написали просто так, ибо ни в разы, ни вообще она не возросла.

                Согласны? Если да, предлагаю на этом дебаты закончить :)


                1. bustEXZ
                  06.02.2018 15:23
                  +1

                  Соглашусь. :)


  1. biziwalker
    05.02.2018 21:14

    Стоило бы упомянуть в статье что это костыль, появившийся из-за отсутствия в js возращения значений из таких конструкций как if/else. Есть proposal с введением do-выражений, но это ИМХО ещё один костыль


    1. k12th
      06.02.2018 01:54

      Пока не появился JSX, возвращение значений из if/else было нужно почти никогда.