Render Props Pattern
"Render Props" бол component-ын логикийг дахин ашиглах арга — component нь "юу render хийх"-ийг шийддэггүй, харин ашиглагч нь props-оор функц дамжуулж render-ийг хянадаг. Нэр нь хүндрэлтэй сонсогдох ч ойлгомжтой жишээ харвал тэр дор ойлгоно. Hooks гарч ирснээс хойш энэ pattern харьцангуй хоёрдогч болсон ч том сан, бодит проектуудад өнөөг хүртэл элбэг тааралддаг тул ойлгох нь чухал.
Render Props гэж юу вэ?
Нэг жишээгээр харвал:
// Render Props ашигласан component
// "children" нь функц байна — энэ л render props-н үндсэн санаа
<MouseTracker>
{({ x, y }) => (
<div>
Хулганы байрлал: {x}, {y}
</div>
)}
</MouseTracker>
MouseTracker нь хулганы байрлалыг хянадаг. Гэхдээ тэр байрлалыг хэрхэн харуулах нь MouseTracker-н хариуцлага биш — хэрэглэгч нь функц дамжуулж өөрөө шийднэ. Иймд нэг MouseTracker component-ыг ямар ч UI-тэй хослуулж болно.
Үндсэн жишээ: MouseTracker
// src/components/MouseTracker.tsx
import { useState, useEffect, ReactNode } from "react";
interface MousePosition {
x: number;
y: number;
}
interface MouseTrackerProps {
children: (position: MousePosition) => ReactNode;
}
function MouseTracker({ children }: MouseTrackerProps) {
const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
// children нь функц — байрлалыг дамжуулж дуудна
return <>{children(position)}</>;
}
export default MouseTracker;
Ашиглах жишээ — нэг MouseTracker, олон харагдах байдал:
// Координат харуулах
<MouseTracker>
{({ x, y }) => <p>X: {x}, Y: {y}</p>}
</MouseTracker>
// Дагаж явдаг элемент
<MouseTracker>
{({ x, y }) => (
<div
style={{
position: "fixed",
left: x - 12,
top: y - 12,
width: 24,
height: 24,
borderRadius: "50%",
background: "#60a5fa",
pointerEvents: "none",
}}
/>
)}
</MouseTracker>
// Тодорхой хэсэгт орвол мессеж харуулах
<MouseTracker>
{({ y }) => (
y > 300
? <div className="banner">Дээш гүй!</div>
: null
)}
</MouseTracker>
render prop ашиглах хувилбар
children props-оор функц дамжуулахын оронд нэрлэсэн prop ашиглаж болно:
// src/components/DataFetcher.tsx
import { useState, useEffect, ReactNode } from "react";
interface FetchState<T> {
data: T | null;
isLoading: boolean;
error: string | null;
}
interface DataFetcherProps<T> {
url: string;
render: (state: FetchState<T>) => ReactNode; // нэрлэсэн render prop
loadingFallback?: ReactNode;
}
function DataFetcher<T>({ url, render, loadingFallback }: DataFetcherProps<T>) {
const [state, setState] = useState<FetchState<T>>({
data: null,
isLoading: true,
error: null,
});
useEffect(() => {
setState({ data: null, isLoading: true, error: null });
fetch(url)
.then((res) => {
if (!res.ok) throw new Error(`Алдаа: ${res.status}`);
return res.json() as Promise<T>;
})
.then((data) => setState({ data, isLoading: false, error: null }))
.catch((err: Error) =>
setState({ data: null, isLoading: false, error: err.message }),
);
}, [url]);
if (state.isLoading && loadingFallback) return <>{loadingFallback}</>;
return <>{render(state)}</>;
}
export default DataFetcher;
Ашиглах жишээ:
interface Course {
slug: string;
title: string;
lessonCount: number;
}
function CoursePage() {
return (
<DataFetcher<Course>
url="/api/courses/javascript"
loadingFallback={<p>Ачааллаж байна...</p>}
render={({ data, error }) => {
if (error) return <p className="error">{error}</p>;
if (!data) return null;
return (
<div>
<h1>{data.title}</h1>
<p>{data.lessonCount} хичээл</p>
</div>
);
}}
/>
);
}
Render Props vs Custom Hook
Render Props болон custom hook хоёулаа логик дахин ашиглахад хэрэглэгддэг. Тэдгээрийн ялгааг ойлгох нь чухал:
// Render Props — логик + render хамтдаа
function WithMousePosition({
children,
}: {
children: (pos: MousePosition) => ReactNode;
}) {
const [pos, setPos] = useState({ x: 0, y: 0 });
// ... event listener логик ...
return <>{children(pos)}</>;
}
// Custom Hook — зөвхөн логик
function useMousePosition() {
const [pos, setPos] = useState({ x: 0, y: 0 });
// ... event listener логик ...
return pos;
}
// Hook-тэй хэрэглэх — илүү энгийн
function MyComponent() {
const { x, y } = useMousePosition();
return (
<p>
X: {x}, Y: {y}
</p>
);
}
Орчин үеийн React-д custom hook нь ихэнх тохиолдолд Render Props-оос илүү тохиромжтой. Гэхдээ Render Props нь дараах тохиолдолд давуу байдаг:
- Логикийг component tree-д нэгтгэх шаардлагатай үед
- Хуучин class component-тэй ажиллахад (hook ашиглаж болохгүй)
- Гуравдагч сангуудын API-д render хяналт шаардлагатай үед
Render Props Pattern-ийг практикт харвал танихгүй байхгүй — react-router-н <Route render={...}>, react-table-н render функцүүд зэрэг олон алдартай сан энэ аргыг ашигладаг. Ойлгоод авсан ухаан чинь хаана ч ажилд хэрэглэгдэнэ.
Дараагийн хичээлд:
Higher-Order Component (HOC) Pattern — component-ыг "боож" нэмэлт чадвар олгох арга. React-н хамгийн хуучин боловч өнөөг хүртэл өргөн хэрэглэгддэг pattern-уудын нэгийг судална.