React / Higher-Order Component

Higher-Order Component

Higher-Order Component (HOC) бол component хүлээн авч, нэмэлт чадвартай шинэ component буцаадаг функц юм. "Higher-Order" гэдэг нь математикийн нэр томьёо — функц хүлээн авдаг функцийг "дээд зэргийн функц" гэнэ. JavaScript-д map, filter, reduce бүгд higher-order функц. HOC нь яг тэр санаагаар ажилладаг, зөвхөн component-д хэрэглэнэ.

HOC гэж юу вэ?

Хамгийн энгийн дүрслэл:

tsx
// Энгийн функц авдаг higher-order функц
const double = (fn: (x: number) => number) => (x: number) => fn(x) * 2;

// Component авдаг higher-order component
const withLogging = (Component: React.ComponentType) => {
  return function WrappedComponent(props: object) {
    console.log("Render:", Component.displayName);
    return <Component {...props} />;
  };
};

Бодит кодонд HOC хэрхэн харагддагыг харцгаая. Нэвтрэлт шаарддаг хэд хэдэн хуудас байна гэж бодъё — withAuth HOC нэмэлт нэвтрэлт шалгалт хийнэ:

tsx
// src/hocs/withAuth.tsx
import { ComponentType } from "react";
import { Navigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";

// HOC — component хүлээн авч, хамгаалагдсан component буцаана
function withAuth<P extends object>(Component: ComponentType<P>) {
  function AuthenticatedComponent(props: P) {
    const { user, isLoading } = useAuth();

    if (isLoading) return <p>Шалгаж байна...</p>;
    if (!user) return <Navigate to="/login" replace />;

    return <Component {...props} />;
  }

  // DevTools-д нэр харагдуулах
  AuthenticatedComponent.displayName = `withAuth(${Component.displayName ?? Component.name})`;

  return AuthenticatedComponent;
}

export default withAuth;

Ашиглах жишээ:

tsx
// src/pages/ProfilePage.tsx
function ProfilePage() {
  return (
    <div>
      <h1>Профайл</h1>
      {/* Нэвтэрсэн хэрэглэгчийн мэдээлэл */}
    </div>
  );
}

// ProfilePage-г HOC-оор "боож" нэвтрэлт шалгадаг хуудас болгоно
export default withAuth(ProfilePage);

// src/pages/DashboardPage.tsx
function DashboardPage() {
  return <div>Dashboard</div>;
}

export default withAuth(DashboardPage);

ProfilePage болон DashboardPage хоёулаа нэвтрэлт шалгах нэмэлт логиктой болно — ганц HOC дахин хэрэглэснээр.

withLoading HOC

Ачаалж байх үед spinner харуулдаг HOC:

tsx
// src/hocs/withLoading.tsx
import { ComponentType } from "react";
import LoadingSpinner from "../components/ui/LoadingSpinner";

interface WithLoadingProps {
  isLoading: boolean;
}

function withLoading<P extends object>(
  Component: ComponentType<Omit<P, "isLoading">>,
) {
  function WithLoadingComponent({ isLoading, ...props }: P & WithLoadingProps) {
    if (isLoading) return <LoadingSpinner />;
    return <Component {...(props as Omit<P, "isLoading">)} />;
  }

  WithLoadingComponent.displayName = `withLoading(${Component.displayName ?? Component.name})`;

  return WithLoadingComponent;
}

export default withLoading;

Ашиглах жишээ:

tsx
// src/components/CourseList.tsx
interface CourseListProps {
  courses: Course[];
  onSelect: (slug: string) => void;
}

function CourseList({ courses, onSelect }: CourseListProps) {
  return (
    <ul>
      {courses.map((c) => (
        <li key={c.slug} onClick={() => onSelect(c.slug)}>
          {c.title}
        </li>
      ))}
    </ul>
  );
}

// isLoading prop нэмэгдсэн шинэ component
const CourseListWithLoading = withLoading(CourseList);

// Ашиглах
function CoursesPage() {
  const { courses, isLoading } = useCourses();

  return (
    <CourseListWithLoading
      isLoading={isLoading}
      courses={courses}
      onSelect={(slug) => navigate(`/courses/${slug}`)}
    />
  );
}

HOC-уудыг давхарлах

Хэд хэдэн HOC-ыг нэгтгэж болно — гэхдээ гүн давхарлалаас зайлсхий:

tsx
// ✅ Хоёр HOC давхарлах — хэвийн
const ProtectedCourseList = withAuth(withLoading(CourseList));

// ❌ Хэт олон давхарлал — уншихад хэцүү
const OverWrapped = withAuth(
  withLoading(withErrorBoundary(withAnalytics(CourseList))),
);

// ✅ Олон HOC-г compose функцээр нэгтгэх
function compose<T>(...hocs: Array<(c: ComponentType<T>) => ComponentType<T>>) {
  return (Component: ComponentType<T>) =>
    hocs.reduceRight((acc, hoc) => hoc(acc), Component);
}

const enhance = compose(withAuth, withLoading);
const EnhancedCourseList = enhance(CourseList);

HOC vs Custom Hook

Hooks гарч ирснээс хойш HOC-н зарим хэрэглээг custom hook орлуулж байна:

tsx
// HOC аргаар — нэмэлт component wrapper шаардлагатай
const ProfileWithAuth = withAuth(Profile);

// Custom hook аргаар — component дотроо шууд ашигладаг
function Profile() {
  const { user } = useRequireAuth(); // нэвтрээгүй бол redirect хийнэ
  return <div>{user.name}</div>;
}

HOC нь дараах тохиолдолд custom hook-оос илүү тохиромжтой:

  • Class component-д (hook ашиглаж болохгүй)
  • Render гарахаас өмнө нөхцөл шалгах шаардлагатай үед — жишээ нь withAuth
  • Гуравдагч сангийн component-д нэмэлт чадвар нэмэхэд

React-н бодит ертөнцөд HOC, Render Props, Hook гурав нь бие биенийг орлохгүй — харин нөхцөлөөс шалтгаалан тохирохыг нь сонгодог. Гурвыг ойлгосон хөгжүүлэгч ямар ч код харахдаа дасан зохицож чадна.

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

Provider Pattern — Context ашиглан апп даяар нэг state-г хуваалцах архитектурын хэв маягийг судална. Том аппуудад state management-н суурь болдог чухал pattern.