В прошлом уроке данные жили прямо в компоненте: const name = 'Мария'. Такой
компонент умеет рисовать только Марию. А карточек пользователей на странице
может быть сотня, и у каждой своё имя.
Решение — передать данные снаружи, как аргументы в функцию. В React они называются props (от properties — свойства).
Передаём props
Props пишут как атрибуты тега:
<UserCard name="Аня" age={30} />React соберёт их в один объект и передаст компоненту первым аргументом:
function UserCard(props) {
return <p>{props.name}, {props.age} лет</p>
}
// Аня, 30 летСтроку можно передать в кавычках. Всё остальное — числа, true/false,
массивы, объекты, функции — в фигурных скобках:
<UserCard name="Аня" age={30} admin={true} tags={['js', 'react']} />Осторожно: age="30" — это строка '30', а не число. Для сложения и
сравнения нужны скобки: age={30}.
Логический prop без значения равен true: <UserCard admin /> — то же, что
admin={true}.
Деструктуризация в параметрах
Писать props. перед каждым именем быстро надоедает. Поэтому props почти
всегда разбирают прямо в параметрах — обычной деструктуризацией из
JavaScript:
function UserCard({ name, age }) {
return <p>{name}, {age} лет</p>
}Заодно сразу видно, какие данные компонент ждёт.
Значения по умолчанию
Если prop не передали, он равен undefined. Значение по умолчанию задают
там же, где и в любой деструктуризации:
function Button({ text, size = 'medium' }) {
return <button className={'btn btn-' + size}>{text}</button>
}
<Button text="Купить" /> // класс btn btn-medium
<Button text="Купить" size="large" /> // класс btn btn-largeУмолчание срабатывает только на undefined. Если передать size={null} или
size="", подставится именно это.
Объект целиком
Когда данных много, удобно передать один объект:
const book = { title: 'Мастер и Маргарита', author: 'Булгаков', year: 1967 }
function BookRow({ book }) {
return <li>{book.title} — {book.author}, {book.year}</li>
}
<BookRow book={book} />Можно и разобрать вложенно — function BookRow({ book: { title, author } }),
— но обычно book.title читается проще.
Функции в props
Prop может быть и функцией. Компонент вызывает её, чтобы получить кусок текста или разметки:
function Price({ amount, format }) {
return <span>{format(amount)}</span>
}
<Price amount={1500} format={(n) => n + ' ₽'} /> // 1500 ₽Главное применение функций в props — обработчики нажатий: «когда кнопку нажмут, позови вот это». До них дойдём в модуле про состояние.
children: то, что внутри тега
Компоненты можно не только закрывать сразу (<Card />), но и класть что-то
внутрь:
<Card>
<p>Любой JSX внутри</p>
</Card>Всё содержимое между открывающим и закрывающим тегом приходит в
специальный prop children. Компонент сам решает, где его нарисовать:
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
)
}Так делают «рамки»: карточку, модальное окно, раскладку страницы. Рамка знает, как выглядит снаружи, а что внутри — решает тот, кто её использует.
Компоненты из компонентов
Свои компоненты используются в JSX так же, как <div>: один компонент
рисует другие и передаёт им props.
function Avatar({ src, name }) {
return <img className="avatar" src={src} alt={name} />
}
function Profile({ user }) {
return (
<Card title={user.name}>
<Avatar src={user.photo} name={user.name} />
<p>{user.city}</p>
</Card>
)
}Данные текут сверху вниз: Profile получил user и раздал его части
дочерним компонентам. Из таких кирпичей и собирается всё приложение.
Props только для чтения
Компонент не меняет свои props. Они принадлежат тому, кто их передал:
function Price({ amount }) {
amount = amount * 2 // так не делают
return <span>{amount} ₽</span>
}Нужно другое значение — заведите новую переменную:
function Price({ amount, discount = 0 }) {
const total = amount - discount
return <span>{total} ₽</span>
}Это правило важнее, чем кажется. Компонент с одинаковыми props должен рисовать одно и то же — как чистая функция с одинаковыми аргументами. На этом держится вся логика React: что перерисовать, а что оставить. А если переданный объект поменять внутри, изменения увидят и другие компоненты, к которым он попал.
Как здесь проверяются props
Тесты передают компоненту разные значения и смотрят на результат:
render(<UserCard name="Аня" age={30} />).getByText('Аня, 30 лет')
render(<UserCard name="Олег" age={45} />).getByText('Олег, 45 лет')Поэтому значения из примера в условии вписывать руками бесполезно — берите их из props.