Props (properties) — это аргументы функции-компонента. Через props родительский компонент передаёт данные дочернему. Props — основной механизм однонаправленного потока данных в React.
// Объявление компонента с props
function Greeting({ name, age }) {
return <p>Привет, {name}! Тебе {age} лет.</p>
}
// Использование — передача props как атрибуты JSX
<Greeting name="Алексей" age={28} />
// ^ число — в {}
// ^ строка — можно без {}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
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:
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>
}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 для "общения снизу вверх" — это идиома 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. Для глубокой вложенности используют 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 — 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 #2Props (properties) — это аргументы функции-компонента. Через props родительский компонент передаёт данные дочернему. Props — основной механизм однонаправленного потока данных в React.
// Объявление компонента с props
function Greeting({ name, age }) {
return <p>Привет, {name}! Тебе {age} лет.</p>
}
// Использование — передача props как атрибуты JSX
<Greeting name="Алексей" age={28} />
// ^ число — в {}
// ^ строка — можно без {}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
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:
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>
}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 для "общения снизу вверх" — это идиома 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. Для глубокой вложенности используют 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 — 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}.