Provider Pattern
Provider Pattern нь React-н Context API дээр суурилсан архитектурын хэв маяг юм. Апп даяар хэрэглэгдэх state — нэвтэрсэн хэрэглэгч, дэлгэцийн горим, хэл — олон component-д props "дамжуулалгүйгээр" хүргэдэг. Энэ pattern-ыг ойлговол Zustand, Redux зэрэг state management сангуудын ажиллах зарчмыг ойлгоход хялбар болно.
Асуудал: Props Drilling
App → Layout → Navbar → UserMenu гэсэн component мод дотор хэрэглэгчийн мэдээллийг дамжуулах гэвэл:
// ❌ Props drilling — бүх давхаргаар дамжуулах шаардлагатай
function App() {
const [user, setUser] = useState<User | null>(null);
return <Layout user={user} onLogout={() => setUser(null)} />;
}
function Layout({
user,
onLogout,
}: {
user: User | null;
onLogout: () => void;
}) {
// Layout өөрөө user ашиглахгүй — зөвхөн дамжуулна
return <Navbar user={user} onLogout={onLogout} />;
}
function Navbar({
user,
onLogout,
}: {
user: User | null;
onLogout: () => void;
}) {
// Navbar ч дамжуулна
return <UserMenu user={user} onLogout={onLogout} />;
}
function UserMenu({
user,
onLogout,
}: {
user: User | null;
onLogout: () => void;
}) {
// Эцэст нь энд л хэрэглэнэ
return user ? <button onClick={onLogout}>{user.name}</button> : null;
}
Layout болон Navbar хоёр user-г шууд ашиглахгүй ч дамжуулах ёстой болдог. Provider Pattern энийг шийднэ.
AuthProvider бүтээх
// src/contexts/AuthContext.tsx
import {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from "react";
interface User {
id: string;
name: string;
email: string;
xp: number;
}
interface AuthContextValue {
user: User | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
// Context үүсгэх — эхний утга null, useAuth шалгаж алдаа гаргана
const AuthContext = createContext<AuthContextValue | null>(null);
// Provider component — apп-н дээд хэсэгт байрлана
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Апп ачаалах үед session шалгах
useEffect(() => {
fetch("/api/me")
.then((res) => (res.ok ? res.json() : null))
.then((data: User | null) => setUser(data))
.finally(() => setIsLoading(false));
}, []);
const login = async (email: string, password: string) => {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (!res.ok) throw new Error("Нэвтрэх амжилтгүй боллоо");
const data: User = await res.json();
setUser(data);
};
const logout = () => {
fetch("/api/logout", { method: "POST" });
setUser(null);
};
return (
<AuthContext.Provider value={{ user, isLoading, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// Custom hook — Context-ийг хялбар ашиглах
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth нь <AuthProvider> дотор ашиглагдах ёстой");
return ctx;
}
Апп-н үндсэн файлд Provider-г оруулна:
// src/main.tsx
import ReactDOM from "react-dom/client";
import App from "./App";
import { AuthProvider } from "./contexts/AuthContext";
ReactDOM.createRoot(document.getElementById("root")!).render(
<AuthProvider>
<App />
</AuthProvider>,
);
Тэгсний дараа апп-н ямар ч component-д props дамжуулалгүйгээр useAuth дуудаж болно:
// Props drilling байхгүй — шууд Context-оос авна
function UserMenu() {
const { user, logout } = useAuth();
if (!user) return null;
return (
<div>
<span>{user.name}</span>
<span>{user.xp} XP</span>
<button onClick={logout}>Гарах</button>
</div>
);
}
ThemeProvider — олон Provider нэгтгэх
Бодит аппуудад Provider-ууд давхарладаг:
// src/contexts/ThemeContext.tsx
import { createContext, useContext, useState, ReactNode } from "react";
type Theme = "dark" | "light";
interface ThemeContextValue {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("dark");
const toggleTheme = () => setTheme((t) => (t === "dark" ? "light" : "dark"));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div data-theme={theme}>{children}</div>
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx)
throw new Error("useTheme нь <ThemeProvider> дотор ашиглагдах ёстой");
return ctx;
}
Олон Provider-г нэгтгэх хоёр арга:
// Арга 1: Шууд давхарлах — тоо цөөн үед тохиромжтой
<AuthProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</AuthProvider>;
// Арга 2: AppProviders component — provider олон болоход цэвэрхэн
// src/providers/AppProviders.tsx
function AppProviders({ children }: { children: ReactNode }) {
return (
<AuthProvider>
<ThemeProvider>{children}</ThemeProvider>
</AuthProvider>
);
}
// main.tsx-д ашиглах
<AppProviders>
<App />
</AppProviders>;
Context-ийн гүйцэтгэлийн анхааруулга
Context-н утга өөрчлөгдөх бүрт бүх Consumer component дахин render хийдэг. Энийг урьдчилан сэргийлэхийн тулд Context-ыг зорилгоор нь хуваах хэрэгтэй:
// ❌ Нэг том Context — нэг ч утга өөрчлөгдвөл бүгд render болно
const AppContext = createContext({ user, theme, language, notifications });
// ✅ Тусдаа Context — өөрчлөгдсөн хэсэгт л render болно
const AuthContext = createContext({ user, login, logout });
const ThemeContext = createContext({ theme, toggleTheme });
const LanguageContext = createContext({ lang, setLang });
Provider Pattern нь React аппын "нуруу" болдог архитектур. Нэвтрэлт, дэлгэцийн горим, хэл солих зэрэг апп даяар хэрэглэгдэх state-г Provider-ээр зохион байгуулах нь кодыг цэвэр, дахин ашиглагдахуйц байлгадаг хамгийн үр дүнтэй арга.
Дараагийн хичээлд:
Storybook — component-уудыг апп-аас тусдаа харж, тест хийх орчин бүтээх хэрэгсэл. UI component-уудаа баримтжуулах хамгийн алдартай арга.