← React/Props: передача данных#259 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: React или VueПрактика: React setТермин: React HooksТема: React: хуки и экосистема

Props: передача данных

Что такое props

Props (properties) — это аргументы функции-компонента. Через props родительский компонент передаёт данные дочернему. Props — основной механизм однонаправленного потока данных в React.

// Объявление компонента с props
function Greeting({ name, age }) {
  return <p>Привет, {name}! Тебе {age} лет.</p>
}

// Использование — передача props как атрибуты JSX
<Greeting name="Алексей" age={28} />
//                        ^ число — в {}
//         ^ строка — можно без {}

Деструктуризация props

Props приходят как единый объект. Деструктуризация — рекомендуемый способ:

// Без деструктуризации (verbose):
function Button(props) {
  return <button className={props.variant}>{props.label}</button>
}

// С деструктуризацией (предпочтительно):
function Button({ label, variant = 'primary', onClick }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {label}
    </button>
  )
}

TypeScript интерфейс для props

Так как вы знаете TypeScript, описывайте props через интерфейс:

// TypeScript — явная типизация props
interface ButtonProps {
  label: string
  variant?: 'primary' | 'secondary' | 'danger'
  disabled?: boolean
  onClick: () => void
}

function Button({ label, variant = 'primary', disabled = false, onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  )
}

Props — только для чтения

Props иммутабельны: компонент не должен изменять свои props. Это правило обеспечивает предсказуемость и однонаправленный поток данных.

// НЕЛЬЗЯ — мутация props:
function BadComponent({ user }) {
  user.name = 'Изменено'  // Ошибка! Props read-only
  return <p>{user.name}</p>
}

// ПРАВИЛЬНО — используй локальный state или создавай новый объект:
function GoodComponent({ user }) {
  const [name, setName] = React.useState(user.name)
  return <p>{name}</p>
}

Специальный prop: children

children — зарезервированный prop для дочернего содержимого между открывающим и закрывающим тегами:

function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">
        {children}   {/* сюда попадает всё что между <Card>...</Card> */}
      </div>
    </div>
  )
}

// Использование:
<Card title="О нас">
  <p>Мы создаём отличные продукты</p>
  <Button label="Связаться" onClick={handleClick} />
</Card>

Передача функций как props (callbacks)

Функции передаются как props для "общения снизу вверх" — это идиома React для событий:

// Родитель передаёт callback
function Parent() {
  const handleDelete = (id) => {
    console.log(`Удаляем элемент ${id}`)
  }

  return <TodoItem id={42} onDelete={handleDelete} />
}

// Дочерний компонент вызывает callback
function TodoItem({ id, text, onDelete }) {
  return (
    <li>
      {text}
      <button onClick={() => onDelete(id)}>Удалить</button>
    </li>
  )
}

Prop drilling — проблема глубокой передачи

Когда нужно передать данные через много уровней — это называется prop drilling. Для глубокой вложенности используют Context API (изучим позже):

// Prop drilling — данные идут через посредников
<App user={user}>
  <Layout user={user}>       // Layout не использует user, но передаёт
    <Sidebar user={user}>    // Sidebar тоже не использует
      <Avatar user={user} /> // Только Avatar нужен user
    </Sidebar>
  </Layout>
</App>

// Context решает эту проблему (тема будущих уроков)

Сравнение с Vue

<!-- Vue — defineProps -->
<script setup>
const props = defineProps({ title: String, onClick: Function })
</script>
// React — просто параметры функции
function Component({ title, onClick }) { ... }

Props в React — обычные аргументы функции. Никакой магии!

Примеры

Система компонентов с props: передача данных, функции-колбэки, children, иммутабельность props

// Моделируем компонентную систему React с props на чистом JS
// Компоненты — функции, props — их аргументы

// ============================================================
// Примитивные компоненты с props и default-значениями
// ============================================================

// Аналог интерфейса TypeScript:
// interface BadgeProps { text: string; variant?: 'info' | 'success' | 'error' }
function Badge({ text, variant = 'info' }) {
  // React JSX: return <span className={`badge badge-${variant}`}>{text}</span>
  return { type: 'span', props: { className: `badge badge-${variant}`, children: text } }
}

// ============================================================
// Props иммутабельны — показываем правильный паттерн
// ============================================================

function processUser(user) {
  // НЕПРАВИЛЬНО — мутация props:
  // user.displayName = user.firstName + ' ' + user.lastName  // НЕЛЬЗЯ!

  // ПРАВИЛЬНО — создаём новый объект, не мутируем:
  const processed = {
    ...user,                                         // копируем все поля
    displayName: `${user.firstName} ${user.lastName}` // добавляем новое
  }
  return processed
}

const originalUser = { id: 1, firstName: 'Алексей', lastName: 'Иванов', age: 28 }
const processedUser = processUser(originalUser)

console.log('Оригинал не изменён:', originalUser.displayName) // undefined — не тронут!
console.log('Новый объект:', processedUser.displayName)        // 'Алексей Иванов'

// ============================================================
// children prop — содержимое между тегами
// ============================================================

// React JSX: function Card({ title, children }) { return <div><h2>{title}</h2>{children}</div> }
function Card({ title, children }) {
  // children — это всё что передано между <Card>...</Card>
  console.log(`Карточка "${title}" содержит: ${typeof children === 'function' ? 'функцию' : JSON.stringify(children)}`)
  return { type: 'div', props: { className: 'card', children: [
    { type: 'h2', props: { children: title } },
    children  // вставляем children как есть
  ]}}
}

// Использование — в JSX это: <Card title="Профиль"><Badge text="Pro" /></Card>
Card({ title: 'Профиль', children: Badge({ text: 'Pro', variant: 'success' }) })
// Карточка "Профиль" содержит: {"type":"span","props":{"className":"badge badge-success",...}}

// ============================================================
// Функции как props (callbacks) — коммуникация дочерний -> родитель
// ============================================================

// Список задач — компонент-контейнер
function TodoList({ todos, onDelete, onToggle }) {
  return todos.map(todo => {
    // React: return <TodoItem key={todo.id} todo={todo} onDelete={onDelete} onToggle={onToggle} />
    const item = {
      id: todo.id,
      text: todo.text,
      done: todo.done,
      // Когда пользователь нажимает "Удалить" — вызываем колбэк родителя
      handleDelete: () => onDelete(todo.id),
      handleToggle: () => onToggle(todo.id)
    }
    return item
  })
}

// "Родительский" контекст
let todos = [
  { id: 1, text: 'Изучить React', done: false },
  { id: 2, text: 'Написать компонент', done: true },
]

const items = TodoList({
  todos,
  onDelete: (id) => { console.log(`Удалён todo #${id}`) },
  onToggle: (id) => { console.log(`Переключён todo #${id}`) }
})

items[0].handleDelete()  // Удалён todo #1
items[1].handleToggle()  // Переключён todo #2

Props: передача данных

Что такое props

Props (properties) — это аргументы функции-компонента. Через props родительский компонент передаёт данные дочернему. Props — основной механизм однонаправленного потока данных в React.

// Объявление компонента с props
function Greeting({ name, age }) {
  return <p>Привет, {name}! Тебе {age} лет.</p>
}

// Использование — передача props как атрибуты JSX
<Greeting name="Алексей" age={28} />
//                        ^ число — в {}
//         ^ строка — можно без {}

Деструктуризация props

Props приходят как единый объект. Деструктуризация — рекомендуемый способ:

// Без деструктуризации (verbose):
function Button(props) {
  return <button className={props.variant}>{props.label}</button>
}

// С деструктуризацией (предпочтительно):
function Button({ label, variant = 'primary', onClick }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {label}
    </button>
  )
}

TypeScript интерфейс для props

Так как вы знаете TypeScript, описывайте props через интерфейс:

// TypeScript — явная типизация props
interface ButtonProps {
  label: string
  variant?: 'primary' | 'secondary' | 'danger'
  disabled?: boolean
  onClick: () => void
}

function Button({ label, variant = 'primary', disabled = false, onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  )
}

Props — только для чтения

Props иммутабельны: компонент не должен изменять свои props. Это правило обеспечивает предсказуемость и однонаправленный поток данных.

// НЕЛЬЗЯ — мутация props:
function BadComponent({ user }) {
  user.name = 'Изменено'  // Ошибка! Props read-only
  return <p>{user.name}</p>
}

// ПРАВИЛЬНО — используй локальный state или создавай новый объект:
function GoodComponent({ user }) {
  const [name, setName] = React.useState(user.name)
  return <p>{name}</p>
}

Специальный prop: children

children — зарезервированный prop для дочернего содержимого между открывающим и закрывающим тегами:

function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">
        {children}   {/* сюда попадает всё что между <Card>...</Card> */}
      </div>
    </div>
  )
}

// Использование:
<Card title="О нас">
  <p>Мы создаём отличные продукты</p>
  <Button label="Связаться" onClick={handleClick} />
</Card>

Передача функций как props (callbacks)

Функции передаются как props для "общения снизу вверх" — это идиома React для событий:

// Родитель передаёт callback
function Parent() {
  const handleDelete = (id) => {
    console.log(`Удаляем элемент ${id}`)
  }

  return <TodoItem id={42} onDelete={handleDelete} />
}

// Дочерний компонент вызывает callback
function TodoItem({ id, text, onDelete }) {
  return (
    <li>
      {text}
      <button onClick={() => onDelete(id)}>Удалить</button>
    </li>
  )
}

Prop drilling — проблема глубокой передачи

Когда нужно передать данные через много уровней — это называется prop drilling. Для глубокой вложенности используют Context API (изучим позже):

// Prop drilling — данные идут через посредников
<App user={user}>
  <Layout user={user}>       // Layout не использует user, но передаёт
    <Sidebar user={user}>    // Sidebar тоже не использует
      <Avatar user={user} /> // Только Avatar нужен user
    </Sidebar>
  </Layout>
</App>

// Context решает эту проблему (тема будущих уроков)

Сравнение с Vue

<!-- Vue — defineProps -->
<script setup>
const props = defineProps({ title: String, onClick: Function })
</script>
// React — просто параметры функции
function Component({ title, onClick }) { ... }

Props в React — обычные аргументы функции. Никакой магии!

Примеры

Система компонентов с props: передача данных, функции-колбэки, children, иммутабельность props

// Моделируем компонентную систему React с props на чистом JS
// Компоненты — функции, props — их аргументы

// ============================================================
// Примитивные компоненты с props и default-значениями
// ============================================================

// Аналог интерфейса TypeScript:
// interface BadgeProps { text: string; variant?: 'info' | 'success' | 'error' }
function Badge({ text, variant = 'info' }) {
  // React JSX: return <span className={`badge badge-${variant}`}>{text}</span>
  return { type: 'span', props: { className: `badge badge-${variant}`, children: text } }
}

// ============================================================
// Props иммутабельны — показываем правильный паттерн
// ============================================================

function processUser(user) {
  // НЕПРАВИЛЬНО — мутация props:
  // user.displayName = user.firstName + ' ' + user.lastName  // НЕЛЬЗЯ!

  // ПРАВИЛЬНО — создаём новый объект, не мутируем:
  const processed = {
    ...user,                                         // копируем все поля
    displayName: `${user.firstName} ${user.lastName}` // добавляем новое
  }
  return processed
}

const originalUser = { id: 1, firstName: 'Алексей', lastName: 'Иванов', age: 28 }
const processedUser = processUser(originalUser)

console.log('Оригинал не изменён:', originalUser.displayName) // undefined — не тронут!
console.log('Новый объект:', processedUser.displayName)        // 'Алексей Иванов'

// ============================================================
// children prop — содержимое между тегами
// ============================================================

// React JSX: function Card({ title, children }) { return <div><h2>{title}</h2>{children}</div> }
function Card({ title, children }) {
  // children — это всё что передано между <Card>...</Card>
  console.log(`Карточка "${title}" содержит: ${typeof children === 'function' ? 'функцию' : JSON.stringify(children)}`)
  return { type: 'div', props: { className: 'card', children: [
    { type: 'h2', props: { children: title } },
    children  // вставляем children как есть
  ]}}
}

// Использование — в JSX это: <Card title="Профиль"><Badge text="Pro" /></Card>
Card({ title: 'Профиль', children: Badge({ text: 'Pro', variant: 'success' }) })
// Карточка "Профиль" содержит: {"type":"span","props":{"className":"badge badge-success",...}}

// ============================================================
// Функции как props (callbacks) — коммуникация дочерний -> родитель
// ============================================================

// Список задач — компонент-контейнер
function TodoList({ todos, onDelete, onToggle }) {
  return todos.map(todo => {
    // React: return <TodoItem key={todo.id} todo={todo} onDelete={onDelete} onToggle={onToggle} />
    const item = {
      id: todo.id,
      text: todo.text,
      done: todo.done,
      // Когда пользователь нажимает "Удалить" — вызываем колбэк родителя
      handleDelete: () => onDelete(todo.id),
      handleToggle: () => onToggle(todo.id)
    }
    return item
  })
}

// "Родительский" контекст
let todos = [
  { id: 1, text: 'Изучить React', done: false },
  { id: 2, text: 'Написать компонент', done: true },
]

const items = TodoList({
  todos,
  onDelete: (id) => { console.log(`Удалён todo #${id}`) },
  onToggle: (id) => { console.log(`Переключён todo #${id}`) }
})

items[0].handleDelete()  // Удалён todo #1
items[1].handleToggle()  // Переключён todo #2

Задание

Создай компонент ProductCard принимающий пропсы name, price, inStock (boolean) и onBuy (функция). Компонент показывает название товара, цену и кнопку "Купить" — если inStock равно true, иначе кнопка недоступна (disabled) с текстом "Нет в наличии". Компонент App рендерит два ProductCard с разными данными.

Подсказка

Подставь {name}, {price} в разметку. onClick принимает onBuy (передай без вызова: onClick={onBuy}). disabled={!inStock} — кнопка недоступна когда товара нет. В App передай числовую цену без кавычек: price={4990}.

Загружаем среду выполнения...
Загружаем AI-помощника...