17 - Зачем компоненту Props? Принцип Information Expert (GRASP)

Оценить качество материала и подачу материала автором видео:

Front-end

Трудоустройтесь middle front-end разработчиком на React JS (TypeScript) за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Front-end

Back-end

Трудоустройтесь middle back-end разработчиком за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Back-end

Карьерный бустер

Получите коммерческий опыт на реальных стартапах, прокачайте tech & soft навыки, научитесь работать в команде, проходить собеседования и получите первую работу в IT!

Перейти на курс
Карьерный бустер

Основы Front-end

Сделайте первый шаг в IT, освоив базовые знания разработки и научившись создавать небольшие проекты на JavaScript

Перейти на курс
Основы Front-end

Основы Back-end

Сделайте первый шаг в IT, освоив базовые знания разработки. Без опыта. Без математики. Только практика: JavaScript, SQL, Node JS, база данных

Перейти на курс
Основы Back-end

📦 React Пропсы

Автор конспекта: Андрей Комаров

🚀 Введение

Приложение в React строится из компонентов — строительных блоков. Эти компоненты могут состоять как из простых JSX-элементов (аналогов HTML-тегов), так и из других компонентов. На верхнем уровне всегда есть App-компонент, в который вложены остальные.

React-приложения строятся из компонентов - строительных блоков пользовательского интерфейса:

tsx
// Древовидная структура компонентов
<App>
  <TrackList>
    <TrackItem />
    <TrackItem />
    <TrackItem />
  </TrackList>
  <TrackDetail />
</App>

Ключевые принципы:

  • Иерархия: Компоненты образуют древовидную структуру
  • Корневой компонент: App - верхний уровень приложения
  • Вложенность: Компоненты могут содержать другие компоненты или JSX-элементы
  • Переиспользуемость: Компоненты проектируются так, чтобы их можно было использовать в разных местах приложения.
  • Изоляция: Каждый компонент инкапсулирует свою разметку, стили и поведение (состояние, обработчики событий).
  • Пропсы (props): Позволяют передавать данные от родителя к дочернему компоненту.
  • Состояние (state): Определяет, как компонент реагирует на изменения данных внутри себя.
  • Чистота функций: Функциональные компоненты часто реализуются как «чистые» — они возвращают UI, основываясь только на входных данных (props).

React отвечает за отрисовку UI на основе данных, используя алгоритм рендера.

Данные — первичны, именно они определяют внешний вид интерфейса.

🏗️ Компоненты и данные

  • Компоненты могут быть составными: один компонент включает в себя другие.
  • Данные внутри компонента могут храниться с помощью useState. Это называется локальный state.
  • Однако часто данные должны быть доступны нескольким компонентам. Для этого их поднимают выше — в родительский компонент.

Принцип: Информационный эксперт — хранит данные и функции для их изменения там, где это логично и удобно

💓 Данные - сердце React

React без данных — это просто картинка. Когда появляются данные (state) — всё оживает.

  • State хранит локальные данные (состояние).
  • Каждый экземпляр компонента имеет собственное состояние.
  • Где хранить state? У того, кто является информационным экспертом.

📦 Props ('Пропсы')

Props (сокращение от properties — «свойства») — это механизм для передачи данных от родительского компонента к дочернему в React и других подобных фреймворках (Vue, Svelte и т.д.).

  • Представьте, что компоненты — это функции, а props — это их аргументы.

🔑 Ключевые характеристики Props

  1. Однонаправленный поток данных: Данные передаются только сверху вниз, от родителя к ребенку. Данные передаются по ссылке, а не копируются. 📤📥
⤵️

Передача данных вниз по иерархии может идти через несколько уровней — это называется props drilling.

  1. Только для чтения (Read-Only): Компонент-получатель не может изменять (мутировать) свои props. Он должен рассматривать их как константы. 📖

  2. Любые типы данных: В props можно передавать не только строки и числа, но и массивы, объекты, функции и даже другие компоненты. 🎭

🎭 Типы компонентов

Smart/Container Components / Контейнерные компоненты ("Умные")

  • Smart/Container Components — управляют состоянием, загружают данные, принимают решения.
  • Presentational/Dumb Components — только принимают данные через пропсы и отображают их.
tsx
function TrackList() {
  const [tracks, setTracks] = useState([])
 
  useEffect(() => {
    // Загрузка данных
    fetchTracks().then(setTracks)
  }, [])
 
  return <TrackList tracks={tracks} />
}

Характеристики:

  • Управляют состоянием 🎛️
  • Содержат бизнес-логику 💼
  • Загружают данные 🌐
  • Передают данные презентационным компонентам 📤

🎨 Presentational/Dumb Components / Презентационные компоненты ("Глупые")

tsx
function TrackList({ tracks }) {
  return (
    <ul>
      {tracks.map((track) => (
        <TrackItem key={track.id} track={track} />
      ))}
    </ul>
  )
}

Характеристики:

  • Получают данные через пропсы 📥
  • Отвечают только за отображение 🖼️
  • Не содержат собственного состояния ⚡
  • Легко переиспользуются 🔄

Сегодня строгого разделения почти не придерживаются — часто встречаются смешанные варианты: часть логики и часть отображения.

⚠️

React-компоненты должны стремиться быть чистыми функциями (pure-function):

  • не изменять входящие данные,
  • всегда возвращать одинаковый результат при одинаковых входных данных.

🔄 Процесс перерисовки (Рендер)

Когда компонент получает новые пропсы? 📬

Только при перерисовке родительского компонента

Цепочка перерисовок:

tsx
  // 1. Изменение состояния в App
function App() {
  const [selectedId, setSelectedId] = useState(1)
 
  // 2. Перерисовка App
  // 3. Перерисовка всех детей
  return (
    <TrackList items={tracks} />
    <TrackDetail track={findTrack(selectedId)} />
  )
}

Особенности:

  • Перерисовывается родительский компонент и все потомки (вложенные компоненты) 🌳

🎯 Локальная перерисовка

tsx
export function TrackItem({ props }) {
  const [likes, setLikes] = useState(0) // Локальное состояние
 
  return (
    <li>
      {props.item.title} - <span onClick={() => setLikes(likes + 1)}>likes: {likes}</span>
    </li>
  )
}

Особенности:

  • Перерисовывается только этот компонент
  • Родитель и соседи не затрагиваются
  • Используются те же пропсы

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

Пример: список треков и отдельные элементы списка.

  1. Родительский компонент App хранит массив треков.
App.tsx
import { useState } from "react"
import { TrackList } from "./components/TrackList.tsx"
import { TrackDetail } from "./components/TrackDetail.tsx"
 
// Родительский компонент - информационный эксперт
export function App() {
  // храним данные в App (пока)
  const [tracks] = useState([
    { id: 1, title: "Bruno Mars - Uptown Funk" },
    { id: 2, title: "Eminem - Lose Yourself" },
    { id: 3, title: "Rihanna - Diamonds" },
  ])
 
  const [selectedTrackId, setSelectedTrackId] = useState(1)
 
  const selectedTrack = tracks.find((track) => track.id === selectedTrackId)
 
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "10px", margin: "20px" }}>
      <button
        onClick={() => {
          setSelectedTrackId(selectedTrackId + 1)
        }}
      >
        Next
      </button>
      <hr />
      <TrackList items={tracks} /> {/*передаём все данные в TrackList*/}
      <hr />
      <TrackDetail track={selectedTrack} /> {/*передаём selectedTrack в TrackDetail*/}
    </div>
  )
}
  1. Он пробрасывает их в компонент TrackList.
TrackList.tsx
// Компонент списка - транзитный
export function TrackList(props) {
  return (
    <ul>
      {/*мапимся по трекам и передаём конкретный трек в TrackItem*/}
      {props.items.map((track) => (
        <TrackItem key={track.id} item={track} />
      ))}
    </ul>
  )
}
🔑
Не забывайте передавать key при рендере списков
  1. TrackList рендерит список, прокидывая каждый трек в TrackItem.
TrackItem.tsx
// Компонент элемента - "глупый"
export function TrackItem(props) {
  const [likes, setLikes] = useState(0) // Локальное состояние
 
  return (
    <li>
      {/*достаём и отображаем данные*/}
      {props.item.title} - <span onClick={() => setLikes(likes + 1)}>likes: {likes}</span>
    </li>
  )
}
  1. TrackItem получает данные через props и отображает.
TrackDetail.tsx
// Компонент деталей
export function TrackDetail(props) {
  if (!props.track) {
    return <div>No track</div>
  }
 
  return (
    <div>
      <h3>{props.track.title}</h3> {/*достаём и отображаем данные*/}
      <p>id: {props.track.id}</p>
    </div>
  )
}

⚠️ ⚠️ ⚠️ Важные моменты:

  • ❗Данные, пришедшие в пропсах, должны использоваться только для отображения, а не изменяться.
  • ❗Можно передавать как отдельные свойства (например, id, title), так и целый объект. Всё зависит от задачи.
  • ❗В списках необходимо использовать уникальный key, чтобы React правильно отслеживал элементы. Для этого идеально подходит id. Если использовать index, это может привести к проблемам, т.к. при добавлении / удалении элементов из массива индексы могут меняться.
tsx
// ✅ ПРАВИЛЬНО
{
  tracks.map((track) => <TrackItem key={track.id} item={track} />)
}
 
// ❌ НЕПРАВИЛЬНО
{
  tracks.map((track, index) => <TrackItem key={index} item={track} />)
}

🔗 Взаимосвязь компонентов

  • Родитель зависит от дочерних компонентов, потому что импортирует их.
  • "Зависимость" означает:
  • ✅ Родитель содержит дочерние компоненты в своем JSX
  • ✅ Без дочерних компонентов родитель не был бы полным
  • ✅ Изменения в дочерних компонентах могут влиять на родителя
  • Поток данных и управление идут от родителя к ребёнку.
  • Дети не знают о родителе, они только принимают данные.

🔍 Что на самом деле означает "зависит":

  1. Статическая зависимость на этапе компиляции
tsx
// Родительский компонент
import Child from "./Child" // ← ЗАВИСИМОСТЬ
 
function Parent() {
  return <Child /> {/* Без импорта Child здесь был бы undefined */}
}
tsx
// Если дочерний компонент отсутствует/сломан
import Child from "./Child" // ❌ Ошибка компиляции
 
function Parent() {
  return <Child /> // ❌ Parent не может быть собран и отрисован. Parent упадёт с ошибкой
}
  1. Зависимость в дереве рендера

Родительский компонент "знает" о дочерних, потому что они являются частью его JSX:

tsx
function Parent() {
  // Родитель "зависит" от Child в том смысле,
  // что не может отрендериться корректно без него
  return (
    <div>
      <Header /> // ← Зависимость
      <Content /> // ← Зависимость
      <Footer /> // ← Зависимость
    </div>
  )
}

🎯 Итог

  • В React всё строится из компонентов.
  • Данные могут храниться локально или подниматься выше.
  • Основной механизм передачи данных вниз — props.
  • Компоненты делятся на умные 🧠 (управляют состоянием) и простые 🎨 (отрисовывают).
  • При проектировании важно определить, где будет храниться состояние и как данные будут передаваться вниз.
  • Props drilling — естественный процесс, который нужно уметь использовать, прежде чем оптимизировать.

⚡ Производительность и оптимизация

Влияние структуры данных на производительность

  • Поднятие состояния наверх может вызывать избыточные перерисовки

  • Локальное состояние уменьшает область перерисовки

  • Правильное расположение состояния улучшает производительность

💡 Рекомендации по оптимизации

  1. Декомпозиция --> разбиение на мелкие компоненты

  2. Локальное состояние --> для данных, нужных только одному компоненту

  3. Поднятие состояния --> для данных, нужных нескольким компонентам

  4. Избегание избыточных пропсов --> передавайте только нужные данные


🏠 Домашнее задание

Цель задания:

Сформировать глубокое понимание механизма props в React и научиться правильно передавать данные между компонентами для создания гибких и переиспользуемых интерфейсов.

Задание 1

Отрисовка данных и пробрасывание пропсов

Создай новое приложение в котором будешь проделывать домашнее задание либо пиши код в уже существующем приложении

  1. Создай компонент UserCard.tsx, который принимает следующие props:
  • id (number) - идентификатор пользователя
  • name (string) - имя пользователя
  • age (number) - возраст
  • email (string) - электронная почта
  • avatar (string) - URL аватарки

Компонент должен отображать карточку пользователя с этой информацией.

  1. Создай компонент Users.tsx в котором используя компонент UserCard.tsx, пробросив необходимые пропсы, отрисуй пользователей
Users.tsx
export function Users() {
  const users = [
    {
      id: 1,
      name: "John",
      age: 32,
      email: "john@gmail.com",
      avatar: "https://tinyurl.com/4ez2s7mt",
    },
    {
      id: 2,
      name: "Alice",
      age: 17,
      email: "alice@yahoo.com",
      avatar: "https://tinyurl.com/ynyx9nhu",
    },
    { id: 3, name: "Mike", age: 44, email: "mike@hotmail.com" },
    {
      id: 4,
      name: "Sarah",
      age: 29,
      email: "sarah@gmail.com",
      avatar: "https://tinyurl.com/yyktspmh",
    },
  ]
 
  return <div>{/*...UserCard...*/}</div>
}
💡
Стилизуй карточку по своему желанию

Итоговый результат 🚀

res17-1

Задание 2

Проверка на возраст и аватар

Расширь задание 1, добавив следующие условия

  • Если avatar не передан, показывай заглушку "no photo"

В качестве заглушки используй placehold (const defaultAvatar = 'https://placehold.co/128?text=no+photo')

  • Если age меньше 18, добавь к возрасту значок "🔞"

Итоговый результат 🚀

res17-2

Задание 3

Создание списка продуктов. Двойная передача пропсов

Создайте приложение для отображения списка продуктов

  1. Главный компонент App должен:
  • Хранить массив продуктов в состоянии
  • Передавать его в ProductList

Данные для тестирования:

App.tsx
function App() {
  const products = [
    { id: 1, name: "Хлеб", price: 30, category: "Выпечка" },
    { id: 2, name: "Молоко", price: 60, category: "Молочные" },
    { id: 3, name: "Яблоки", price: 120, category: "Фрукты" },
  ]
 
  return <div>{/*ProductList*/}</div>
}
  1. Компонент ProductList должен:
  • Принимать props items (массив продуктов)
  • Отрисовывать список ProductItem компонентов
  • Каждый элемент должен иметь уникальный key
  1. Компонент ProductItem должен принимать props:
  • product (object) с полями: id, name, price, category
  • Отображать название, цену и категорию
💡
Стилизуй карточку продукта по своему желанию

Итоговый результат 🚀

res17-3

Задание 4

Добавление товаров в корзину

Расширь задание 3, добавив следующие условия:

  1. Добавь для каждого товара кнопку с названием 'добавить в корзину'

  2. Добавь локальное состояние показывающее добавлен товар в корзину или нет. const [inCart, setInCart] = useState(false)

  3. При нажатии на кнопку 'добавить в корзину' пользователь должен увидеть:

  • Всплывающее сообщение alert в котором пользователь увидит сообщение "Товар добавлен в корзину"
  • Название кнопки изменится на 'товар в корзине' и цвет кнопки станет желтым
  1. При нажатии на кнопку 'товар в корзинеу' пользователь должен увидеть обратную операцию:
  • Всплывающее сообщение alert в котором пользователь увидит сообщение "Товар удален из корзины"
  • Название кнопки изменится на 'добавить в корзину' и цвет кнопки станет белым

Итоговый результат 🚀

res17-4

Задание 5

Добавление товаров в корзину

Поставь в каждом компоненте логи (console.log) и пронализируй в какой момент компонент рендерится. Нажми на кнопку 'добавить в корзину' и также предугай какие логи ты увидишь

  • console.log('🔶 App')
  • console.log('📋 ProductList')
  • console.log('🧺 ProductItem')

🤔 Вопросы для самопроверки

  1. Что такое props и как они передаются в компоненты?
  2. В чем разница между props и state?
  3. Как компонент может получить новые props?
  4. Что такое props drilling и какие проблемы он создает?
  5. Когда происходит перерендер дочерних компонентов?

💡

🔶 Помни: каждая задача отражает реальные паттерны из коммерческой разработки — от простых карточек до сложных систем фильтрации.

🔶 Экспериментируй: изменяй props, добавляй console.log в компоненты и наблюдай за порядком рендеров.

🔶 Удачи! Понимание props — это фундамент для работы с любой React-архитектурой. 🚀

Боевой маршрут (React Путь Самурая: без альтернатив)

Видеоурок - 19 видео из 30