Вивчай

Компоненти та Props

React компоненти та Props — конструктор LEGO де деталі з'єднуються у будинок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.

Інфо

Корисні посилання: