Существуют несколько способов заставить 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)
faiwer
05.02.2018 16:44+2Тем, кому не по душе использование тернарных операторов,
.map
и&&
прямо в XML-подобном коде, рекомендую попробовать этот jsx-control-statements плагин дляbabel
. + к нему ещё есть плагин дляeslint
.bustEXZ
06.02.2018 12:27Не понимаю почему плюсуют вас и коммент сверху. Конечно у каждого свои вкусы. Но использовать огород из `If` блоков вообще жесть. Как только понял и попробовал использовать && и || стало в 100 раз удобнее и читаемость возросла в разы. Это подчеркнул не только я, но и огромный пласт комьюнити.
Конечно каждый делает как удобно, но имхо юзать jsx-control-statements сверх грязно.
Конечно интересна любая критика.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/>
.
faiwer
06.02.2018 13:34+1Disclaimer: всё что ниже моё личное глубокое 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>
Таким образом я пишу все теги где появляется проблема с читаемостью аттрибутов. Громоздно, но опрятно и легко воспринимаемо. Тоже на костёр? :) Тоже грязь?
bustEXZ
06.02.2018 14:30Как делает мой преттир с почти дефолтными настройками, и моим видением оформления таких операций, это код ниже.
<some> { someBoolVar && <div> <span>1</span> </div> || <div> <span>2</span> </div> } </some>
Не вижу смысла ставить перенос строки после { и переносить } на следующую.
По поводу самого последнего кода с атрибутами, пишу точно так же, так же как и преттир подсказывает или правит автоматом)faiwer
06.02.2018 14:39Не вижу смысла ставить перенос строки после { и переносить } на следующую.
Ну дык это просто разные style-guide-ы. А ваш вариант я рассматриваю как ещё менее, читаемый, чем тот что предложил я (без jsx-control). У вас, помимо прочего, теперь начало 2-го тега и конец 1-го тега смешались в одной строке и отличаются только косой чертой. Вот как раз за подобный код я очень не люблю JSX. На мой глаз это выглядит как беспорядочное нагромождение скобок и кавычек без ясно различимой семантики. Необходимо пробежаться глазами по всему блоку, причём внимательно, чтобы понять что к чему. Т.е. "распарсить" головой всё, чтобы понять часть. И это на элементарном то примере. А почему? Потому что вложенность почти испарилась. Это чем-то сродни тому, как писать всё без отступов вовсе. Очень многим программистам на начальном этапе обучения так проще (я не шучу, мне так они объясняли).
А что в варианте с jsx-control-ом, по вашему, уменьшает читаемость, да ещё и аж в 100 раз (вы сами так написали)? Я бы отметил следующие минусы:
- код более громоздкий
- условие дублируется дважды
Не совсем понимаю, где тут разница в восприятии аж в 2 порядка.
bustEXZ
06.02.2018 15:02Слишком много кода ты дописываешь, прямо слишком много, в добавок ты ещё и зависимость тянешь. Ну не знаю на счет читаемости, тернарные я не использую, использую только && и ||, с ними проще и быстрее. Я ещё не сталкивался с тем чтобы у другого или у себя долго в код въезжать. Тут скорее дело привычки. Опять же все это я видел очень давно в best practice. Они тоже не из воздуха берутся (даже видел что так писал Гаерон).
На вкус и цвет...)faiwer
06.02.2018 15:19best practice
Ну вы же понимаете, что best practice от конторы к конторе сильно различается ;) Тут не утихают жаркие дебаты пробелы-vs-табы, египетские кавычки vs на отдельной строке, а вы про JSX. Мне вот ваш код читать будет тяжело. Конечно со временем я привыкну. Но только со временем. Думаю вам мой код читать будет до приторного просто и скучно. Но в моей системе ценностей это важнее, чем кол-во строчек. Что что, а на них я не экономлю. Совсем.
В общем подведу итоги:
- это субъективно всё, смысла спорить нет;
- фразу "читаемость возросла в разы" вы написали просто так, ибо ни в разы, ни вообще она не возросла.
Согласны? Если да, предлагаю на этом дебаты закончить :)
biziwalker
05.02.2018 21:14Стоило бы упомянуть в статье что это костыль, появившийся из-за отсутствия в js возращения значений из таких конструкций как if/else. Есть proposal с введением do-выражений, но это ИМХО ещё один костыль
k12th
06.02.2018 01:54Пока не появился JSX, возвращение значений из if/else было нужно почти никогда.
k12th
Использование тернарного оператора и
&&
в JSX — костыль, вытекающий из того, что JSX компилируется в дерево вызововReact.createElement
. Пользоваться ими можно, но читабельности коду они не добавляют, особенно когда растет вложенность. Лечится использованием компонентов высшего порядка, выносом «тупых» кусков в небольшие SFC и созданием методов типа renderTableHeader (когда выделение SFC потребует передавать state).