Типизация компонентов в React
В React компоненты можно воспринимать как конструктор: вы собираете интерфейс из отдельных блоков. Пропсы, передаваемые в компонент, являются "инструкцией" для этого блока — какие атрибуты и данные он должен использовать. TypeScript помогает сделать эти "инструкции" строгими, а значит — надёжными. Давайте разберёмся, как это работает.
Что такое пропсы и зачем их типизировать?
Пропсы (props) — это данные, которые вы передаёте в компонент для его отображения или работы. Например:
function Greeting(props: { name: string }) {
return <h1>Привет, {props.name}!</h1>;
}
Пропсы здесь — это объект, содержащий одно поле name с типом string. Типизация позволяет IDE (например, VS Code) сразу подсказать вам, что передать в компонент, а если вы ошиблись, она укажет на проблему ещё до запуска.
Почему это важно? Если вы передадите что-то странное, например, число вместо строки, TypeScript вас остановит! Без типизации ошибка всплывёт только в браузере, а до этого её ещё надо отловить.
Использование интерфейсов для пропсов
Типы в TypeScript можно задавать разными способами. Но для React компонентов на практике чаще всего используются интерфейсы. Интерфейсы — это способ описать структуру объекта. Вот как это выглядит:
interface GreetingProps {
name: string;
}
function Greeting(props: GreetingProps) {
return <h1>Привет, {props.name}!</h1>;
}
// Использование компонента
<Greeting name="Иван" />;
interface GreetingPropsописывает, какие именно пропсы принимает компонентGreeting.- Если вы попытаетесь передать что-то неподходящее, TypeScript сразу сообщит об ошибке.
Пример ошибки:
<Greeting name={42} />; // Ошибка: Type 'number' is not assignable to type 'string'.
Необязательные пропсы и значения по умолчанию
Иногда пропсы могут быть необязательными. Например, если мы хотим добавить поле age, но оно не всегда нужно:
interface GreetingProps {
name: string;
age?: number; // ? обозначает, что пропс необязателен
}
function Greeting(props: GreetingProps) {
return (
<div>
<h1>Привет, {props.name}!</h1>
{props.age && <p>Тебе {props.age} лет.</p>}
</div>
);
}
// Использование компонента
<Greeting name="Иван" />;
<Greeting name="Иван" age={25} />;
Если age не передан, код внутри проверки props.age && просто не выполнится.
Значения по умолчанию для пропсов
Если вы хотите, чтобы пропсы имели значение по умолчанию, но при этом оставались необязательными, добавьте их значения в теле функции:
interface GreetingProps {
name: string;
age?: number;
}
function Greeting({ name, age = 18 }: GreetingProps) {
return (
<div>
<h1>Привет, {name}!</h1>
<p>Тебе {age} лет.</p>
</div>
);
}
// Даже если возраст не указан, мы покажем 18
<Greeting name="Иван" />;
Здесь age = 18 устанавливает значение по умолчанию для пропса age.
Передача функций через пропсы
Компоненты могут не только отображать данные, но и вызывать функции. Например:
interface ButtonProps {
label: string;
onClick: () => void;
}
function Button(props: ButtonProps) {
return <button onClick={props.onClick}>{props.label}</button>;
}
// Использование компонента
function handleClick() {
alert("Кнопка нажата!");
}
<Button label="Нажми меня" onClick={handleClick} />;
onClick: () => void— это функция, которая не принимает аргументов и ничего не возвращает.- Если кто-то попытается передать, например, строку вместо функции, TypeScript сработает как сторожевой пёс и начнёт лаять.
Комплексные структуры данных
Если ваши компоненты принимают массивы, объекты или вложенные структуры, интерфейсы снова приходят на помощь.
Допустим, у нас есть список задач, и мы передаём его как пропс:
interface Task {
id: number;
title: string;
completed: boolean;
}
interface TaskListProps {
tasks: Task[];
}
function TaskList({ tasks }: TaskListProps) {
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.title} - {task.completed ? "Сделано" : "В процессе"}
</li>
))}
</ul>
);
}
// Использование компонента
const tasks = [
{ id: 1, title: "Выучить TypeScript", completed: true },
{ id: 2, title: "Написать код", completed: false },
];
<TaskList tasks={tasks} />;
Здесь интерфейс Task описывает, какими должны быть объекты в массиве tasks.
Пример: добавляем пропсы в наше приложение
Давайте доработаем наше приложение. Мы создадим компонент Counter, который будет отображать текущее значение счётчика и кнопку для увеличения.
- Создаём интерфейс для пропсов:
interface CounterProps {
initialValue: number;
}
- Реализуем компонент:
function Counter({ initialValue }: CounterProps) {
const [count, setCount] = React.useState(initialValue);
return (
<div>
<p>Текущее значение: {count}</p>
<button onClick={() => setCount(count + 1)}>Увеличить</button>
</div>
);
}
- Используем компонент:
<Counter initialValue={0} />;
Здесь initialValue типизирован как число, и его нельзя пропустить.
Типизация с React.FC
В React часто используется специальный тип React.FC (Function Component). Он автоматически добавляет типизацию для пропсов и детей:
interface GreetingProps {
name: string;
}
const Greeting: React.FC<GreetingProps> = ({ name }) => {
return <h1>Привет, {name}!</h1>;
};
Хотя это удобно, есть небольшой нюанс: React.FC автоматически добавляет проп children даже если вы его не используете. Это может привести к путанице, поэтому руководства всё чаще рекомендуют избегать этого типа и писать явно. Выбор за вами!
Типичные ошибки
- Пропущенные типы: часто забывают типизировать пропсы или делают их слишком общими
any. Это убирает всю магию TypeScript. - Избыточные или некорректные значения: если вы передаёте пропсы, не описанные в интерфейсе, TypeScript тоже не оставит это без внимания.
- Неправильная обработка необязательных пропсов: если вы используете необязательные пропсы, проверяйте их на
undefined, прежде чем использовать.
Сегодня вы узнали, как типизировать пропсы компонентов с помощью интерфейсов. Это невероятно мощный инструмент, который сделает ваш код надёжным, понятным и безопасным. В реальных проектах такая типизация поможет избежать множества ошибок и значительно упростит работу в команде.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ