Пошук уроків, статей та іншого контенту
Розглянете типи для обов’язкових, необов’язкових і вкладених властивостей компонентів.
Props — це дані, які батьківський компонент передає дочірньому. У TypeScript для props потрібно описати структуру: назви властивостей, їхні типи та обов’язковість.
Типізація допомагає:
бачити помилки ще до запуску застосунку;
отримувати підказки в редакторі;
розуміти, які дані потрібні компоненту;
безпечно працювати з вкладеними об’єктами.
Властивість без знака ? є обов’язковою. Якщо компонент очікує title і count, їх потрібно передати під час використання компонента.
type CounterProps = {
title: string;
count: number;
};
function Counter({ title, count }: CounterProps) {
return (
<section>
<h2>{title}</h2>
<p>Значення: {count}</p>
</section>
);
}
export default function App() {
return <Counter title="Лічильник" count={3} />;
}У цьому прикладі:
title має бути рядком (string);
count має бути числом (number);
обидві властивості потрібно передати компоненту Counter.
Такий код буде помилковим:
<Counter title="Лічильник" />Властивість count не передана, хоча вона обов’язкова.
Також TypeScript повідомить про помилку, якщо передати значення неправильного типу:
<Counter title="Лічильник" count="три" />count оголошено як number, тому рядок "три" передавати не можна.
Щоб зробити властивість необов’язковою, після її назви потрібно поставити ?.
type GreetingProps = {
name: string;
subtitle?: string;
};
function Greeting({ name, subtitle }: GreetingProps) {
return (
<section>
<h2>Привіт, {name}!</h2>
{subtitle && <p>{subtitle}</p>}
</section>
);
}
export default function App() {
return (
<>
<Greeting name="Олена" subtitle="Раді бачити вас у застосунку" />
<Greeting name="Андрій" />
</>
);
}Тепер name потрібно передати завжди, а subtitle можна пропустити.
Оскільки subtitle необов’язковий, його фактичний тип — string | undefined. Це означає, що властивість може містити рядок або бути відсутньою.
Тому перед виведенням необов’язкової властивості часто виконують перевірку:
{subtitle && <p>{subtitle}</p>}Якщо subtitle не передано, абзац не буде відрендерено.
Необов’язковій властивості можна призначити значення за замовчуванням під час деструктуризації:
type ButtonProps = {
label: string;
color?: string;
};
function Button({ label, color = "blue" }: ButtonProps) {
return (
<button style={{ color }}>
{label}
</button>
);
}
export default function App() {
return (
<>
<Button label="Зберегти" />
<Button label="Скасувати" color="gray" />
</>
);
}Якщо color не передано, компонент використає значення "blue".
Props можуть містити не лише прості значення, а й об’єкти. Для такого об’єкта зручно створити окремий тип.
type User = {
name: string;
email: string;
};
type UserCardProps = {
user: User;
isActive: boolean;
};
function UserCard({ user, isActive }: UserCardProps) {
return (
<article>
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>{isActive ? "Активний користувач" : "Неактивний користувач"}</p>
</article>
);
}
export default function App() {
const user = {
name: "Марія",
email: "maria@example.com",
};
return <UserCard user={user} isActive />;
}Тут:
UserCard отримує властивість user;
user має тип User;
об’єкт User повинен містити name і email;
isActive є обов’язковою логічною властивістю.
Для вкладених властивостей TypeScript також перевіряє назви та типи полів:
<UserCard
user={{
name: "Марія",
email: "maria@example.com",
}}
isActive={true}
/>Якщо пропустити поле email, виникне помилка:
<UserCard
user={{
name: "Марія",
}}
isActive={true}
/>Тип User вимагає наявності email.
Необов’язковими можуть бути як сам вкладений об’єкт, так і окремі його поля.
type Product = {
name: string;
price: number;
description?: string;
};
type ProductCardProps = {
product: Product;
};
function ProductCard({ product }: ProductCardProps) {
return (
<article>
<h2>{product.name}</h2>
<p>Ціна: {product.price} грн</p>
{product.description && <p>{product.description}</p>}
</article>
);
}
export default function App() {
return (
<ProductCard
product={{
name: "Навушники",
price: 1200,
}}
/>
);
}У типі Product поле description має знак ?, тому його можна не передавати.
type Avatar = {
url: string;
alt: string;
};
type ProfileProps = {
name: string;
avatar?: Avatar;
};
function Profile({ name, avatar }: ProfileProps) {
return (
<article>
<h2>{name}</h2>
{avatar && <img src={avatar.url} alt={avatar.alt} />}
</article>
);
}
export default function App() {
return (
<>
<Profile name="Олег" />
<Profile
name="Софія"
avatar={{
url: "/images/sofia.png",
alt: "Фото Софії",
}}
/>
</>
);
}Оскільки avatar може бути відсутнім, перед зверненням до avatar.url потрібно перевірити, чи існує об’єкт avatar.
interfaceДля опису props можна використовувати як type, так і interface.
interface MessageProps {
text: string;
author?: string;
}
function Message({ text, author = "Невідомий автор" }: MessageProps) {
return (
<article>
<p>{text}</p>
<small>Автор: {author}</small>
</article>
);
}
export default function App() {
return <Message text="Вітаємо в застосунку!" />;
}Для простих компонентів обидва варіанти підходять. Головне — послідовно описати:
які властивості існують;
які типи вони мають;
які властивості є обов’язковими;
які властивості можуть бути відсутніми.
У наступному прикладі поєднані обов’язкові, необов’язкові та вкладені властивості:
type Address = {
city: string;
country?: string;
};
type Customer = {
name: string;
email: string;
address?: Address;
};
type CustomerCardProps = {
customer: Customer;
showEmail?: boolean;
};
function CustomerCard({
customer,
showEmail = true,
}: CustomerCardProps) {
return (
<article>
<h2>{customer.name}</h2>
{showEmail && <p>Email: {customer.email}</p>}
{customer.address && (
<p>
Адреса: {customer.address.city}
{customer.address.country
? `, ${customer.address.country}`
: ""}
</p>
)}
</article>
);
}
export default function App() {
return (
<CustomerCard
customer={{
name: "Ірина",
email: "iryna@example.com",
address: {
city: "Львів",
country: "Україна",
},
}}
showEmail
/>
);
}У цьому компоненті:
customer — обов’язкова властивість;
customer.name і customer.email — обов’язкові поля;
customer.address — необов’язковий вкладений об’єкт;
customer.address.country — необов’язкове поле;
showEmail — необов’язкова властивість із значенням за замовчуванням true.
type TitleProps = {
title: string;
};
function Title({ title }: TitleProps) {
return <h1>{title}</h1>;
}
// Помилка: властивість title не передана
<Title />;Виправлення:
<Title title="Головна сторінка" />;type ScoreProps = {
score: number;
};
function Score({ score }: ScoreProps) {
return <p>Результат: {score}</p>;
}
// Помилка: score має бути number
<Score score="100" />;Виправлення:
<Score score={100} />;type Company = {
name: string;
};
type EmployeeProps = {
company?: Company;
};
function Employee({ company }: EmployeeProps) {
// Помилка: company може бути undefined
return <p>{company.name}</p>;
}Потрібно перевірити наявність об’єкта:
function Employee({ company }: EmployeeProps) {
return <p>{company ? company.name : "Компанію не вказано"}</p>;
}Або використати optional chaining:
function Employee({ company }: EmployeeProps) {
return <p>{company?.name ?? "Компанію не вказано"}</p>;
}Не слід додавати ? до всіх властивостей лише для того, щоб позбутися помилок. Якщо компонент не може працювати без певного значення, це значення має бути обов’язковим.
type ArticleProps = {
title: string;
description?: string;
};У цьому прикладі компонент завжди має отримати title, але може працювати без description.
Передавання числа як рядка або рядка як числа.
Використання необов’язкової властивості без перевірки.
Невідповідність назви поля в об’єкті та в типі.
Надання вкладеному об’єкту неповної структури.
Використання any замість точного типу.
Позначення властивості як необов’язкової, хоча без неї компонент не має сенсу.
Тип Props описує дані, які компонент може отримати.
Властивість без ? є обов’язковою.
Властивість із ? є необов’язковою та може мати значення undefined.
Для вкладених об’єктів варто створювати окремі типи.
Необов’язкові вкладені властивості потрібно перевіряти перед використанням.
Значення за замовчуванням можна задавати під час деструктуризації props.
Для опису Props можна використовувати type або interface.