Компоненты и свойства

Что это такое

Экран на 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) — как превратить набор переиспользуемых компонентов в осознанную библиотеку.