Компоненти та Props
React компоненти та Props — конструктор LEGO де деталі з'єднуються у будинок
У попередньому уроці ми познайомились з React та JSX. Тепер час навчитись головному -- розбивати інтерфейс на компоненти та передавати їм дані через props. Це фундамент, на якому будується будь-який React-додаток.
Що таке компонент?
Компонент -- це функція, яка повертає JSX. Кожен шматочок інтерфейсу -- кнопка, картка, хедер, форма -- це окремий компонент.
function Greeting() {
return <h1>Привіт, світе!</h1>
}
Щоб використати компонент, просто запиши його як тег:
function App() {
return (
<div>
<Greeting />
<Greeting />
<Greeting />
</div>
)
}
Назви компонентів завжди починаються з великої літери: Greeting, UserCard, NavMenu. Якщо написати <greeting />, React сприйме це як звичайний HTML-тег.
Props -- передача даних компонентам
Props (properties) -- це об'єкт з даними, які батьківський компонент передає дочірньому. Це як аргументи функції:
// Компонент приймає props
function Greeting(props: { name: string }) {
return <h1>Привіт, {props.name}!</h1>
}
// Батьківський компонент передає props
function App() {
return (
<div>
<Greeting name="Олексій" />
<Greeting name="Марія" />
<Greeting name="Дмитро" />
</div>
)
}
Деструктуризація props
Замість props.name зручніше одразу деструктуризувати:
function Greeting({ name }: { name: string }) {
return <h1>Привіт, {name}!</h1>
}
Типізація props з interface
Для кількох props краще винести тип окремо:
interface UserCardProps {
name: string
email: string
age: number
isOnline: boolean
}
function UserCard({ name, email, age, isOnline }: UserCardProps) {
return (
<div className="user-card">
<h2>{name}</h2>
<p>Email: {email}</p>
<p>Вік: {age}</p>
<span>{isOnline ? "Онлайн" : "Офлайн"}</span>
</div>
)
}
function App() {
return (
<UserCard
name="Олексій"
email="alex@test.com"
age={25}
isOnline={true}
/>
)
}
Рядки передаються в лапках: name="Олексій". Числа, булеани, об'єкти, масиви -- у фігурних дужках: age={25}, isOnline={true}, items={[1, 2, 3]}.
Опціональні props та значення за замовчуванням
interface ButtonProps {
text: string
variant?: 'primary' | 'secondary' // опціональний
size?: 'small' | 'medium' | 'large'
}
function Button({ text, variant = 'primary', size = 'medium' }: ButtonProps) {
return (
<button className={`btn btn-${variant} btn-${size}`}>
{text}
</button>
)
}
// Використання
<Button text="Зберегти" /> // variant='primary', size='medium'
<Button text="Скасувати" variant="secondary" /> // size='medium'
<Button text="ОК" variant="primary" size="small" /> // все задано
Children -- вкладений вміст
Спеціальний prop children -- це те, що ти передаєш між відкриваючим та закриваючим тегами компонента:
interface CardProps {
title: string
children: React.ReactNode
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2 className="card-title">{title}</h2>
<div className="card-body">
{children}
</div>
</div>
)
}
function App() {
return (
<Card title="Новина">
<p>Це вміст картки. Тут може бути будь-що!</p>
<button>Детальніше</button>
</Card>
)
}
React.ReactNode -- тип, що приймає будь-що, що можна відрендерити: рядок, число, JSX, масив елементів, null.
Обгортки (Wrapper components)
Children ідеально підходить для компонентів-обгорток:
interface ContainerProps {
children: React.ReactNode
maxWidth?: string
}
function Container({ children, maxWidth = '1200px' }: ContainerProps) {
return (
<div style={{ maxWidth, margin: '0 auto', padding: '0 20px' }}>
{children}
</div>
)
}
function App() {
return (
<Container maxWidth="800px">
<h1>Мій сайт</h1>
<p>Контент у контейнері</p>
</Container>
)
}
Композиція компонентів
Сила React -- у композиції. Маленькі компоненти складаються у більші:
interface AvatarProps {
src: string
alt: string
}
function Avatar({ src, alt }: AvatarProps) {
return <img className="avatar" src={src} alt={alt} />
}
interface UserInfoProps {
name: string
role: string
avatarUrl: string
}
function UserInfo({ name, role, avatarUrl }: UserInfoProps) {
return (
<div className="user-info">
<Avatar src={avatarUrl} alt={name} />
<div>
<h3>{name}</h3>
<p>{role}</p>
</div>
</div>
)
}
interface HeaderProps {
userName: string
userRole: string
userAvatar: string
}
function Header({ userName, userRole, userAvatar }: HeaderProps) {
return (
<header className="header">
<h1>Мій додаток</h1>
<UserInfo name={userName} role={userRole} avatarUrl={userAvatar} />
</header>
)
}
Розділення на файли
У реальних проектах кожен компонент живе у своєму файлі:
src/
├── components/
│ ├── Button.tsx
│ ├── Card.tsx
│ ├── UserCard.tsx
│ └── Header.tsx
├── App.tsx
└── main.tsx
// src/components/Button.tsx
interface ButtonProps {
text: string
onClick: () => void
variant?: 'primary' | 'secondary'
}
export default function Button({ text, onClick, variant = 'primary' }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{text}
</button>
)
}
// src/App.tsx
import Button from './components/Button'
function App() {
return (
<div>
<Button text="Натисни" onClick={() => alert('Клік!')} />
</div>
)
}
export default App
Є два підходи до експорту: export default (один компонент на файл) та іменований export (кілька на файл). Обидва працюють, але export default -- найпоширеніший для компонентів.
Props -- це read-only!
Компонент ніколи не повинен змінювати свої props:
// НІКОЛИ так не роби!
function Bad({ name }: { name: string }) {
name = "Хакер" // ПОМИЛКА -- не мутуй props!
return <h1>{name}</h1>
}
Props -- це вхідні дані від батька. Якщо потрібні дані, які змінюються, використовуй state -- тема наступного уроку.
Підсумок
- Компонент -- функція, що повертає JSX. Назва -- з великої літери
- Props -- дані, що передаються від батьківського компонента
- Деструктуризація та TypeScript-інтерфейси роблять props зрозумілими
- children -- спеціальний prop для вкладеного вмісту
- Композиція -- маленькі компоненти складаються у більші
- Кожен компонент -- окремий файл
- Props -- read-only, їх не можна змінювати
Що далі?
Компоненти вміють приймати дані, але поки що вони статичні. У наступному уроці ми навчимось робити компоненти інтерактивними за допомогою state та хука useState.
Корисні посилання:
- React.dev: Your First Component -- створення компонентів
- React.dev: Passing Props to a Component -- все про props
- React.dev: Thinking in React -- як мислити компонентами