Базовый пример рендер-пропса
type RenderProps = {
data: string;
};
const DataProvider: React.FC<{ render: (props: RenderProps) => JSX.Element }> = ({ render }) => {
const someData = "Hello from DataProvider!";
return <>{render({ data: someData })}</>;
};
const App = () => {
return (
<DataProvider
render={({ data }) => <div>{data}</div>}
/>
);
};
В этом примере мы передали функцию render через пропс. Она принимает объект { data } и возвращает JSX. Всё просто, но без типизации это могла бы быть неразбериха вроде any. Представьте, что вы случайно передадите не тот тип данных в data. Упс.
Типизация функции рендер-пропса
Теперь давайте разберёмся, как правильно типизировать функции рендер-пропсов.
1. Описание типа пропса
Для начала, нужно указать, какие данные компонент будет передавать. Здесь нам помогают интерфейсы или типы.
type RenderProps = {
data: string;
};
Тут мы говорим TypeScript: «Эй, рендер-функция примет объект с полем data, и это поле будет строкой».
2. Типизация компонента
На следующем этапе нужно сообщить компоненту, что он принимает функцию в качестве пропса.
const DataProvider: React.FC<{
render: (props: RenderProps) => JSX.Element;
}> = ({ render }) => {
const someData = "Hello from DataProvider!";
return <>{render({ data: someData })}</>;
};
Здесь render — это функция, которая принимает объект типа RenderProps и возвращает JSX. Обратите внимание: это важно, чтобы TypeScript мог проверить корректность у переданной функции.
3. Использование компонента
Шаг простой, но, чтобы TypeScript подхватил всю типизацию правильно, сам вызывающий код также должен быть корректен:
const App = () => {
return (
<DataProvider
render={({ data }) => <div>{data}</div>}
/>
);
};
Если вы случайно попытаетесь сделать что-то не то, например:
<DataProvider
render={(value) => <div>{value}</div>} // ошибка
/>
TypeScript сразу подскажет, что value не соответствует типу RenderProps. Всё под контролем!
Задачи посложнее: когда рендер-пропсы зависят от типов
В реальном мире наши компоненты редко используют одну только строку. Давайте рассмотрим более сложный случай: компонент, предоставляющий список элементов различного типа.
Пример: типизация рендер-пропсов для списков
Допустим, у нас есть компонент, который предоставляет список данных. Каждый элемент списка может быть разным (например, строкой или числом). Для начала создадим интерфейс:
type ListProps<T> = {
items: T[];
render: (item: T) => JSX.Element;
};
Этот интерфейс говорит: «Я приму массив любого типа T и рендер-функцию, которая обработает каждый элемент этого массива».
Теперь создаём компонент:
const List = <T extends unknown>({ items, render }: ListProps<T>) => {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{render(item)}</li>
))}
</ul>
);
};
Обратите внимание на <T extends unknown>: здесь T — это обобщённый тип. Он говорит TypeScript, что List может работать с любым типом.
Использование компонента
Теперь посмотрим, как использовать такой компонент. Для примера возьмём массив строк:
const App = () => {
const strings = ["React", "TypeScript", "HOC"];
return (
<List
items={strings}
render={(item) => <strong>{item}</strong>}
/>
);
};
TypeScript автоматически понимает, что item — это строка, и вы получите ошибку, если попробуете, например, применить к нему числовую операцию.
Хотим использовать числа? Пожалуйста:
const numbers = [1, 2, 3, 4, 5];
return (
<List
items={numbers}
render={(item) => <em>{item * 2}</em>}
/>
);
Разбор типичных ошибок и ловушек
Неверная типизация рендер-функции
Если вы неправильно укажете тип рендер-пропса, TypeScript начнёт выдавать ошибки. Например:
type RenderProps = {
data: string;
};
const DataProvider: React.FC<{ render: (props: RenderProps) => JSX.Element }> = ({ render }) => {
const someData = "Hello!";
return <>{render({ data: someData })}</>;
};
// Неверная типизация:
render={(props) => <div>{props.number}</div>} // Ошибка: 'number' не существует в типе 'RenderProps'.
Практическое применение
В реальных проектах рендер-пропсы часто используются для:
- Общих компонентов: рендер-пропсы позволяют удобно делиться логикой, например, отображением списков или загрузкой данных.
- Обеспечения гибкости: с их помощью можно легко передавать уникальную JSX-разметку в общий компонент.
- Типизация API-данных: через рендер-пропсы можно типизировать данные, которые компонент забирает из API, что предотвращает ошибки.
На этом всё! Теперь вы знаете, как типизировать функции рендер-пропсов и использовать их в реальных проектах. В следующей лекции мы сравним HOC и рендер-пропсы и разберёмся, в каких случаях выбирать тот или иной подход.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ