В прошлых уроках мы с вами разобрались, что такое компоненты и научились создавать свои собственные «кирпичики» для нашего приложения. Мы уже видели, как здорово дробить интерфейс на независимые, переиспользуемые части. Но пока что наши компоненты были, скажем так, немного «одинокими». Они умели красиво отрисовываться, но не умели общаться друг с другом и были очень статичными.
Сегодня мы исправим эту ситуацию и откроем один из самых важных концептов в React, это Props (сокращение от «properties» — свойства). Именно props позволяют компонентам становиться динамичными, взаимодействовать и превращаться в живой, дышащий организм вашего приложения. Представьте, что если бы каждый кирпичик в доме мог получать инструкции, какого ему быть цвета или размера, вот что такое props для React-компонентов.
Давайте же без лишних предисловий погрузимся в эту увлекательную тему.
Что такое Props?
Props это механизм в React для передачи данных от родительского компонента к дочернему. Давайте разберем это определение по косточкам.
Представьте, что у нас есть компонент Приветствие. В его текущем виде он всегда выводит одно и то же: «Привет, мир!». Но что, если мы хотим, чтобы один раз он поздоровался с Аней, другой раз с Петей, а в третий вообще попрощался? Писать для каждого случая отдельный компонент неэффективно и противоречит самой идее переиспользования. Вместо этого мы можем сделать наш компонент Приветствие универсальным. Мы будем передавать ему имя того, с кем нужно поздороваться и текст приветствия в виде props.
Родительский компонент в этой схеме это тот, кто использует другой компонент внутри себя. А дочерний тот, кого используют. Данные текут строго в одном направлении: сверху вниз, от родителя к ребенку. Это однонаправленный поток данных и он является ключевым принципом архитектуры React, который делает приложения предсказуемыми и легкими для отладки.
Props можно передавать практически что угодно: строки, числа, булевы значения, массивы, объекты и даже… функции! Да-да, вы не ослышались. Функции, переданные через props, позволяют дочернему компоненту «сообщать» родителю о каких-то событиях (например, о том, что пользователь нажал на кнопку). Но обо всем по порядку.
Чтение Props и правила иммутабельности
Теперь давайте посмотрим, как же дочерний компонент получает эти самые props и что с ними можно делать.
Чтение Props
Когда родительский компонент рендерит дочерний, он передает ему props в виде атрибутов, очень похожих на атрибуты HTML-тегов. Внутри же самого дочернего компонента эти props поступают в виде первого аргумента функции (для функциональных компонентов) или через this.props (для классовых компонентов). Мы с вами в основном работаем с функциональными компонентами, поэтому будем рассматривать первый способ.
Давайте создадим тот самый компонент Приветствие.
// Дочерний компонент Greeting function Greeting(props) { return <h1>Привет, {props.name}!</h1>; }
Посмотрите на этот код. Наш компонент Greeting объявлен как функция, которая принимает один аргумент props. props это обычный JavaScript-объект. Когда родитель передает атрибуты, они группируются в этот объект. Внутри JSX мы используем фигурные скобки {}, чтобы «выполнить» JavaScript-выражение и вставить значение свойства props.name в наш заголовок.
Теперь давайте посмотрим, как родительский компонент передает эти данные:
// Родительский компонент App function App() { return ( <div> <Greeting name="Аня" /> <Greeting name="Петя" /> <Greeting name="Мария" /> </div> ); }
Вот так просто! Мы используем компонент Greeting как тег и передаем ему атрибут name с разными значениями. В результате в браузере мы увидим:
Привет, Аня! Привет, Петя! Привет, Мария!
Один компонент, три разных результата! Это и есть суть props и сила переиспользования.
Правила иммутабельности
А теперь очень важное правило, которое нужно запомнить раз и навсегда: Props являются иммутабельными (read-only) внутри дочернего компонента.
Что это значит на практике? Дочерний компонент не может изменять props, которые он получил. Он может только читать их. Представьте, что вы получаете письмо. Вы можете прочитать его содержимое, но вы не можете изменить текст в этом письме. Вы можете сделать его копию и изменять копию, но исходное письмо остается неизменным.
Почему это так важно? Это правило делает поток данных предсказуемым. Если вы видите, что какой-то prop изменился, вы всегда знаете, что это изменение произошло в том месте, где этот prop был объявлен в родительском компоненте. Это избавляет от хаоса и сложной отладки, когда данные могут меняться в любом уголке приложения.
Если дочернему компоненту нужно как-то изменить данные, он должен «попросить» об этом родителя, отправив ему сообщение (через функцию, переданную в props). Родитель, владеющий данными, сам их изменит и передаст новые значения вниз. Мы подробно разберем эту схему в следующих уроках, когда будем говорить о состоянии.
Давайте рассмотрим пример с ошибкой:
function Greeting(props) { props.name = "Максим"; // ОШИБКА! Нельзя изменять props! return <h1>Привет, {props.name}!</h1>; }
Так делать нельзя. React выдаст ошибку, потому что вы пытаетесь изменить иммутабельный объект.
Передача разных типов данных через Props
Как я уже упоминал, через props можно передавать не только строки. Давайте рассмотрим несколько примеров.
Передача чисел
При передаче чисел их нужно оборачивать в фигурные скобки, чтобы отличить от строки.
function Rating(props) { return <p>Рейтинг этого товара: {props.stars} из 5</p>; } // Использование в родителе function App() { return ( <div> <Rating stars={5} /> <Rating stars={4} /> </div> ); }
Если бы мы написали stars="5", то в props.stars пришла бы строка "5", а не число.
Передача булевых значений, массивов и объектов
Булевы значения часто используются для условного рендеринга (о котором мы поговорим в будущем).
function UserProfile(props) { return ( <div> <h2>{props.userName}</h2> {props.isOnline && <span style={{color: 'green'}}>В сети</span>} {props.isPremium && <span> Премиум-пользователь</span>} </div> ); } // Использование function App() { return ( <div> <UserProfile userName="Аня" isOnline={true} isPremium={true} /> <UserProfile userName="Петя" isOnline={false} isPremium={false} /> </div> ); }
Передача массивов и объектов также осуществляется через фигурные скобки.
function TodoList(props) { return ( <ul> {props.items.map((item, index) => <li key={index}>{item}</li>)} </ul> ); } function App() { const myTodos = ['Купить молоко', 'Позвонить маме', 'Заплатить за интернет']; return <TodoList items={myTodos} />; }
Обратите внимание на атрибут key в примере выше. Когда вы рендерите список элементов, React просит вас добавлять уникальный key (ключ) к каждому элементу. Это помогает React эффективно обновлять список при изменениях. Пока мы используем index (индекс элемента в массиве), но в реальных приложениях лучше использовать уникальный id из ваших данных.
Продвинутые приемы работы с Props
Деструктуризация Props
Согласитесь, постоянно писать props. перед каждым свойством не очень удобно. На помощь приходит деструктуризация, возможность JavaScript «извлекать» значения из объектов. Мы можем сразу в параметрах функции «достать» нужные нам свойства.
Сравните:
// Без деструктуризации function Greeting(props) { return <h1>Привет, {props.name}! Тебе {props.age} лет.</h1>; } // С деструктуризацией (гораздо чище!) function Greeting({ name, age }) { return <h1>Привет, {name}! Тебе {age} лет.</h1>; }
Оба варианта работают одинаково, но второй короче, нагляднее и его легче читать. Мы будем активно использовать деструктуризацию в дальнейшем.
Props по умолчанию
Иногда вы хотите, чтобы у вашего компонента были значения props «по умолчанию», на случай, если родитель их не передал. React позволяет это легко сделать.
Для функциональных компонентов есть два основных способа.
Способ 1: Деструктуризация с значениями по умолчанию.
function Greeting({ name = "Незнакомец", age = 0 }) { return <h1>Привет, {name}! Тебе {age} лет.</h1>; } // Использование function App() { return ( <div> <Greeting name="Аня" age={25} /> {/* Привет, Аня! Тебе 25 лет. */} <Greeting /> {/* Привет, Незнакомец! Тебе 0 лет. */} </div> ); }
Способ 2: Использование defaultProps (более устаревший, но все еще встречается).
function Greeting({ name, age }) { return <h1>Привет, {name}! Тебе {age} лет.</h1>; } Greeting.defaultProps = { name: "Незнакомец", age: 0 };
Результат будет таким же. Я рекомендую использовать первый способ, так как он более современный и лаконичный.
Передача функций через Props и обратные вызовы
Как я и обещал, давайте рассмотрим, как дочерний компонент может «общаться» с родителем. Для этого родитель передает в дочерний компонент функцию через props. Дочерний компонент затем может вызвать эту функцию, чтобы сообщить о каком-то событии или передать данные наверх.
Рассмотрим классический пример, кнопка «Лайк».
// Дочерний компонент LikeButton function LikeButton({ isLiked, onLikeClick }) { return ( <button onClick={onLikeClick}> {isLiked ? '❤️ Вам нравится это' : ' Лайк'} </button> ); } // Родительский компонент App function App() { // Представим, что у нас есть состояние для лайка (о состоянии в следующих уроках!) const [liked, setLiked] = useState(false); const handleLikeClick = () => { setLiked(!liked); // Меняем состояние на противоположное }; return ( <div> <p>Посмотрите на эту кошечку!</p> <img src="https://example.com/cat.jpg" alt="Милая кошка" /> <LikeButton isLiked={liked} onLikeClick={handleLikeClick} /> </div> ); }
Давайте разберем, что здесь происходит:
-
В родительском компоненте
Appмы объявляем функциюhandleLikeClick. Эта функция знает, как изменить состояние лайка. -
Мы передаем эту функцию в дочерний компонент
LikeButtonв виде prop с именемonLikeClick. -
Внутри
LikeButtonмы вешаем эту переданную функцию на событиеonClickкнопки. -
Когда пользователь нажимает на кнопку, срабатывает
onClick, который вызываетonLikeClick, который, в свою очередь, является ссылкой наhandleLikeClickиз родителя. -
Родитель выполняет
handleLikeClick, изменяет свое состояниеlikedи React автоматически перерисовывает иAppиLikeButtonс новыми props (isLikedстанет противоположным значением).
Это и есть паттерн «обратный вызов» (callback). Поток данных идет снизу вверх, но управляется всегда сверху вниз. Родитель полностью контролирует логику, а ребенок лишь уведомляет о событиях.
Практические задачи для закрепления
Давайте закрепим знания на нескольких задачах. Попробуйте решить их самостоятельно, прежде чем смотреть на решение.
Задача 1: Карточка пользователя
Создайте компонент UserCard, который принимает следующие props: avatar (URL аватарки), firstName, lastName, profession. Используйте этот компонент в App, чтобы отобразить информацию о нескольких разных пользователях.
Подсказка. Не забудьте про деструктуризацию!
Решение задачи 1
// Компонент UserCard function UserCard({ avatar, firstName, lastName, profession }) { return ( <div style={{ border: '1px solid #ccc', padding: '10px', margin: '10px', borderRadius: '5px' }}> <img src={avatar} alt={`${firstName} ${lastName}`} width="100" /> <h3>{firstName} {lastName}</h3> <p>Профессия: {profession}</p> </div> ); } // Родительский компонент App function App() { return ( <div> <UserCard avatar="https://i.pravatar.cc/150?img=1" firstName="Иван" lastName="Петров" profession="Фронтенд-разработчик" /> <UserCard avatar="https://i.pravatar.cc/150?img=2" firstName="Мария" lastName="Сидорова" profession="Дизайнер" /> </div> ); } export default App;
Задача 2: Список дел (расширенная)
У вас есть массив задач в родительском компоненте App:
const tasks = ['Изучить React', 'Написать приложение', 'Сдать проект'];
Создайте компонент TaskList, который принимает массив задач через prop tasks и отображает их в виде списка (<ul>). Каждая задача должна быть в теге <li>. Не забудьте про ключ key!
Решение задачи 2:
// Компонент TaskList function TaskList({ tasks }) { return ( <ul> {tasks.map((task, index) => ( <li key={index}>{task}</li> ))} </ul> ); } // Родительский компонент App function App() { const tasks = ['Изучить React', 'Написать приложение', 'Сдать проект']; return ( <div> <h2>Мой список дел:</h2> <TaskList tasks={tasks} /> </div> ); } export default App;
Задача 3: Кнопка-счетчик
Создайте компонент CounterButton. Родительский компонент App должен передавать ему два props: count (текущее значение счетчика) и onIncrement (функцию для увеличения счетчика). При нажатии на кнопку внутри CounterButton счетчик в родителе должен увеличиваться.
Это задание уже затрагивает тему состояния, но попробуйте собрать его по аналогии с примером про лайк. В следующем уроке мы детально разберем useState.
Решение задачи 3:
import { useState } from 'react'; // Дочерний компонент CounterButton function CounterButton({ count, onIncrement }) { return ( <button onClick={onIncrement}> Кликнуто раз: {count} </button> ); } // Родительский компонент App function App() { const [clickCount, setClickCount] = useState(0); const handleIncrement = () => { setClickCount(clickCount + 1); }; return ( <div> <CounterButton count={clickCount} onIncrement={handleIncrement} /> </div> ); } export default App;
Итоги и что дальше?
Вы только что освоили один из краеугольных камней React, механизм props. Теперь вы умеете:
-
Передавать данные от родительского компонента к дочернему.
-
Читать и использовать эти данные внутри дочернего компонента.
-
Понимаете и соблюдаете правило иммутабельности props.
-
Знаете, как передавать разные типы данных: строки, числа, массивы, объекты и функции.
-
Используете деструктуризацию для чистоты кода.
-
Задавать значения props по умолчанию.
-
Организовывать простое взаимодействие между компонентами с помощью функций-колбэков.
Теперь ваши компоненты перестали быть статичными и научились общаться. Вы можете создавать по-настоящему переиспользуемые и гибкие «кирпичики» для вашего UI.
Но чего-то все еще не хватает. Наши примеры с кнопкой-счетчиком и лайком намекают на это. Данные в них как-то меняются… но где они хранятся? Как React узнает, что нужно перерисовать интерфейс при их изменении? Ответ на эти вопросы, состояние (State).
В следующих уроках, мы изучим хук useState, который наделяет функциональные компоненты «памятью» и реактивностью. Мы научимся создавать данные, которые могут меняться со временем и в ответ на эти изменения React будет автоматически обновлять то, что мы видим на экране.
А если вы хотите не пропускать уроки и иметь доступ ко всем материалам в одном месте, то вам сюда:
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


