Компоненты и свойства
Что это такое
Экран на React собирается из компонентов, вложенных друг в друга, а данные, которые текут по этому дереву вниз, передаются как свойства. Свойство — это именованный вход компонента, и в JSX его записывают точно так же, как атрибут. Разбить представление на компоненты и решить, что получит каждый, — основная проектная работа в кодовой базе на React.
Почему это важно
Компонент, который отрисовывает целую страницу, работает ровно до первого изменения этой страницы. Дальше правки идут по нарастающей: свойство-флаг, чтобы спрятать раздел; вторая копия той же разметки, отличающаяся одной строкой; обработчик события, протянутый через четыре слоя. Компоненты с чётким набором свойств можно переиспользовать, тестировать в изоляции и переставлять, не заглядывая внутрь. Провести границы правильно сразу гораздо дешевле, чем позже выковыривать их из компонента на 600 строк: само извлечение — простая часть, а болит именно поток данных.
Ключевые идеи
Свойства — входные данные только для чтения
Компонент никогда не должен записывать в свои свойства. Они принадлежат родителю, а изменение ломает предположение, будто рендер — чистая функция своих входов. Если компоненту нужно что-то поменять, он об этом просит родителя.
function Price({ amount, currency }) {
return <strong>{formatMoney(amount, currency)}</strong>;
}
Деструктурировать свойства прямо в списке параметров — обычный стиль, но правило от этого не меняется: читать, а не писать.
Проводите границы там, где живут данные
Ставьте компонент вокруг части данных, а не вокруг части визуального пространства. UserCard, принимающая объект user, переносится легче, чем LeftColumn, тянущаяся к глобальному состоянию. Если одно значение нужно двум компонентам и ни один из них им не владеет, поднимите значение к ближайшему общему родителю — дублировать его не надо.
Компонуйте через children
Вместо добавления свойства showHeader принимайте children и позвольте вызывающему решить, что будет внутри. Так собственная ответственность компонента остаётся небольшой, а вызывающий собирает раскладки, о которых компонент ничего не знает.
function Panel({ title, children }) {
return (
<section className="panel">
<h2>{title}</h2>
{children}
</section>
);
}
<Panel title="Счета">
<InvoiceTable rows={rows} />
</Panel>
Передавайте поведение вниз функцией
Ребёнку, который должен запустить действие, не нужно знать, что это за действие: дайте ему свойство-колбэк, а решение оставьте родителю.
function SaveButton({ onSave, saving }) {
return (
<button onClick={onSave} disabled={saving}>
{saving ? "Сохранение…" : "Сохранить"}
</button>
);
}
Называйте свойство по событию, а не по реализации: onSave, а не handleSaveClick. Префикс on — это то, по чему читатель понимает, что функцию вызывает компонент, а не импортирует он сам.
Держите дерево по возможности плоским
Каждый уровень вложенности добавляет ещё один уровень данных, которые приходится протягивать, а свойство, существующее лишь затем, чтобы его передали дальше, — признак границы, проведённой не там. Три ясных уровня читаются лучше, чем восемь тонких.
Практика
Разбейте страницу на компоненты
Возьмите страницу, которую вы уже построили, и определите три области, владеющие разными данными. Выпишите список свойств для каждого компонента до того, как менять код, затем выполните разбиение.
Готово, когда: ни один компонент не получает больше четырёх свойств и ни один ребёнок не тянется к данным, которых ему не дали.
Замените свойство-флаг композицией
Найдите компонент, принимающий булево значение вроде showFooter и отрисовывающий при нём дополнительную разметку. Замените флаг на children или свойство-слот и обновите каждое место вызова.
Готово, когда: компонент больше не упоминает удалённую возможность, а каждое место вызова по-прежнему отрисовывает то же, что и раньше.
Частые ошибки
- Изменение объекта-свойства — данные родителя меняются у него за спиной, и следующий рендер с ними не согласен; исправить: копируйте нужные части и передавайте новый объект наверх через колбэк.
- Протягивание свойств через компоненты, которые их игнорируют — каждый средний слой теперь знает о возможности, которой не пользуется; исправить: перенесите значение ближе к тем, кто его читает, или уберите слой композицией.
- Компонент на каждый визуальный блок — дерево становится глубже модели данных, и каждое изменение пересекает четыре файла; исправить: группируйте по владению данными, а не по границам пикселей.
- Имена колбэков по их реализации —
handleSaveClickничего не говорит читателю о том, когда он срабатывает; исправить: называйте по событию, напримерonSave. - Новый объект или функция на каждом рендере без нужды — дочерние компоненты, которые сравнивают свойства, видят изменение каждый раз; исправить: передавайте устойчивое значение там, где это важно, и принимайте новое там, где нет.
Что читать дальше
- Рендеринг и JSX (02-frontend/29-react/01-rendering-and-jsx) — перечитайте раздел о согласовании:
key— решение, близкое к свойствам, и его место рядом с границами компонентов. - Состояние и хуки (02-frontend/29-react/03-state-and-hooks) — свойства текут вниз, состояние остаётся на месте; о том, где состоянию жить, — в следующем уроке.
- UI-компоненты (02-frontend/12-ui-components) — как превратить набор переиспользуемых компонентов в осознанную библиотеку.