From Html To Project

JSX — розмітка всередині TypeScript

Практичне використання TypeScript-виразів у JSX — фігурні дужки як вікно у TypeScript, динамічне формування атрибутів, обчислювані стилі, умовний рендеринг через тернарні оператори та логічні вирази, робота з масивами та об'єктами у розмітці.

JSX — розмітка всередині TypeScript

🎯 Мета лекції

  • Опанувати використання TypeScript-виразів усередині JSX через фігурні дужки.
  • Навчитися динамічно формувати атрибути, класи та стилі на основі даних.
  • Застосовувати умовний рендеринг через тернарні оператори та логічні вирази.
  • Зрозуміти обмеження JSX: що можна вставляти через {}, а що ні.
  • Практикувати роботу з масивами, об'єктами та обчислювальними виразами у розмітці.

🔑 Ключові терміни

  • Вираз (expression) — фрагмент коду TypeScript, що повертає значення: змінна, виклик функції, тернарний оператор.
  • Інструкція (statement) — команда TypeScript, що виконує дію, але не повертає значення: if, for, while, const x = 5;.
  • Фігурні дужки {} — механізм вставки TypeScript-виразів у JSX-розмітку.
  • Умовний рендеринг (conditional rendering) — відображення різних UI-елементів залежно від умови.
  • Обчислювані властивості (computed properties) — динамічне формування значень атрибутів на основі даних.

🔗 Офіційний референс React:
JavaScript у JSX із фігурними дужками · Умовний рендеринг


Фігурні дужки: вікно у TypeScript-світ

JSX — це розширення TypeScript, що дозволяє писати HTML-подібну розмітку прямо у коді. Але що робити, коли потрібно вставити динамічні дані — значення змінної, результат обчислення, виклик функції?

Для цього у JSX існує спеціальний синтаксис фігурних дужок {}. Усередині них можна написати будь-який TypeScript-вираз, і React автоматично обчислить його значення та вставить у розмітку.

Базовий приклад: вставка змінних

function Greeting() {
  const userName: string = "Олександр";
  const currentYear: number = 2026;

  return (
    <div>
      <h1>Привіт, {userName}!</h1>
      <p>Поточний рік: {currentYear}</p>
    </div>
  );
}

Результат у DOM:

<div>
  <h1>Привіт, Олександр!</h1>
  <p>Поточний рік: 2026</p>
</div>

React виконує вираз усередині {}, отримує результат та вставляє його як текстовий вузол.

Що таке вираз у TypeScript?Вираз (expression) — це фрагмент коду, що обчислюється до певного значення. Приклади виразів:
  • Змінна: userName
  • Число: 42
  • Рядок: "Привіт"
  • Арифметика: 5 + 3
  • Виклик функції: Math.random()
  • Тернарний оператор: isActive ? "Так" : "Ні"
  • Логічні оператори: isLoggedIn && <Menu />
Інструкції (statements) не є виразами і не можуть бути вставлені через {}:
  • if (...) { ... }
  • for (...) { ... }
  • while (...) { ... }
  • const x = 5;

Обчислювані вирази

Усередині {} можна не лише використовувати змінні, а й виконувати обчислення:

function PriceDisplay() {
  const basePrice: number = 1000;
  const tax: number = 0.20; // 20% податок
  const discount: number = 0.10; // 10% знижка

  const finalPrice: number = basePrice * (1 + tax) * (1 - discount);

  return (
    <div>
      <p>Базова ціна: {basePrice} грн</p>
      <p>Податок (20%): {basePrice * tax} грн</p>
      <p>Знижка (10%): {basePrice * discount} грн</p>
      <p><strong>Підсумкова ціна: {finalPrice.toFixed(2)} грн</strong></p>
    </div>
  );
}

TypeScript перевіряє типи:

const price: number = 1000;
const label: string = "Ціна: " + price; // ✅ OK

// ❌ TypeScript покаже помилку:
// const invalid: number = "текст" + 5;

Виклики функцій

function formatDate(date: Date): string {
  return date.toLocaleDateString("uk-UA", {
    year: "numeric",
    month: "long",
    day: "numeric",
  });
}

function DateDisplay() {
  const today: Date = new Date();

  return (
    <div>
      <p>Сьогодні: {formatDate(today)}</p>
      <p>Час: {today.toLocaleTimeString("uk-UA")}</p>
    </div>
  );
}

React викличе функцію formatDate(today), отримає рядок і вставить його як текст.

Чисті функції у виразах JSX:Функції, які викликаються всередині {}, мають бути чистими — повертати однаковий результат для однакових вхідних даних без побічних ефектів (не змінювати зовнішній стан, не викликати мережеві запити).Якщо функція має побічні ефекти (наприклад, console.log), вона виконається при кожному рендері, що може призвести до несподіваної поведінки.

Динамічне формування атрибутів

Фігурні дужки можна використовувати не лише для текстового контенту, а й для значень атрибутів.

Динамічні src, alt, href

function ProductImage() {
  // Локальні змінні всередині компонента
  const imageUrl: string = "/images/laptop.jpg";
  const productName: string = "Ноутбук Lenovo ThinkPad";

  return (
    <img 
      src={imageUrl} 
      alt={`Зображення товару: ${productName}`}
      width={300}
      height={300}
    />
  );
}

Результат у DOM:

<img 
  src="/images/laptop.jpg" 
  alt="Зображення товару: Ноутбук Lenovo ThinkPad"
  width="300"
  height="300"
/>

Динамічні класи через className

Найпоширеніший випадок — зміна класу залежно від даних:

function StatusMessage() {
  // Локальні змінні
  const isError: boolean = false;
  const messageType: string = isError ? "error" : "success";
  
  // Формуємо рядок класів динамічно
  const messageClass: string = `message message-${messageType}`;

  return (
    <div className={messageClass}>
      {isError ? "Сталася помилка" : "Операція успішна"}
    </div>
  );
}

// Результат: <div class="message message-success">Операція успішна</div>

Альтернативний підхід через умовний вираз:

function ToggleButton() {
  const isActive: boolean = true;
  const label: string = "Натисни мене";

  return (
    <button className={isActive ? "btn btn-active" : "btn btn-inactive"}>
      {label}
    </button>
  );
}

Складніший приклад із кількома умовами:

function StatusBadge() {
  const status: "success" | "warning" | "error" = "success";
  const size: "small" | "medium" | "large" = "medium";

  const baseClass = "badge";
  const statusClass = `badge-${status}`;
  const sizeClass = `badge-${size}`;

  // Об'єднуємо класи
  const className = [baseClass, statusClass, sizeClass].join(" ");

  return <span className={className}>{status.toUpperCase()}</span>;
}

// Результат: <span class="badge badge-success badge-medium">SUCCESS</span>
Бібліотека clsx для складних класів:Для проєктів із великою кількістю умовних класів рекомендується бібліотека clsx:
import clsx from "clsx";

function Button({ variant, size, disabled }: ButtonProps) {
  return (
    <button 
      className={clsx(
        "btn",
        `btn-${variant}`,
        `btn-${size}`,
        { "btn-disabled": disabled }
      )}
    >
      Кнопка
    </button>
  );
}

Динамічні інлайнові стилі

Атрибут style у JSX приймає об'єкт із CSS-властивостями у camelCase:

function ProgressBar() {
  // Локальні дані
  const progress: number = 75; // від 0 до 100
  const color: string = "#10b981";

  const barStyle: React.CSSProperties = {
    width: `${progress}%`,
    height: "20px",
    backgroundColor: color,
    borderRadius: "10px",
    transition: "width 0.3s ease",
  };

  const containerStyle: React.CSSProperties = {
    width: "100%",
    backgroundColor: "#e2e8f0",
    borderRadius: "10px",
  };

  return (
    <div style={containerStyle}>
      <div style={barStyle} />
    </div>
  );
}

Результат у DOM:

<div style="width: 100%; background-color: rgb(226, 232, 240); border-radius: 10px;">
  <div style="width: 75%; height: 20px; background-color: rgb(16, 185, 129); border-radius: 10px; transition: width 0.3s ease;"></div>
</div>
Обмеження інлайнових стилів:
  1. Немає псевдокласів: :hover, :focus, :active не працюють у style.
  2. Немає медіа-запитів: @media не працює у style.
  3. Погана продуктивність: Інлайнові стилі створюють новий об'єкт при кожному рендері (якщо не винесені у константу), що може уповільнити великі списки.
Використовуйте інлайнові стилі лише для:
  • Динамічних значень (прогрес-бари, анімації, позиціонування).
  • Швидких прототипів.
Для статичних стилів використовуйте CSS Modules або Tailwind CSS.

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

Одна з найпотужніших можливостей JSX — умовний рендеринг: відображення різних елементів залежно від умови.

Тернарний оператор ? :

Найпоширеніший спосіб умовного рендерингу:

function Message() {
  const isError: boolean = false;
  const text: string = "Операція виконана успішно";

  return (
    <div className={isError ? "message-error" : "message-success"}>
      {isError ? "❌" : "✅"} {text}
    </div>
  );
}

Логічний оператор && для умовного відображення

Якщо потрібно або показати елемент, або нічого, використовуйте &&:

function Notification() {
  const hasNewMessages: boolean = true;
  const messageCount: number = 5;

  return (
    <div>
      <h2>Сповіщення</h2>
      {hasNewMessages && (
        <p className="notification">
          У вас {messageCount} нових повідомлень!
        </p>
      )}
    </div>
  );
}

// Якщо hasNewMessages === true, показується <p>
// Якщо hasNewMessages === false, нічого не показується

Як працює &&:

TypeScript/JavaScript оцінює вираз зліва направо:

true && <p>Показується</p>   // Результат: <p>Показується</p>
false && <p>Не показується</p> // Результат: false (React не рендерить false)
Пастка && із числами:
const count = 0;

// ❌ НЕПРАВИЛЬНО — у DOM з'явиться "0"!
{count && <p>{count} повідомлень</p>}

// ✅ ПРАВИЛЬНО — явна перевірка на булеве значення
{count > 0 && <p>{count} повідомлень</p>}
Чому? У JavaScript 0 є falsy, але React рендерить число 0 як текст. Тому при count = 0 вираз повертає 0, а не false.Правило: Ліва частина &&завжди має бути булевим значенням (true / false).

Складний умовний рендеринг через змінні

Для складніших умов виносьте логіку у змінні:

function UserDashboard() {
  const role: "admin" | "user" | "guest" = "admin";
  
  let content: React.ReactNode;

  if (role === "admin") {
    content = (
      <div className="admin-panel">
        <h2>Панель адміністратора</h2>
        <button>Управління користувачами</button>
        <button>Налаштування системи</button>
      </div>
    );
  } else if (role === "user") {
    content = (
      <div className="user-panel">
        <h2>Панель користувача</h2>
        <button>Мій профіль</button>
        <button>Мої замовлення</button>
      </div>
    );
  } else {
    content = (
      <div className="guest-panel">
        <h2>Вітаємо, гість!</h2>
        <button>Увійти</button>
        <button>Зареєструватися</button>
      </div>
    );
  }

  return <div className="dashboard">{content}</div>;
}
React Умовний рендеринг залежно від ролі
Файли проєкту
src
App.tsx
App.css
localhost:3000

Збирання React застосунку…


Що НЕ можна вставляти через {}

Фігурні дужки приймають лише вирази, а не інструкції.

❌ Не працює: if / else

// ❌ НЕПРАВИЛЬНО — if є інструкцією, а не виразом
function Message({ isError }: { isError: boolean }) {
  return (
    <div>
      {if (isError) {
        return <p>Помилка!</p>;
      } else {
        return <p>Успіх!</p>;
      }}
    </div>
  );
}

Рішення: Тернарний оператор або змінна:

// ✅ Тернарний оператор
{isError ? <p>Помилка!</p> : <p>Успіх!</p>}

// ✅ Змінна
const message = isError ? <p>Помилка!</p> : <p>Успіх!</p>;
return <div>{message}</div>;

❌ Не працює: цикли for / while

// ❌ НЕПРАВИЛЬНО — for є інструкцією
<ul>
  {for (let i = 0; i < 5; i++) {
    <li>{i}</li>
  }}
</ul>

Рішення: .map() для масивів:

// ✅ Використовуйте .map()
<ul>
  {[0, 1, 2, 3, 4].map(i => <li key={i}>{i}</li>)}
</ul>

❌ Не працює: оголошення змінних

// ❌ НЕПРАВИЛЬНО — const є інструкцією
<div>
  {const x = 5;}
  {x + 10}
</div>

Рішення: Виносьте оголошення за межі JSX:

// ✅ Оголошуйте змінні ДО return
const x = 5;
return <div>{x + 10}</div>;
Правило великого пальця:Якщо ви можете написати щось після= у присвоєнні — це вираз:
const result = 5 + 3;        // ✅ Вираз: 5 + 3
const result = Math.max(a, b); // ✅ Вираз: виклик функції
const result = isActive ? "Так" : "Ні"; // ✅ Вираз: тернарний оператор

// Інструкції не можна присвоїти:
const result = if (true) { }; // ❌ Синтаксична помилка
const result = for (;;) { };  // ❌ Синтаксична помилка

Робота з масивами: метод .map()

Найпоширеніший випадок використання виразів у JSX — рендеринг списків через .map().

Базовий приклад

function FruitList() {
  const fruits: string[] = ['Яблуко', 'Банан', 'Апельсин', 'Груша'];

  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  );
}

Результат у DOM:

<ul>
  <li>Яблуко</li>
  <li>Банан</li>
  <li>Апельсин</li>
  <li>Груша</li>
</ul>

Рендеринг масиву об'єктів

interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

function ProductList() {
  const products: Product[] = [
    { id: 1, name: 'Ноутбук', price: 25000, inStock: true },
    { id: 2, name: 'Миша', price: 500, inStock: true },
    { id: 3, name: 'Клавіатура', price: 1200, inStock: false },
  ];

  return (
    <div className="product-list">
      {products.map(product => (
        <div key={product.id} className="product-card">
          <h3>{product.name}</h3>
          <p className="price">{product.price} грн</p>
          {product.inStock ? (
            <span className="in-stock">В наявності</span>
          ) : (
            <span className="out-of-stock">Немає в наявності</span>
          )}
        </div>
      ))}
    </div>
  );
}
Важливість key:React використовує key для ідентифікації елементів у списках. Завжди використовуйте унікальний ідентифікатор (наприклад, id із бази даних), а не індекс масиву.Чому індекс як key небезпечний:
// ❌ НЕБЕЗПЕЧНО
{items.map((item, index) => <div key={index}>{item.name}</div>)}
Якщо список змінюється (додаються, видаляються або переміщуються елементи), індекси зміщуються, і React може неправильно асоціювати стан із елементами.Правильно:
// ✅ Використовуйте унікальний ID
{items.map(item => <div key={item.id}>{item.name}</div>)}
Детальніше про key ми поговоримо у лекції про списки.

Фільтрація та сортування

Оскільки .map() — це вираз, ви можете ланцюжити методи масивів:

interface Task {
  id: number;
  title: string;
  completed: boolean;
  priority: number;
}

function TaskList() {
  const tasks: Task[] = [
    { id: 1, title: 'Зробити презентацію', completed: false, priority: 1 },
    { id: 2, title: 'Відповісти на листи', completed: true, priority: 3 },
    { id: 3, title: 'Зателефонувати клієнту', completed: false, priority: 2 },
  ];

  return (
    <div>
      <h2>Активні завдання (відсортовані за пріоритетом)</h2>
      <ul>
        {tasks
          .filter(task => !task.completed)  // Лише незавершені
          .sort((a, b) => a.priority - b.priority)  // Сортування за пріоритетом
          .map(task => (
            <li key={task.id}>
              <strong>[Пріоритет {task.priority}]</strong> {task.title}
            </li>
          ))}
      </ul>
    </div>
  );
}

Робота з об'єктами: динамічний доступ до властивостей

TypeScript дозволяє динамічно звертатися до властивостей об'єктів через квадратні дужки:

interface TranslationKey {
  greeting: string;
  farewell: string;
  thankYou: string;
}

const translations: Record<string, TranslationKey> = {
  uk: {
    greeting: 'Привіт',
    farewell: 'До побачення',
    thankYou: 'Дякую',
  },
  en: {
    greeting: 'Hello',
    farewell: 'Goodbye',
    thankYou: 'Thank you',
  },
};

interface GreetingProps {
  language: 'uk' | 'en';
  messageType: keyof TranslationKey;
}

function Greeting({ language, messageType }: GreetingProps) {
  const message: string = translations[language][messageType];

  return <h1>{message}</h1>;
}

// Використання
<Greeting language="uk" messageType="greeting" />  // Привіт
<Greeting language="en" messageType="thankYou" />  // Thank you

Практика: динамічна картка користувача

React Динамічна картка з обчислювальними виразами
Файли проєкту
src
App.tsx
App.css
localhost:3000

Збирання React застосунку…

Що демонструє цей приклад:

  1. Обчислювані значення: fullName, initials, membershipDays, isNewUser.
  2. Динамічні класи: cardClass, statusClass.
  3. Динамічні стилі: avatarStyle з градієнтами.
  4. Умовний рендеринг: {isNewUser && ...}, {user.isActive ? ... : ...}.
  5. Форматування дат: .toLocaleDateString().
  6. Робота з масивами: .map() для рендерингу списку користувачів.

Підсумок

✅ Що ми опанували

  • Фігурні дужки {} дозволяють вставляти TypeScript-вирази у JSX.
  • Вирази (змінні, виклики функцій, тернарні оператори) можна використовувати через {}.
  • Інструкції (if, for, const) не можна вставляти — виносьте їх за межі JSX.
  • Умовний рендеринг: тернарний оператор ? : та логічний &&.
  • Динамічні атрибути: className, style, src — все через {}.

🔑 Ключові правила

  • Усередині {} — лише вирази, що повертають значення.
  • Ліва частина && має бути булевою — уникайте count && <div />.
  • key обов'язковий у списках — використовуйте унікальні ID, а не індекси.
  • Інлайнові стилі — об'єкт із camelCase-властивостями: backgroundColor, fontSize.
  • TypeScript перевіряє типи — використовуйте явні анотації для складних об'єктів.
Наступні кроки:У наступній лекції ми детально розглянемо CSS Modules — як ізолювати стилі компонентів, уникати конфліктів класів та типізувати CSS через TypeScript.

Запитання для самоперевірки

Copyright © 2026