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

Вашим компонентам нужно часто отображать различные вещи в зависимости от различных условий. В 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. Вы хотите добавить галочку (✔) к упакованным вещам, если if 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>
);

Вы можете читать это как “if 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, или не рендерить ничего. С &&, вы можете исходя из условия зарендерить галочку, if isPacked это true:

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

Вы можете читать это как “if 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, и React не рендерит ничего на этом месте.

Pitfall

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

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

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

Чтобы исправить это, сделайте левую часть булевым значением (true/false): messageCount > 0 && <p>New messages</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 />} означает “if cond, рендери <A />, в противном случае <B />.
  • В JSX, {cond && <A />} означает “if cond, рендери <A />, иначе ничего”.
  • Эти сокращения являются общепринятыми, но вы не обязаны их использовать, если предпочитаете простые выражения. if.

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

Используй тернарный оператор (cond ? a : b) чтобы зарендерить ❌ if 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>
  );
}