Почему типизация пропсов важна?
Пропсы — это основной способ передачи данных в React-компоненты. Они позволяют различным компонентам общаться друг с другом. Однако, если мы не будем типизировать пропсы, мы можем легко столкнуться с массой проблем:
- Ошибки из-за несоответствия типов. Например, вместо объекта мы передадим строку — и приложение "ломается".
- Трудности при сопровождении кода. С ростом проекта отсутствие типизации делает код менее читаемым и понятным.
- Нет автодополнения в IDE. Без типизации разработчики теряют мощные инструменты подсказок.
К счастью, в React у нас есть два мощных инструмента для работы с типами пропсов: интерфейсы TypeScript и PropTypes. Давайте их разбирать.
Типизация пропсов с интерфейсами TypeScript
Интерфейсы для типизации пропсов
TypeScript позволяет нам описывать типы пропсов с помощью интерфейсов. Интерфейсы — это наши "контрактные обязательства" между компонентом и данными, которые в него передаются.
Пример простого компонента с типизацией пропсов:
import React from 'react';
// Создаем интерфейс для пропсов
interface GreetingProps {
name: string; // Ожидаем строку
age?: number; // Возраст опциональный, знак "?" говорит об этом
}
// Используем интерфейс в компоненте
const Greeting: React.FC<GreetingProps> = ({ name, age }) => {
return (
<div>
<h1>Привет, {name}!</h1>
{age && <p>Тебе {age} лет.</p>}
</div>
);
};
// Используем компонент
export const App = () => {
<Greeting name="Иван" age={25} />;
}
Что здесь происходит:
- Мы создали интерфейс
GreetingProps, который описывает ожидаемые пропсы. - Мы явно указали
React.FC<GreetingProps>для компонента, чтобы TypeScript мог проверить соответствие переданных данных интерфейсу. - Проп
ageпомечен как опциональный. Если его не передать — всё будет работать.
Передача массивов, объектов и сложных типов
Что если наш компонент ожидает более сложные типы данных, такие как массивы или объекты? TypeScript справляется с этим на ура:
interface TodoItem {
id: number;
title: string;
completed: boolean;
}
interface TodoListProps {
items: TodoItem[]; // Массив объектов типа TodoItem
}
const TodoList: React.FC<TodoListProps> = ({ items }) => {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.title} {item.completed ? "✅" : "❌"}
</li>
))}
</ul>
);
};
// Пример данных и использования
const todos = [
{ id: 1, title: "Написать лекцию по TypeScript", completed: true },
{ id: 2, title: "Сделать рефакторинг кода", completed: false },
];
export const App = () => <TodoList items={todos} />;
- Здесь мы описали сложный объект
TodoItemв интерфейсе и указали, что компонент получает массив таких объектов. - TypeScript автоматически проверит структуру каждого переданного объекта.
Необязательные и значения по умолчанию
Иногда пропсы могут отсутствовать, и в таком случае уместно задавать значения по умолчанию через defaultProps или с помощью ES6-деструкции.
interface GreetingProps {
name: string;
age?: number; // Возраст опционален
}
const Greeting: React.FC<GreetingProps> = ({ name, age = 18 }) => {
return (
<div>
<h1>Привет, {name}!</h1>
<p>Тебе {age} лет.</p>
</div>
);
};
// Работает даже без указания "age"
export const App = () => <Greeting name="Мария" />;
PropTypes для валидации данных
TypeScript отлично работает на этапе разработки, но в реальной жизни (в Production) он уже не спасет от ошибок: данные могут прийти неверные. В таких случаях на помощь приходит встроенная библиотека PropTypes.
Валидация пропсов с PropTypes
PropTypes проверяет данные во время выполнения. Вот пример использования:
import PropTypes from 'prop-types';
const Button = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
// Определяем PropTypes
Button.propTypes = {
label: PropTypes.string.isRequired, // Обязательно строка
onClick: PropTypes.func.isRequired, // Обязательно функция
};
// Используем компонент
export const App = () => (
<Button label="Нажми меня" onClick={() => console.log("Клик!")} />
);
Что здесь происходит:
- Мы объявляем ожидаемые типы данных для пропсов прямо в поле
propTypes. - Пропс
labelдолжен быть строкой и обязательно переданisRequired. - Если тип пропса или его наличие не соответствует указанным правилам, в консоли выведется предупреждение.
Поддержка сложных типов и массивов
В PropTypes можно описывать массивы, объекты и их структуры:
const TodoList = ({ todos }) => {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text} {todo.completed ? "✅" : "❌"}
</li>
))}
</ul>
);
};
TodoList.propTypes = {
todos: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
text: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired,
})
).isRequired,
};
// Пример использования
export const App = () => (
<TodoList
todos={[
{ id: 1, text: "Выучить TypeScript", completed: true },
{ id: 2, text: "Сделать домашку", completed: false },
]}
/>
);
arrayOfпроверяет каждый элемент массива.shapeзадает форму объектов, переданных в массиве.
Сравнение TypeScript и PropTypes
Возможно, у вас возник вопрос: "Зачем использовать PropTypes, если есть TypeScript?" Всё зависит от контекста:
| Особенность | TypeScript | PropTypes |
|---|---|---|
| Когда проверяет | На этапе разработки (compile time) | Во время выполнения (runtime) |
| Типы данных | Более мощные, включает union, generics и т.д. | Ограниченные (строка, число, и т.д.) |
| Производство (Prod) | Работает только в разработке | Проверяет в runtime, работает в Prod |
| Легкость изучения | Требует знания TypeScript | Прост в освоении |
В идеале, TypeScript и PropTypes можно использовать вместе. TypeScript заботится о разработке, а PropTypes защищает от реальных ошибок в продакшене.
Типичные ошибки и как их избежать
Отсутствие типизации сложных объектов. Никогда не оставляйте массивы или объекты "неявными". Сделайте интерфейсы для них с TypeScript или используйте
PropTypes.shape.Пропущенные опциональные пропсы. Убедитесь, что опциональные пропсы корректно помечены (
?в TypeScript или безisRequiredв PropTypes). Это избавит от редких багов.Конфликт между defaultProps и TypeScript. Если используете
defaultProps, убедитесь, что TypeScript не считает эти пропсы обязательными. В TypeScript 4.0+ рекомендуют использовать ES6-деструкцию вместоdefaultProps.
Применение на практике
Теперь, когда у вас есть знания о TypeScript и PropTypes, вы можете защитить свои компоненты как на этапе разработки, так во время работы. Это крайне важно в крупных коммерческих проектах, где ошибки и баги стоят времени и денег. Используйте TypeScript для типизации компонентов, а PropTypes для runtime-проверок.
И помните: пропсы — это как границы в отношениях. Лучше сразу договориться, что кому можно передавать, чтобы потом не возникло конфликтов!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ