Передача пропсов в компонент

React-компоненты используют пропсы, чтобы общаться друг с другом. Каждый родительский компонент через пропсы может передать информацию в дочерние компоненты. Пропсы похожи на HTML-атрибуты, но позволяют вам передавать через них любые JavaScript-значения, включая объекты, массивы и функции.

You will learn

  • Как передать пропсы в компонент
  • Как использовать пропсы в компоненте
  • Как определять значения по умолчанию
  • Как передать JSX в компонент
  • Как значения пропсов изменяются

Знакомство с пропсами

Пропсы — это данные, которые вы передаёте вместе с JSX-тегом. Например, className, src, alt, width, и height — это пропсы, которые могут быть использованы с <img>:

function Avatar() {
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/1bX5QH6.jpg"
      alt="Линь Ланьинг"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return (
    <Avatar />
  );
}

Пропсы, которые можно передать с тегом <img>, уже определены (ReactDOM следует HTML-стандартам). Однако, вы можете указать пропсы для ваших собственных компонентов, таких как <Avatar>, и таким образом настроить их. Давайте разберёмся как!

Передача пропсов в компонент

В следующем примере компонент Profile ничего не передаёт в дочерний компонент Avatar:

export default function Profile() {
return (
<Avatar />
);
}

Вы можете определить пропсы для компонента Avatar в два шага.

Шаг 1: передать пропсы в дочерний компонент

Сначала передадим пропсы в Avatar. Например, давайте определим два пропса: person (объект) и size (число):

export default function Profile() {
return (
<Avatar
person={{ name: 'Линь Ланьинг', imageId: '1bX5QH6' }}
size={100}
/>
);
}

Note

Если вас смущают двойные фигурные скобки после person=, напоминаем, что они просто обозначают объект внутри фигурных скобок JSX-кода.

Теперь вы можете прочитать эти пропсы внутри компонента Avatar.

Шаг 2: прочитать пропсы внутри дочернего компонента

Вы можете перечислить имена пропсов person, size через запятую между ({ и }) сразу после объявления function Avatar. Это позволяет обращаться к значениям пропсов в коде компонента Avatar так же, как к переменным.

function Avatar({ person, size }) {
// теперь person и size доступны здесь
}

Добавьте в Avatar логику, которая использует person и size для рендеринга, и всё готово.

Теперь вы можете сконфигурировать Avatar, чтобы получать разные результаты рендеринга с разными пропсами. Попробуйте поиграть со значениями пропсов!

import { getImageUrl } from './utils.js';

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
  return (
    <div>
      <Avatar
        size={100}
        person={{ 
          name: 'Кацуко Сарухаси', 
          imageId: 'YfeOqp2'
        }}
      />
      <Avatar
        size={80}
        person={{
          name: 'Аклилу Лемма', 
          imageId: 'OKS67lh'
        }}
      />
      <Avatar
        size={50}
        person={{ 
          name: 'Линь Ланьинг',
          imageId: '1bX5QH6'
        }}
      />
    </div>
  );
}

Пропсы позволяют вам думать о родительском и дочернем компонентах как об отдельных сущностях. К примеру, вы можете изменить проп person или size внутри Profile и не думать о том, как Avatar их использует. Аналогично, вы можете изменить логику использования этих пропсов внутри Avatar, не заглядывая в Profile.

Вы можете думать о пропсах как о ручках регулировки прибора, которые служат для настройки вашего компонента. Они выполняют ту же роль, что и аргументы для функций. Фактически, пропсы и есть аргумент для вашего компонента! Функция React-компонента принимает единственный аргумент — объект с пропсами:

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

Обычно вам не нужен объект props сам по себе, поэтому вы будете деструктурировать его в отдельные пропсы.

Pitfall

Не забывайте про пару фигурных скобок { и } внутри круглых ( и ), когда объявляете пропсы:

function Avatar({ person, size }) {
// ...
}

Такой синтаксис называется “деструктурирующим присваиванием” или “деструктуризацией” и равнозначен чтению свойств из параметра функции:

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

Определение значения по умолчанию для пропа

Если вы хотите дать пропу значение по умолчанию, когда значение не было определено, вы можете это сделать при помощи деструктуризации, добавив = и значение по умолчанию после параметра:

function Avatar({ person, size = 100 }) {
// ...
}

Теперь, если <Avatar person={...} /> будет использован без пропа size, size примет значение 100.

Значение по умолчанию используется, только если проп size пропущен или передан size={undefined}. Но если вы передаёте size={null} или size={0}, значение по умолчанию не будет использовано.

Передача пропсов в JSX с оператором расширения

Иногда пропсы передаются сквозь компонент:

function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}

В этом нет ничего плохого и такое дублирование может быть уместным. Но в то же время хочется писать код лаконичнее. Некоторые компоненты передают все их пропсы дочерним компонентам так, как это делает Profile с Avatar. Так как непосредственно сам компонент не использует пропсы, имеет смысл использовать более краткую запись и оператор расширения:

function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}

Так вы можете передать все пропсы компонента Profile в компонент Avatar без перечисления их имён.

Используйте оператор расширения сдержанно. Если вы используете этот оператор в каждом компоненте, то, скорее всего, что-то пошло не так. Часто это говорит о том, что вам нужно разделить компоненты и передать дочерние через JSX. Давайте посмотрим как!

Передача JSX через проп children

Обычная практика вкладывать стандартные элементы браузера друг в друга:

<div>
<img />
</div>

Вы можете сделать то же самое и для ваших компонентов:

<Card>
<Avatar />
</Card>

Когда вы вкладываете что-то внутрь JSX-тега, родительский компонент получит это в специальном пропе children. В примере ниже компонент Card получает проп children и оборачивает его содержимое, то есть компонент <Avatar />, в div-элемент:

import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Кацуко Сарухаси',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

Попробуйте поменять <Avatar> внутри <Card> на любой текст и посмотреть как компонент Card оборачивает содержимое. Ему необязательно “знать” что рендерится внутри него. Вы ещё увидите множество применений этого гибкого паттерна.

Вы можете представить себе компонент с пропом children в виде “эклера”, в который родительский компонент “добавляет начинку” через JSX. Вы будете часто использовать проп children для компонентов-обёрток: панелей, гридов и т.д.

Карточка (Card) похожа на пазл со слотом для "дочерних" кусочков наподобие текста и аватара (Avatar)

Illustrated by Rachel Lee Nabors

Как пропсы изменяются

Компонент Clock ниже принимает два пропса из родительского компонента: color и time. (Код родительского компонента опущен, потому что он использует состояние, на котором мы не хотим сейчас заострять внимание.)

Попробуйте поменять цвет в выпадающем меню ниже:

export default function Clock({ color, time }) {
  return (
    <h1 style={{ color: color }}>
      {time}
    </h1>
  );
}

Этот пример показывает, что компонент может получать различные значения пропсов в разные моменты времени. Пропсы не всегда статические! В этом примере проп time меняется каждую секунду, а проп color меняется при выборе цвета в меню. Пропсы отражают данные компонента в конкретный момент, а не только в начале его существования.

Однако, пропсы неизменяемы (immutable) —- термин в информатике, обозначающий объект, который не может быть изменён после создания. Когда компоненту нужно поменять его пропсы (например, в результате действий пользователя или обновления данных), он должен “спросить” у родительского компонента другие пропсы -— новый объект! Старые пропсы окажутся ненужными, и в итоге движок JavaScript освободит память, которую они занимали.

Не пытайтесь “изменить пропсы”. Когда вам нужно реагировать на действия пользователя (например, они меняют цвет), вы должны “установить состояние”, о котором вы можете узнать в статье Состояние: память компонента.

Recap

  • Чтобы передать пропсы, добавьте их в JSX, по аналогии с HTML-атрибутами.
  • Чтобы прочитать пропсы, используйте деструктурирующее присваивание function Avatar({ person, size }).
  • Вы можете определить значение по умолчанию size = 100, в случае если проп принимает undefined или пропущен.
  • Вы можете передать все пропсы в другой компонент <Avatar {...props} /> с помощью оператора расширения, но не злоупотребляйте этой возможностью!
  • Вложенный JSX, например <Card><Avatar /></Card>, будет передан в компонент Card в специальном пропе с именем children.
  • Пропсы можно только читать: при каждом рендере компонент получает новую версию пропсов.
  • Вы не можете менять пропсы. Используйте состояние компонента, если вам нужно добавить интерактивность.

Challenge 1 of 3:
Выделение компонента

Компонент Gallery содержит очень похожую разметку для двух профилей. Выделите компонент Profile, чтобы не дублировать код. Вам нужно определить пропсы, которые вы передадите в этот компонент.

import { getImageUrl } from './utils.js';

export default function Gallery() {
  return (
    <div>
      <h1>Выдающиеся учёные</h1>
      <section className="profile">
        <h2>Мария Склодовская-Кюри</h2>
        <img
          className="avatar"
          src={getImageUrl('szV5sdG')}
          alt="Мария Склодовская-Кюри"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Профессия: </b> 
            физик и химик
          </li>
          <li>
            <b>Награды: 4 </b> 
            (Нобелевская премия по физике, Нобелевская премия по химии, медаль Дэви, медаль Маттеуччи)
          </li>
          <li>
            <b>Открытия: </b>
            полоний (элемент)
          </li>
        </ul>
      </section>
      <section className="profile">
        <h2>Кацуко Сарухаси</h2>
        <img
          className="avatar"
          src={getImageUrl('YfeOqp2')}
          alt="Кацуко Сарухаси"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Профессия: </b> 
            геохимик
          </li>
          <li>
            <b>Награды: 2 </b> 
            (Премия Мияке по геохимии, Премия Танака)
          </li>
          <li>
            <b>Открытия: </b>
            метод измерения углекислого газа в морской воде
          </li>
        </ul>
      </section>
    </div>
  );
}