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 виконує вираз усередині {}, отримує результат та вставляє його як текстовий вузол.
- Змінна:
userName - Число:
42 - Рядок:
"Привіт" - Арифметика:
5 + 3 - Виклик функції:
Math.random() - Тернарний оператор:
isActive ? "Так" : "Ні" - Логічні оператори:
isLoggedIn && <Menu />
{}: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), отримає рядок і вставить його як текст.
{}, мають бути чистими — повертати однаковий результат для однакових вхідних даних без побічних ефектів (не змінювати зовнішній стан, не викликати мережеві запити).Якщо функція має побічні ефекти (наприклад, 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>
- Немає псевдокласів:
:hover,:focus,:activeне працюють уstyle. - Немає медіа-запитів:
@mediaне працює уstyle. - Погана продуктивність: Інлайнові стилі створюють новий об'єкт при кожному рендері (якщо не винесені у константу), що може уповільнити великі списки.
- Динамічних значень (прогрес-бари, анімації, позиціонування).
- Швидких прототипів.
Умовний рендеринг
Одна з найпотужніших можливостей 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>}
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 застосунку…
Що НЕ можна вставляти через {}
Фігурні дужки приймають лише вирази, а не інструкції.
❌ Не працює: 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>)}
// ✅ Використовуйте унікальний 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 застосунку…
Що демонструє цей приклад:
- Обчислювані значення:
fullName,initials,membershipDays,isNewUser. - Динамічні класи:
cardClass,statusClass. - Динамічні стилі:
avatarStyleз градієнтами. - Умовний рендеринг:
{isNewUser && ...},{user.isActive ? ... : ...}. - Форматування дат:
.toLocaleDateString(). - Робота з масивами:
.map()для рендерингу списку користувачів.
Підсумок
✅ Що ми опанували
- Фігурні дужки
{}дозволяють вставляти TypeScript-вирази у JSX. - Вирази (змінні, виклики функцій, тернарні оператори) можна використовувати через
{}. - Інструкції (
if,for,const) не можна вставляти — виносьте їх за межі JSX. - Умовний рендеринг: тернарний оператор
? :та логічний&&. - Динамічні атрибути:
className,style,src— все через{}.
🔑 Ключові правила
- Усередині
{}— лише вирази, що повертають значення. - Ліва частина
&&має бути булевою — уникайтеcount && <div />. keyобов'язковий у списках — використовуйте унікальні ID, а не індекси.- Інлайнові стилі — об'єкт із camelCase-властивостями:
backgroundColor,fontSize. - TypeScript перевіряє типи — використовуйте явні анотації для складних об'єктів.
Запитання для самоперевірки
Ні, if є інструкцією (statement), а не виразом (expression). Усередині фігурних дужок {} можна використовувати лише вирази.
Чому?
Інструкція if не повертає значення — вона виконує код у блоці { }, але сама по собі не є значенням, яке можна вставити у JSX.
Альтернативи:
- Тернарний оператор:
{isError ? <p>Помилка!</p> : <p>Успіх!</p>}
- Логічний оператор
&&:
{isError && <p>Помилка!</p>}
- Змінна:
let content;
if (isError) {
content = <p>Помилка!</p>;
} else {
content = <p>Успіх!</p>;
}
return <div>{content}</div>;
- IIFE (Immediately Invoked Function Expression):
{(() => {
if (role === 'admin') return <AdminPanel />;
if (role === 'user') return <UserPanel />;
return <GuestPanel />;
})()}
Логічний оператор && працює так: якщо ліва частина falsy (хибна), він повертає саму ліву частину, а не false.
const count = 0;
// Що відбувається:
count && <p>Текст</p>
// Оцінюється як:
0 // React рендерить число 0!
Результат у DOM: на екрані з'явиться "0".
Чому?
У JavaScript 0 є falsy значенням, тому && повертає 0. Але React рендерить числа як текст, тому 0 відображається.
Правильно:
// ✅ Явна перевірка на булеве значення
{count > 0 && <p>{count} повідомлень</p>}
// ✅ Подвійне заперечення перетворює на boolean
{!!count && <p>{count} повідомлень</p>}
// ✅ Тернарний оператор
{count > 0 ? <p>{count} повідомлень</p> : null}
Правило: Ліва частина && завжди має бути булевим значенням (true / false).
React використовує key для ідентифікації елементів у списках під час reconciliation (порівняння старого та нового Virtual DOM).
Проблема з індексом:
// ❌ Індекс як key
{items.map((item, index) => <div key={index}>{item.name}</div>)}
Якщо ви видалите перший елемент або змінюєте порядок, індекси зміщуються:
Було: [0] Яблуко, [1] Банан, [2] Апельсин
Після: [0] Банан, [1] Апельсин
React бачить, що елемент із key={0} змінився з "Яблуко" на "Банан", і перестворює DOM-елемент, хоча насправді "Банан" вже існував із key={1}.
Наслідки:
- Повільний рендеринг: React перестворює елементи замість оновлення.
- Втрата стану: Якщо елементи мають внутрішній стан (наприклад,
<input>), він скидається. - Неправильна асоціація: Фокус, анімації та рефи можуть прив'язатися до неправильних елементів.
Правильно:
// ✅ Унікальний ID із бази даних
{items.map(item => <div key={item.id}>{item.name}</div>)}
Коли індекс допустимий:
- Список ніколи не змінюється (статичні дані).
- Елементи не можуть бути відсортовані або відфільтровані.
- Елементи не мають внутрішнього стану.
Ні, інлайнові стилі через style={{ }} не підтримують псевдокласи (:hover, :focus, :active) та медіа-запити (@media).
Чому?
Атрибут style у HTML (і, відповідно, у JSX) встановлює лише базові CSS-властивості елемента. Псевдокласи — це частина CSS-селекторів, які працюють на рівні таблиць стилів, а не атрибутів.
Що не працює:
// ❌ НЕ ПРАЦЮЄ
<button style={{
backgroundColor: '#3b82f6',
':hover': { backgroundColor: '#2563eb' } // Ігнорується!
}}>
Кнопка
</button>
Рішення:
- CSS Modules або звичайний CSS:
/* Button.module.css */
.button {
background-color: #3b82f6;
}
.button:hover {
background-color: #2563eb;
}
import styles from './Button.module.css';
<button className={styles.button}>Кнопка</button>
- Бібліотека styled-components або Emotion:
import styled from 'styled-components';
const Button = styled.button`
background-color: #3b82f6;
&:hover {
background-color: #2563eb;
}
`;
- Стан компонента для інтерактивності:
Примітка: Цей приклад використовує
useState(хук для управління станом), який ми детально розглянемо у наступних лекціях. Поки що сприймайте його як "магію", що дозволяє змінювати значення при взаємодії користувача.
import { useState } from 'react';
function Button() {
const [isHovered, setIsHovered] = useState(false);
return (
<button
style={{
backgroundColor: isHovered ? '#2563eb' : '#3b82f6',
padding: '0.75rem 1.5rem',
border: 'none',
borderRadius: '0.5rem',
color: 'white',
cursor: 'pointer',
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
Кнопка
</button>
);
}
Коли використовувати інлайнові стилі:
- Динамічні значення (прогрес-бари, позиціонування).
- Швидкі прототипи.
- Анімації через JavaScript.
Для статичних стилів використовуйте CSS Modules або CSS-in-JS бібліотеки.
Технічно — так, але це антипатерн.
Проблема:
function Component() {
const style = { color: 'red' };
style.fontSize = '16px'; // ⚠️ Мутація об'єкта
return <div style={style}>Текст</div>;
}
При кожному рендері створюється новий об'єкт style, але потім він змінюється. React порівнює об'єкти за посиланням, тому може вважати, що стиль не змінився, якщо посилання залишилося тим самим.
Правильно:
// ✅ Створюйте незмінний об'єкт
function Component() {
const style = {
color: 'red',
fontSize: '16px',
};
return <div style={style}>Текст</div>;
}
// ✅ Або використовуйте useMemo для оптимізації
function Component({ color, size }) {
const style = useMemo(() => ({
color,
fontSize: `${size}px`,
}), [color, size]);
return <div style={style}>Текст</div>;
}
Принцип: Об'єкти у React мають бути незмінними (immutable). Створюйте новий об'єкт замість зміни існуючого.
Компоненти — будівельні блоки інтерфейсу
Фундаментальні принципи компонентної архітектури React — що таке компонент, як створювати функціональні компоненти, композиція проти успадкування, передача пропсів, деструктуризація, children та найкращі практики побудови переповторно використовуваних UI-блоків.
Хук useReducer: структуроване управління складним станом
Глибоке занурення в хук useReducer для керування складним станом у React через чіткі екшени та передбачувану логіку оновлень. Від простих лічильників до багаторівневих форм.