Умовний рендеринг

Вашим компонентам часто буде потрібно відображати різні речі залежно від різних умов. У React ви можете умовно рендерити JSX, використовуючи синтаксис JavaScript, наприклад, оператори if, && та ? :.

  • Як повернути різні JSX залежно від умови
  • Як умовно включити або виключити фрагмент JSX
  • Поширені умовні синтаксичні скорочення, які ви зустрінете в кодових базах React

Умовно повертає JSX

Припустимо, у вас є компонент PackingList, який рендерить декілька Item, які можна позначити як упаковані або ні:

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

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </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>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </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>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </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, щоб зробити ваш код більш СУХИМ.

Умовний (тернарний) оператор (? :)

JavaScript має компактний синтаксис для запису умовного виразу - умовний оператор або "тернарний оператор".

Замість цього:

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

Можна записати так:

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

Це можна прочитати як "якщо isPacked істинно, то (?) відрендерити name + ' ✔', інакше (:) відрендерити name".

Чи є ці два приклади повністю еквівалентними?

Якщо ви прийшли з об'єктно-орієнтованого програмування, ви можете припустити, що два наведені вище приклади ледь помітно відрізняються, оскільки один з них може створити два різних "екземпляри" <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>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </ul>
    </section>
  );
}

Цей стиль добре працює у простих умовах, але використовуйте його помірковано. Якщо ваші компоненти захаращені занадто великою кількістю вкладеної умовної розмітки, подумайте про вилучення дочірніх компонентів, щоб навести лад. У React розмітка є частиною вашого коду, тому ви можете використовувати такі інструменти, як змінні та функції, щоб навести лад у складних виразах.

Логічний оператор AND (&&)

Ще один поширений ярлик, який ви можете зустріти, - це оператор логічного І (&&) JavaScript. Всередині React-компонентів він часто виникає, коли ви хочете відрендерити якийсь JSX, коли умова істинна, або не відрендерити нічого в іншому випадку. За допомогою && ви можете умовно відрендерити галочку, тільки якщо isPacked є істинною:

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>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </ul>
    </section>
  );
}

А JavaScript вираз && повертає значення своєї правої частини (у нашому випадку, галочки), якщо ліва частина (наша умова) істинна. Але якщо умова має значення false, то весь вираз стає false. React вважає false "діркою" у дереві JSX, так само як null або undefined, і не відображає нічого на його місці.

не ставити числа ліворуч від &&.

Для перевірки умови JavaScript автоматично перетворює ліву частину в булеву. Однак, якщо ліва частина має значення 0, то весь вираз отримує це значення (0), і React з радістю відобразить 0, а не нічого.

Наприклад, поширеною помилкою є написання коду типу messageCount && <p>New messages</p>. Легко припустити, що він нічого не відрендерить, коли messageCount є 0, але насправді він відрендерить сам 0!

Щоб виправити це, зробіть ліву частину логічною: messageCount > 0 && <p>New messages</p>.

Умовне присвоєння JSX змінній

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

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>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </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>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </ul>
    </section>
  );
}

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

  • У React ви керуєте логікою розгалужень за допомогою JavaScript.
  • Ви можете повернути вираз JSX умовно за допомогою інструкції if.
  • Ви можете умовно зберегти деякий JSX у змінну, а потім включити його до іншого JSX за допомогою фігурних дужок.
  • У JSX {cond ? <A /> : <B />} означає "якщо cond, рендерити <A />, інакше <B />".
  • У JSX {cond && <A />} означає "якщо cond, рендерити <A />, інакше нічого".
  • Ці комбінації є стандартними, але ви можете не використовувати їх, якщо віддаєте перевагу звичайним if.

Показувати іконку для незавершених елементів з ? :

Використовуйте умовний оператор (cond ? a : b) для виведення ❌, якщо isPacked не є істинним .

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

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </ul>
    </section>
  );
}
function Item({ name, isPacked }) {
  return (
    <li className="item">
      {name} {isPacked ? '✔' : '❌'}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          isPacked={true} 
          name="Space suit" 
        />
        <Item 
          isPacked={true} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          isPacked={false} 
          name="Photo of Tam" 
        />
      </ul>
    </section>
  );
}

Показати важливість елемента за допомогою &&

У цьому прикладі кожен Item отримує числову важливість пропу . Використовуйте оператор && для виділення "(Важливість: X)" курсивом, але лише для елементів, які мають ненульову важливість. Ваш список елементів має виглядати так:

  • Скафандр (Важливість: 9)
  • Шолом із золотим листям
  • Фотографія Там(Важливість: 6)

Не забудьте додати пробіл між двома мітками!

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

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          importance={9} 
          name="Space suit" 
        />
        <Item 
          importance={0} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          importance={6} 
          name="Photo of Tam" 
        />
      </ul>
    </section>
  );
}

Це має спрацювати:

function Item({ name, importance }) {
  return (
    <li className="item">
      {name}
      {importance > 0 && ' '}
      {importance > 0 &&
        <i>(Importance: {importance})</i>
      }
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item 
          importance={9} 
          name="Space suit" 
        />
        <Item 
          importance={0} 
          name="Helmet with a golden leaf" 
        />
        <Item 
          importance={6} 
          name="Photo of Tam" 
        />
      </ul>
    </section>
  );
}

Зверніть увагу, що ви повинні написати importance > 0 && ..., а не importance && ..., щоб якщо значення дорівнює 0, 0 не виводилося як результат!

У цьому рішенні використовуються дві окремі умови для вставки пробілу між назвою та міткою важливості. Крім того, ви можете використати фрагмент з пробілом на початку: importance > 0 && <> <i>...</i></> або додати пробіл безпосередньо всередині <i>: importance > 0 && <i> ...</i>.

Рефакторинг серії ? : до if та змінних

Цей Drink компонент використовує низку ? : умов для відображення різної інформації залежно від того, чи проп name є "tea" або "coffee". Проблема полягає в тому, що інформація про кожен напій розподілена між декількома умовами. Рефакторингуйте цей код, щоб використовувати один оператор if замість трьох умов ? :.

function Drink({ name }) {
  return (
    <section>
      <h1>{name}</h1>
      <dl>
        <dt>Part of plant</dt>
        <dd>{name === 'tea' ? 'leaf' : 'bean'}</dd>
        <dt>Caffeine content</dt>
        <dd>{name === 'tea' ? '15–70 mg/cup' : '80–185 mg/cup'}</dd>
        <dt>Age</dt>
        <dd>{name === 'tea' ? '4,000+ years' : '1,000+ years'}</dd>
      </dl>
    </section>
  );
}

export default function DrinkList() {
  return (
    <div>
      <Drink name="tea" />
      <Drink name="coffee" />
    </div>
  );
}

Після того, як ви переробили код для використання if, чи є у вас подальші ідеї, як його спростити?

Є кілька способів, але ось один з них:

function Drink({ name }) {
  let part, caffeine, age;
  if (name === 'tea') {
    part = 'leaf';
    caffeine = '15–70 mg/cup';
    age = '4,000+ years';
  } else if (name === 'coffee') {
    part = 'bean';
    caffeine = '80–185 mg/cup';
    age = '1,000+ years';
  }
  return (
    <section>
      <h1>{name}</h1>
      <dl>
        <dt>Part of plant</dt>
        <dd>{part}</dd>
        <dt>Caffeine content</dt>
        <dd>{caffeine}</dd>
        <dt>Age</dt>
        <dd>{age}</dd>
      </dl>
    </section>
  );
}

export default function DrinkList() {
  return (
    <div>
      <Drink name="tea" />
      <Drink name="coffee" />
    </div>
  );
}

Тут інформація про кожен напій згрупована разом, а не розпорошена по декількох умовах. Це полегшує додавання нових напоїв у майбутньому.

Іншим рішенням може бути повне видалення умови шляхом переміщення інформації в об'єкти:

const drinks = {
  tea: {
    part: 'leaf',
    caffeine: '15–70 mg/cup',
    age: '4,000+ years'
  },
  coffee: {
    part: 'bean',
    caffeine: '80–185 mg/cup',
    age: '1,000+ years'
  }
};

function Drink({ name }) {
  const info = drinks[name];
  return (
    <section>
      <h1>{name}</h1>
      <dl>
        <dt>Part of plant</dt>
        <dd>{info.part}</dd>
        <dt>Caffeine content</dt>
        <dd>{info.caffeine}</dd>
        <dt>Age</dt>
        <dd>{info.age}</dd>
      </dl>
    </section>
  );
}

export default function DrinkList() {
  return (
    <div>
      <Drink name="tea" />
      <Drink name="coffee" />
    </div>
  );
}