React / Context дэвшилтэт хэрэглээ

Context дэвшилтэт хэрэглээ

useContext hook-ийг өмнөх хичээлд үзсэн. Энэ хичээлд Context-ийг бодит аппад хэрхэн зөв зохион байгуулах, useReducer-тэй хослуулах, болон гүйцэтгэлийн асуудлаас зайлсхийх аргуудыг сурна. Энэ мэдлэг том аппуудад маш хэрэгтэй!

Custom hook-оор Context-ийг ариун цэвэр болгох

Context-ийг шууд export хийхийн оронд custom hook-оор боож хэрэглэх нь хамгийн зөв pattern:

jsx
import { createContext, useContext, useState } from "react";

// Context-ийг модулийн дотор л нуун хадгална
const AuthContext = createContext(null);

// Provider component — state ба логикийг агуулна
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  function login(email, password) {
    // жинхэнэ аппад энд API дуудна
    setUser({ email, name: "Болд" });
  }

  function logout() {
    setUser(null);
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Custom hook — AuthContext-ийг шууд биш, энээр дуудна
export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) {
    throw new Error("useAuth нь AuthProvider дотор хэрэглэгдэх ёстой");
  }
  return ctx;
}

Аппдаа ийнхүү хэрэглэнэ:

jsx
import { AuthProvider, useAuth } from "./context/AuthContext";

// Дээд түвшинд Provider-г боодог
function App() {
  return (
    <AuthProvider>
      <Navbar />
      <Main />
    </AuthProvider>
  );
}

// Хаана ч ашиглаж болно
function Navbar() {
  const { user, logout } = useAuth();
  return (
    <nav>
      {user ? (
        <>
          <span>Сайн уу, {user.name}!</span>
          <button onClick={logout}>Гарах</button>
        </>
      ) : (
        <a href="/login">Нэвтрэх</a>
      )}
    </nav>
  );
}

useReducer-тэй хослуулах

Нарийн төвөгтэй state-ийг Context + useReducer хослолоор зохицуулах нь цэвэр, хянахад хялбар:

jsx
import { createContext, useContext, useReducer } from "react";

// State-ийн бүтэц
const initialState = {
  items: [],
  total: 0,
};

// Reducer — state-ийг хэрхэн өөрчлөх тодорхойлно
function cartReducer(state, action) {
  switch (action.type) {
    case "ADD_ITEM": {
      const exists = state.items.find((i) => i.id === action.item.id);
      const items = exists
        ? state.items.map((i) =>
            i.id === action.item.id ? { ...i, qty: i.qty + 1 } : i,
          )
        : [...state.items, { ...action.item, qty: 1 }];
      return { items, total: items.reduce((s, i) => s + i.price * i.qty, 0) };
    }
    case "REMOVE_ITEM": {
      const items = state.items.filter((i) => i.id !== action.id);
      return { items, total: items.reduce((s, i) => s + i.price * i.qty, 0) };
    }
    case "CLEAR":
      return initialState;
    default:
      return state;
  }
}

const CartContext = createContext(null);

export function CartProvider({ children }) {
  const [state, dispatch] = useReducer(cartReducer, initialState);

  // Dispatch-ийг шууд дамжуулах оронд helper функцүүд гаргана
  function addItem(item) {
    dispatch({ type: "ADD_ITEM", item });
  }
  function removeItem(id) {
    dispatch({ type: "REMOVE_ITEM", id });
  }
  function clearCart() {
    dispatch({ type: "CLEAR" });
  }

  return (
    <CartContext.Provider value={{ ...state, addItem, removeItem, clearCart }}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  const ctx = useContext(CartContext);
  if (!ctx) throw new Error("useCart нь CartProvider дотор хэрэглэгдэх ёстой");
  return ctx;
}

Олон Context хослуулах

Нэг том Context-д бүгдийг хийхийн оронд сэдвээр нь ангилж тусдаа Context үүсгэх нь дээр:

jsx
// app/providers.jsx — бүх Provider-уудыг нэг газарт цуглуулна
import { AuthProvider } from "./context/AuthContext";
import { CartProvider } from "./context/CartContext";
import { ThemeProvider } from "./context/ThemeContext";

export function AppProviders({ children }) {
  return (
    <AuthProvider>
      <ThemeProvider>
        <CartProvider>{children}</CartProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

// main.jsx
import { AppProviders } from "./app/providers";

createRoot(document.getElementById("root")).render(
  <AppProviders>
    <App />
  </AppProviders>,
);

Context-н гүйцэтгэлийн нюанс

Context value өөрчлөгдөх болгонд тухайн Context-г subscribe хийсэн бүх component дахин рендерлэгдэнэ. Тиймээс value-г тогтворжуулах нь чухал:

jsx
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("dark");

  // ❌ Муу — рендерлэгдэх болгонд шинэ object үүснэ
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("dark");

  // ✅ Дээр — useMemo-гоор value-г тогтворжуулна
  const value = useMemo(() => ({ theme, setTheme }), [theme]);

  return (
    <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
  );
}

Context нь дунд хэмжээний глобал state-д тохиромжтой. Маш олон газарт өөрчлөгдөх state-д Zustand зэрэг тусгай library ашиглах нь дээр — 31-р хичээлд үүнийг сурна.

Дараагийн хичээлд:

React Router сурна — React аппд хэд хэдэн хуудас (route) хэрхэн үүсгэхийг ойлгоно. BrowserRouter, Routes, Route, Link зэрэг үндсэн component-уудыг танина.