React / Provider Pattern

Provider Pattern

Provider Pattern нь React-н Context API дээр суурилсан архитектурын хэв маяг юм. Апп даяар хэрэглэгдэх state — нэвтэрсэн хэрэглэгч, дэлгэцийн горим, хэл — олон component-д props "дамжуулалгүйгээр" хүргэдэг. Энэ pattern-ыг ойлговол Zustand, Redux зэрэг state management сангуудын ажиллах зарчмыг ойлгоход хялбар болно.

Асуудал: Props Drilling

AppLayoutNavbarUserMenu гэсэн component мод дотор хэрэглэгчийн мэдээллийг дамжуулах гэвэл:

tsx
// ❌ 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 бүтээх

tsx
// 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-г оруулна:

tsx
// 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 дуудаж болно:

tsx
// 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-ууд давхарладаг:

tsx
// 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-г нэгтгэх хоёр арга:

tsx
// Арга 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-ыг зорилгоор нь хуваах хэрэгтэй:

tsx
// ❌ Нэг том 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-уудаа баримтжуулах хамгийн алдартай арга.