Шаблон render props у React
Що таке патерн Render Props?
Render props — це патерн, при якому компонент отримує функцію як пропс, яка повертає елементи React. Це дозволяє компоненту ділитися своєю внутрішньою логікою/станом, дозволяючи споживачу контролювати рендеринг.
Основний приклад
tsx
// Компонент з render prop — ділиться позицією миші
function MouseTracker({ render }: {
render: (position: { x: number; y: number }) => React.ReactNode;
}) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e: React.MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove} style={{ height: "100vh" }}>
{render(position)}
</div>
);
}
// Споживач контролює UI
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<p>Миша знаходиться на ({x}, {y})</p>
)}
/>
);
}Використання дітей як Render Prop
tsx
function MouseTracker({ children }: {
children: (position: { x: number; y: number }) => React.ReactNode;
}) {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>
{children(position)}
</div>
);
}
// Використання — чистіший синтаксис
<MouseTracker>
{({ x, y }) => <span>({x}, {y})</span>}
</MouseTracker>Практичні приклади
Компонент Toggle
tsx
function Toggle({ children }: {
children: (props: { isOn: boolean; toggle: () => void }) => React.ReactNode;
}) {
const [isOn, setIsOn] = useState(false);
const toggle = () => setIsOn(prev => !prev);
return <>{children({ isOn, toggle })}</>;
}
// Повторно використовувана логіка перемикання, різні UI
<Toggle>
{({ isOn, toggle }) => (
<button onClick={toggle}>
{isOn ? "УВІМКНУТО" : "ВИМКНУТО"}
</button>
)}
</Toggle>
<Toggle>
{({ isOn, toggle }) => (
<div className={`switch ${isOn ? "active" : ""}`} onClick={toggle} />
)}
</Toggle>Завантажувач даних
tsx
function DataFetcher<T>({ url, children }: {
url: string;
children: (props: { data: T | null; loading: boolean; error: string | null }) => React.ReactNode;
}) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(e => setError(e.message))
.finally(() => setLoading(false));
}, [url]);
return <>{children({ data, loading, error })}</>;
}
// Використання
<DataFetcher<User[]> url="/api/users">
{({ data, loading, error }) => {
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <UserList users={data!} />;
}}
</DataFetcher>Render Props vs Кастомні Хуки
| Особливість | Render Props | Кастомні Хуки |
|---|---|---|
| Повторне використання логіки | ✅ | ✅ |
| Без обгорткового компонента | ❌ (додає обгортку) | ✅ |
| Працює в класових компонентах | ✅ | ❌ |
| Може контролювати рендеринг | ✅ | ❌ |
| Читабельність | Може стати вкладеним | Чистіше |
tsx
// Така ж логіка як Кастомний Хук (сучасний підхід)
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e: MouseEvent) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener("mousemove", handler);
return () => window.removeEventListener("mousemove", handler);
}, []);
return position;
}
// Використання — чистіше, без обгортки
function App() {
const { x, y } = useMousePosition();
return <p>({x}, {y})</p>;
}Важливо:
Render props — це потужний патерн для ділення станом логіки між компонентами. Хоча кастомні хуки в основному замінили render props у сучасному React, цей патерн все ще цінний у бібліотеках (наприклад, React Router, Formik, Downshift) і коли вам потрібні компоненти, які працюють як з класовими, так і з функціональними компонентами.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.