Условный рендеринг

Вашим компонентам нужно часто отображать различные вещи в зависимости от различных условий. В React вы можете рендерить JSX в зависимости от его условий, используя JavaScript операторы. Такие, как if, && и ? :

You will learn

  • Как вернуть разный JSX, в зависимости от условия.
  • Как в зависимости от условий добавить или убрать часть JSX.
  • Часто встречающиеся сокращения синтаксиса условных выражений, с которыми вы столкнётесь в проектах на React.

Условный возврат JSX

Допустим, у вас есть компонент PackingList, который рендерит несколько компонентов Item, который могут быть отмечены как упакованные или нет:

function Item({ name, isPacked }) {
  return <li className="item">{name}</li>;
}
export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}

Обратите внимание, что у некоторых компонентов Item проп isPacked имеет значение true, вместо значения false. Если isPacked={true}, вы хотите добавить галочку(✔) к упакованным вещам.

Можно реализовать это с помощью управляющей конструкции if/else таким образом:

if (isPacked) {
return <li className="item">{name}</li>;
}
return <li className="item">{name}</li>;

Если isPacked проп равен true, то этот код вернёт другое JSX дерево. Вместе с этим изменением, некоторые вещи получат галочку в конце:

function Item({ name, isPacked }) {
  if (isPacked) {
    return <li className="item">{name}</li>;
  }
  return <li className="item">{name}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}

Попробуйте отредактировать то, что возвращается в обоих случаях, и посмотрите, как изменится результат!

Обратите внимание, как вы создаёте разветвлённую логику с помощью операторов JavaScript if и return. В React управление потоком выполнения (например, условия) обрабатывает JavaScript.

Условно возвращаем ничего, с помощью null

В некоторых ситуациях вы вообще не захотите ничего рендерить. Например, вы не хотите показывать упакованные предметы. Компонент должен что-то возвращать. В этом случае вы можете вернуть null:

if (isPacked) {
return null;
}
return <li className="item">{name}</li>;

Если isPacked равен true, то компонент не вернёт ничего, null. В противном случае, он вернёт JSX для рендеринга.

function Item({ name, isPacked }) {
  if (isPacked) {
    return null;
  }
  return <li className="item">{name}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}

На практике возврат null из компонента не является обычным делом, поскольку это может удивить разработчика, пытающегося его рендерить. Чаще всего вы будете условно включать или исключать компонент в JSX родительского компонента. Вот как это сделать!

Условное включение JSX

В предыдущем примере вы контролировали, какое JSX дерево будет возвращено компонентом (если вообще будет!). Возможно, вы уже заметили некоторое дублирование в выводе рендера:

<li className="item">{name}</li>

очень похоже на

<li className="item">{name}</li>

Обе ветки условия возвращают <li className="item">...</li>:

if (isPacked) {
return <li className="item">{name}</li>;
}
return <li className="item">{name}</li>;

Хоть и такое дублирование не вредно, но оно может усложнить поддержку вашего кода. Что если вы захотите изменить className? Вам придётся делать это в двух местах вашего кода! В такой ситуации вы можете условно включить небольшой JSX, чтобы сделать ваш код более DRY..

Условный (тернанрый) оператор (? :)

В JavaScript есть компактный синтаксис для записи условного выражения — условный оператор или “тернарный оператор”.

Вместо этого:

if (isPacked) {
return <li className="item">{name}</li>;
}
return <li className="item">{name}</li>;

Вы можете написать это:

return (
<li className="item">
{isPacked ? name + ' ✔' : name}
</li>
);

Вы можете читать это как “если isPacked равно true, тогда (?) рендерим name + ' ✔', в противном случае (:) рендерим name.

Deep Dive

Эти два примера полностью эквивалентны?

Если вы знакомы с объектно-ориентированным программированием, вы можете предположить, что два приведенных выше примера немного отличаются друг от друга, поскольку один из них может создавать два разных “экземпляра” <li>. Но JSX-элементы не являются “экземплярами”, потому что они не хранят никакого внутреннего состояния и не являются реальными DOM-узлами. Это лёгкие описания, как чертежи. На самом деле эти два примера совершенно эквивалентны. В Сохранение и сброс состояния подробно рассказывается о том, как это работает.

Теперь предположим, что вы хотите обернуть текст завершённого элемента в другой HTML-тег, например <del>, чтобы вычеркнуть его. Вы можете добавить ещё больше переносов строк и круглых скобок, чтобы было проще вкладывать JSX в каждом из случаев:

function Item({ name, isPacked }) {
  return (
    <li className="item">
      {isPacked ? (
        <del>
          {name + ' ✔'}
        </del>
      ) : (
        name
      )}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}

Этот стиль хорошо работает для простых условий, но используйте его в меру. Если ваши компоненты становятся запутанными из-за слишком большого количества вложенной условной разметки, подумайте об выделении дочерних компонентов, чтобы навести порядок. В React разметка является частью кода, поэтому вы можете использовать такие инструменты, как переменные и функции, чтобы привести в порядок сложные выражения.

Логический оператор И(&&)

Еще одно часто встречающееся сокращение JavaScript логический оператор И (&&). Внутри React-компонентов он часто используется, когда вам нужно отрендерить JSX, когда условие true, или не рендерить ничего. С помощью && вы можете условно рендерить галочку, если isPacked равно true:

return (
<li className="item">
{name} {isPacked && '✔'}
</li>
);

Вы можете читать это как “если isPacked, тогда (&&) рендерим галочку, в противном случае — ничего не рендерим”.

Вот это в действии:

function Item({ name, isPacked }) {
  return (
    <li className="item">
      {name} {isPacked && '✔'}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}

JavaScript выражение && возвращает значение правой части (в нашем случае это галочка), если левая часть (наше условие) является true. Но если наше условие — false, тогда всё выражение становится false. React рассматривает false как “пустое место” в дереве JSX, прямо как null или undefined, и ничего не рендерит на этом месте.

Pitfall

Не ставь числа по левую сторону &&.

Для проверки условия JavaScript автоматически преобразует левую часть в булевое значение. Однако, если левая часть равна 0, то всё выражение получает это значение (0), и React будет с радостью рендерить 0 вместо ничего.

Например, распространённой ошибкой является написание кода вида messageCount && <p>Новые сообщения</p>. Легко предположить, что ничего не отрендерено, когда messageCount равно 0, но на самом деле будет рендериться 0!

Чтобы исправить это, сделайте левую часть булевым значением: messageCount > 0 && <p>Новые сообщения</p>.

Условное присвоение JSX к переменной

Когда сокращения мешают написанию понятного кода, то попробуйте использовать if оператор и переменную. Вы можете переназначать переменные, объявленные с помощью let, поэтому начните с предоставления содержимого по умолчанию, которое вы хотите отобразить, например, name:

let itemContent = name;

Используйте if оператор, чтобы переназначить JSX-выражение itemContent, если isPacked равно true:

if (isPacked) {
itemContent = name + ' ✔';
}

Фигурные скобки открывают “окно в мир JavaScript”. Вставьте переменную с помощью фигурных скобок в возвращаемое дерево JSX, вложив ранее вычисленное выражение внутрь JSX:

<li className="item">
{itemContent}
</li>

Этот стиль самый многословный, но и самый гибкий. Вот он в действии:

function Item({ name, isPacked }) {
  let itemContent = name;
  if (isPacked) {
    itemContent = name + ' ✔';
  }
  return (
    <li className="item">
      {itemContent}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}

Как и раньше, это работает не только для текста, но и для произвольного JSX:

function Item({ name, isPacked }) {
  let itemContent = name;
  if (isPacked) {
    itemContent = (
      <del>
        {name + " ✔"}
      </del>
    );
  }
  return (
    <li className="item">
      {itemContent}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}

Если вы не знакомы с JavaScript, то такое разнообразие стилей может показаться поначалу ошеломляющим. Однако их изучение поможет вам читать и писать любой JavaScript код, а не только React-компоненты! Выберите тот, который вам больше нравится, и при необходимости обратитесь к этому справочнику, если вы забудете, как работают другие.

Recap

  • В React вы управляете логикой ветвления с помощью JavaScript.
  • Вы можете условно возвращать JSX-выражение с помощью оператора if.
  • Вы можете условно сохранить JSX в переменную и затем включить её в другой JSX с помощью фигурных скобок.
  • В JSX выражение {cond ? <A /> : <B />} означает “если cond, то отрендерить <A />, иначе <B />.
  • В JSX выражение {cond && <A />} означает “если cond, то отрендерить<A />, иначе ничего”.
  • Эти сокращения являются общепринятыми, но эти сокращения необязательно использовать, если вы предпочитаете простой if.

Challenge 1 of 3:
Показать иконку для неупакованных вещей с ? :

Используйте тернарный оператор (cond ? a : b), чтобы отрендерить❌, если isPacked не равен true.

function Item({ name, isPacked }) {
  return (
    <li className="item">
      {name} {isPacked && '✔'}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Список вещей Салли Райд</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Космический скафандр" 
        />
        <Item 
          isPacked={true} 
          name="Шлем с золотым листом" 
        />
        <Item 
          isPacked={false} 
          name="Фотография Тэма" 
        />
      </ul>
    </section>
  );
}