Компоненты и пропсы
Компоненты позволяют разбить интерфейс на независимые части, про которые легко думать в отдельности. Их можно складывать вместе и использовать несколько раз. На этой странице мы ознакомимся с самой идеей компонентов — детальное описание API находится здесь.
Во многом компоненты ведут себя как обычные функции JavaScript. Они принимают произвольные входные данные (так называемые «пропсы») и возвращают React-элементы, описывающие, что мы хотим увидеть на экране.
Функциональные и классовые компоненты
Проще всего объявить React-компонент как функцию:
function Welcome(props) {
return <h1>Привет, {props.name}</h1>;
}
Эта функция — компонент, потому что она получает данные в одном объекте («пропсы») в качестве параметра и возвращает React-элемент. Мы будем называть такие компоненты «функциональными», так как они буквально являются функциями.
Ещё компоненты можно определять как классы ES6:
class Welcome extends React.Component {
render() {
return <h1>Привет, {this.props.name}</h1>;
}
}
С точки зрения React, эти два компонента эквивалентны.
Функциональным и классовым компонентам доступны дополнительные возможности, о которых мы поговорим в следующих главах.
Как отрендерить компонент
Пока что мы только встречали React-элементы, представляющие собой DOM-теги:
const element = <div />;
Но элементы могут описывать и наши собственные компоненты:
const element = <Welcome name="Алиса" />;
Когда React встречает подобный элемент, он собирает все JSX-атрибуты и дочерние элементы в один объект и передаёт их нашему компоненту. Этот объект называется «пропсы» (props).
Например, этот компонент выведет «Привет, Алиса» на страницу:
function Welcome(props) { return <h1>Привет, {props.name}</h1>;
}
const element = <Welcome name="Алиса" />;ReactDOM.render(
element,
document.getElementById('root')
);
Давайте разберём, что именно здесь происходит:
- Мы передаём React-элемент
<Welcome name="Алиса" />
вReactDOM.render()
. - React вызывает наш компонент
Welcome
с пропсами{name: 'Алиса'}
. - Наш компонент
Welcome
возвращает элемент<h1>Привет, Алиса</h1>
в качестве результата. - React DOM делает минимальные изменения в DOM, чтобы получилось
<h1>Привет, Алиса</h1>
.
Примечание: Всегда называйте компоненты с заглавной буквы.
Если компонент начинается с маленькой буквы, React принимает его за DOM-тег. Например,
<div />
это div-тег из HTML, а<Welcome />
это уже наш компонентWelcome
, который должен быть в области видимости.Чтобы узнать больше про это соглашение, прочитайте Углублённое изучение JSX.
Композиция компонентов
Компоненты могут ссылаться на другие компоненты в возвращённом ими дереве. Это позволяет нам использовать одну и ту же абстракцию — компоненты — на любом уровне нашего приложения. Неважно, пишем ли мы кнопку, форму или целый экран: все они, как правило, представляют собой компоненты в React-приложениях.
Например, компонент App
может отрендерить компонент Welcome
несколько раз:
function Welcome(props) {
return <h1>Привет, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Алиса" /> <Welcome name="Базилио" /> <Welcome name="Буратино" /> </div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
В приложениях, написанных на React с нуля, как правило, есть один компонент App
, который находится на самом верху. В случае, если вы переписываете существующее приложение на React, имеет смысл начать работу с маленького компонента типа Button
и постепенно двигаться «вверх» по иерархии.
Извлечение компонентов
Не бойтесь разбивать компоненты на части.
Допустим, у нас есть компонент Comment
:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Этот компонент представляет собой комментарий в социальной сети. Его пропсы включают в себя author
(объект), text
(строка), и date
(дата).
С этим компонентом может быть не очень удобно работать из-за излишней вложенности. Мы также не можем повторно использовать его составные части. Давайте извлечём из него пару компонентов.
Для начала извлечём Avatar
:
function Avatar(props) {
return (
<img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} /> );
}
Компоненту Avatar
незачем знать, что он рендерится внутри Comment
. Поэтому мы дали его пропу чуть менее конкретное имя — user
, а не author
.
Пропсы следует называть так, чтобы они имели смысл в первую очередь с точки зрения самого компонента, а уже во вторую тех компонентов, которые его рендерят.
Теперь можно немножко упростить наш Comment
:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} /> <div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Следующим шагом извлечём компонент UserInfo
, который рендерит Avatar
рядом с именем пользователя:
function UserInfo(props) {
return (
<div className="UserInfo"> <Avatar user={props.user} /> <div className="UserInfo-name"> {props.user.name} </div> </div> );
}
Это позволит ещё сильнее упростить Comment
:
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} /> <div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
Извлечение компонентов может сначала показаться неблагодарной работой. Тем не менее, в больших приложениях очень полезно иметь палитру компонентов, которые можно многократно использовать. Если вы не уверены, извлекать компонент или нет, вот простое правило. Если какая-то часть интерфейса многократно в нём повторяется (Button
, Panel
, Avatar
) или сама по себе достаточно сложная (App
, FeedStory
, Comment
), имеет смысл её вынести в независимый компонент.
Пропсы можно только читать
Компонент никогда не должен что-то записывать в свои пропсы — вне зависимости от того, функциональный он или классовый.
Возьмём для примера функцию sum
:
function sum(a, b) {
return a + b;
}
Такие функции называют «чистыми», потому что они не меняют свои входные данные и предсказуемо возвращают один и тот же результат для одинаковых аргументов.
А вот пример нечистой функции — она записывает данные в свои же аргументы:
function withdraw(account, amount) {
account.total -= amount;
}
React достаточно гибкий, но есть одно правило, которое нельзя нарушать:
React-компоненты обязаны вести себя как чистые функции по отношению к своим пропсам.
Конечно, интерфейсы приложений обычно изменяются с течением времени. В следующей главе мы узнаем о том, что такое «состояние» компонента. Состояние даёт компонентам возможность реагировать на действия пользователя, ответы сервера и другие события, не нарушая чистоту компонента.