Когда использовать рендер-пропсы?
Рендер-пропсы особенно хороши, когда вам нужно разделить логику и отображение. Например: - Работать с API, получая данные и предоставляя их компонентам. - Управлять состояниями, такими как модальные окна, меню, переключатели.
Типичный пример: у вас есть компонент, который должен отображать список объектов. Вместо того чтобы жёстко задавать, как именно отображать данные, вы предоставляете потребителю возможность самому решить, каким образом это лучше сделать.
Пример №1: создание компонента с рендер-пропсами
Для начала создадим простой компонент, который будет передавать данные через рендер-пропсы. Наш компонент называется DataProvider. Он предоставляет данные через переданный рендер-пропс.
import React, { ReactNode } from "react";
// Создадим интерфейс для данных
interface DataProviderProps {
render: (data: string[]) => ReactNode; // Рендер-пропс — функция, возвращающая ReactNode
}
const DataProvider: React.FC<DataProviderProps> = ({ render }) => {
// Данные, которыми мы будем делиться
const data = ["🍎 Apple", "🍊 Orange", "🍌 Banana"];
return (
<div>
<h3>Here is your data:</h3>
{render(data)} {/* Вызываем пропс render и передаем ему данные */}
</div>
);
};
export default DataProvider;
И теперь мы можем использовать этот компонент:
import React from "react";
import DataProvider from "./DataProvider";
const App: React.FC = () => {
return (
<DataProvider
render={(data) => (
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
)}
/>
);
};
export default App;
Компонент DataProvider предоставляет данные через функцию, а App решает, как их отобразить. Это гибкость в чистом виде.
Пример №2: управление состоянием через рендер-пропсы
Теперь давайте создадим компонент Toggle, который будет управлять состоянием (вкл./выкл.) и предоставлять его через рендер-пропс.
import React, { useState, ReactNode } from "react";
interface ToggleProps {
render: (isOn: boolean, toggle: () => void) => ReactNode;
}
const Toggle: React.FC<ToggleProps> = ({ render }) => {
const [isOn, setIsOn] = useState(false);
const toggle = () => setIsOn(!isOn); // Переключаем состояние
return <div>{render(isOn, toggle)}</div>; // Передаём состояние и функцию toggle
};
export default Toggle;
Используем его:
import React from "react";
import Toggle from "./Toggle";
const App: React.FC = () => {
return (
<Toggle
render={(isOn, toggle) => (
<div>
<p>The toggle is {isOn ? "ON" : "OFF"}</p>
<button onClick={toggle}>Switch</button>
</div>
)}
/>
);
};
export default App;
Теперь у нас есть компонент, который управляет своим состоянием, но оставляет рендеринг полностью на усмотрение потребителя. Хотите сделать переключатель в виде кнопки? Пожалуйста! Хотите заменить это на чекбокс? Без проблем.
Как типизировать рендер-пропсы?
Для типизации рендер-пропсов нам нужно описать функцию, которая передаётся как пропс. Вот несколько примеров.
Пример 1: рендер-пропс с простыми данными
Как в случае с DataProvider, мы передаём функцию, принимающую массив строк:
interface DataProviderProps {
render: (data: string[]) => ReactNode; // Функция принимает массив строк, возвращает ReactNode
}
Пример 2: рендер-пропс с состоянием
Как в случае с Toggle, функция принимает состояние и функцию для его изменения:
interface ToggleProps {
render: (isOn: boolean, toggle: () => void) => ReactNode; // Состояние и функция toggle
}
Пример №3: используем рендер-пропсы для работы с API
Давайте создадим компонент FetchData, который будет загружать данные с API и отдавать их через рендер-пропс.
import React, { useState, useEffect, ReactNode } from "react";
interface FetchDataProps {
url: string; // URL для загрузки данных
render: (data: any | null, loading: boolean, error: string | null) => ReactNode;
}
const FetchData: React.FC<FetchDataProps> = ({ url, render }) => {
const [data, setData] = useState<any | nullм(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, [url]);
return <div>{render(data, loading, error)}</div>;
};
export default FetchData;
Используем его:
import React from "react";
import FetchData from "./FetchData";
const App: React.FC = () => {
return (
<FetchData
url="https://jsonplaceholder.typicode.com/todos"
render={(data, loading, error) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map((todo: any) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}}
/>
);
};
export default App;
Этот компонент загружает данные с API и предоставляет их через рендер-пропсы. Мы можем использовать эту логику где угодно, не дублируя код.
Типичные ошибки при работе с рендер-пропсами
Иногда рендер-пропсы могут быть слишком переусложнёнными, если использовать их избыточно. Если компонент слишком универсален, он может стать трудно читаемым. Используйте рендер-пропсы, когда это действительно нужно. Если задача проще, возможно, лучше подойдут обычные пропсы или HOC.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ