Higher Order Component Pattern In React

هو طريقة تسمح بإضافة وظائف أو سلوكيات جديدة إلى المكونات دون تعديل تركيبتها الأساسية. HOC هو في الأساس وظيفة (Function) تأخذ مكون كمدخل (Argument) وتُعيد مكون جديد مُحسّن. هذا النمط يُمكِّن المطورين من إنشاء مكونات معدلة أو مُعززة عن طريق إضافة منطق جديد أو سلوكيات مشتركة
Higher Order Component Pattern In React
Higher Order Component Pattern In React

في هذه الصفحة

مع تزايد شعبية React كأداة رئيسية في بناء تطبيقات الويب المعاصرة، يبرز نمط Higher Order Component كاستراتيجية فعالة لتعزيز قابلية التوسيع وتحسين إعادة استخدام المكونات بكفاءة. يُقدم هذا النمط منهجية فريدة للتعامل مع المنطق المشترك والمتكرر في تطبيقات React. في هذا المقال، سنتعرف على هذا النمط، مستكشفين كيف يعمل، وكيف يمكن استخدامه لتحسين بنية تطبيقات React.

تعريف نمط التصميم HOC

نمط التصميم Higher Order Component (HOC) في React هو طريقة تسمح بإضافة وظائف أو سلوكيات جديدة إلى المكونات دون تعديل تركيبتها الأساسية. HOC هو في الأساس وظيفة (Function) تأخذ مكون كمدخل (Argument) وتُعيد مكون جديد مُحسّن. هذا النمط يُمكِّن المطورين من إنشاء مكونات معدلة أو مُعززة عن طريق إضافة منطق جديد أو سلوكيات مشتركة، مما يُقلل من التكرار في الشيفرة ويُحسّن قابلية الصيانة والاختبار.

استخدام نمط التصميم HOC

في سياق استخدام نمط التصميم HOC سنواصل العمل مع المكون Progress Bar الذي قمنا بتصميمه في مقالات سابقة. يمكن الرجوع إلى تلك المقالات من هنا: المقال الأول، المقال الثاني، المقال الثالث، لمن يرغب في الإطلاع على الشرح التفصيلي لأجزاء المُكون، في هذا المقال، سنركز بشكل خاص على كيفية دمج واستخدام نمط HOC مع المكون المذكور.


أود أن أذكر أن الكود المصدري الكامل للمثال المستخدم هنا متاح للمراجعة والاستفادة منه. يمكنكم الاطلاع على الكود لفهم أفضل للنقاط التي نوقشت في هذا المقال. هذا يوفر فرصة جيدة لتعميق الفهم وملاحظة كيفية تطبيق النمط في سياق عملي. يمكن العثور على الكود المصدري من هنا.

ضمن تطبيقاتنا، غالبا ما نجد الحاجة إلى استخدام نفس المنطق في مُكونات مُتعددة. على سبيل المثال لا الحصر: تنسيقات الأنماط (Styles)، عمليات المصادقة (Authorization)، أو الإدارة العامة للحالة (Global State Management)...

في مثال Progress Bar الذي بين أيدينا، يتجاوز المكون مفهوم الـ Headless Component، حيث نقوم بتطبيق التنسيقات بشكل مباشر على العناصر، كما يظهر في الكود التالي:

export const useProgress = (props: UseProgressProps) => {

  const getProgressBarProps: PropGetter = useCallback(
    (additionalProps = {}) => ({
      // className: progressBar({ size, isIndeterminate }).root, // في حالة استخدام Panda css
      className: baseStyles({ class: otherProps.className }), // في حالة استخدام Tailwind-variants
    }),
    [baseStyles, isIndeterminate, progressBarProps, otherProps],
  );

  const getLabelProps: PropGetter = useCallback(
    ({ className, ...additionalProps } = {}) => ({
      // className: progressBar({ size, isIndeterminate }).label, // في حالة استخدام Panda css
      className: labelStyles({
        class: `${isIndeterminate ? "text-orange-400" : "text-black"
          } font-semibold ${className}`
      }), // في حالة استخدام Tailwind-variants
    }),
    [isIndeterminate, labelProps, labelStyles],
  );

  const getProgressBarTrackProps: PropGetter = useCallback(
    ({ className, ...additionalProps } = {}) => ({
      // className: progressBar({ size, isIndeterminate }).track, // في حالة استخدام Panda css
      className: trackStyles({ class: className }), // في حالة استخدام Tailwind-variants
    }),
    [trackStyles],
  );

  const getProgressBarIndicatorProps: PropGetter = useCallback(
    ({ className, ...additionalProps } = {}) => ({
      // className: progressBar({ isIndeterminate }).indicator, // في حالة استخدام Panda css
      className: indicatorStyles({ class: className }), // في حالة استخدام Tailwind-variants
    }),
    [indicatorStyles, percentage],
  );

};

بالتالي لو انتهجنا نفس الأمر مع باقي المُكونات من ناحية تطبيق التنسيقات فنحن لسنا بحاجة لهذا النمط إطلاقا، خاصةً أننا نملك القدرة على التعديل بشكل مباشر على شيفرة المكونات وهنا يتجلى لنا السيناريو الأفضل الذي يستدعي استخدام كهذا نمط وهو تلك الحالات التي لا نملك فيها القدرة على الوصول المباشر لشيفرة المكونات. ويكون هذا الأمر شائع عند استخدام مكتبات المكونات الخارجية مثل Ark UI, Radix UI, Headless UI, و React Aria كأمثلة عن ذلك.

///////////////////////////////////////////
/////
||||| هذا المثال يستخدم مكتبة Ark UI
/////
///////////////////////////////////////////

// src/components/ui/accordion.tsx
import { Accordion as ArkAccordion } from '@ark-ui/react/accordion'
import { styled } from 'styled-system/jsx'
import { accordion } from 'styled-system/recipes'
import { createStyleContext } from '~/lib/create-style-context'

// withProvider & withContext are our Higher Order Functions
//          👇            👇
const { withProvider, withContext } = createStyleContext(accordion)

const Accordion = withProvider(styled(ArkAccordion.Root), 'root')
const AccordionItem = withContext(styled(ArkAccordion.Item), 'item')
const AccordionItemContent = withContext(styled(ArkAccordion.ItemContent), 'itemContent')
const AccordionItemIndicator = withContext(styled(ArkAccordion.ItemIndicator), 'itemIndicator')
const AccordionItemTrigger = withContext(styled(ArkAccordion.ItemTrigger), 'itemTrigger')

const Root = Accordion
const Item = AccordionItem
const ItemContent = AccordionItemContent
const ItemIndicator = AccordionItemIndicator
const ItemTrigger = AccordionItemTrigger

export {
  Accordion,
  AccordionItem,
  AccordionItemContent,
  AccordionItemIndicator,
  AccordionItemTrigger,
  Item,
  ItemContent,
  ItemIndicator,
  ItemTrigger,
  Root,
}


// src/App.tsx
import * as Accordion from '~/components/ui/accordion'

function Example() {
  const items = ['React', 'Solid', 'Svelte', 'Vue']
  return (
    <Accordion.Root defaultValue={['React']} multiple {...props}>
      {items.map((item, id) => (
        <Accordion.Item key={id} value={item} disabled={item === 'Svelte'}>
          <Accordion.ItemTrigger>
            {item}
            <Accordion.ItemIndicator>
              <ChevronDownIcon />
            </Accordion.ItemIndicator>
          </Accordion.ItemTrigger>
          <Accordion.ItemContent>
            <div>
              Pudding donut gummies chupa chups oat cake marzipan biscuit tart.
            </div>
          </Accordion.ItemContent>
        </Accordion.Item>
      ))}
    </Accordion.Root>
  )
}

///////////////////////////////////////////
/////
||||| هذا المثال يستخدم مكتبة Radix UI
/////
///////////////////////////////////////////

// src/components/ui/tooltip.tsx
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { tooltip } from "styled-system/recipes";
import { styled } from "styled-system/jsx";

// withProvider & withContext are our Higher Order Functions
//          👇            👇
const { withContext, withProvider } = createStyleContext(tooltip);

export const TooltipProvider = TooltipPrimitive.Provider;
export const Tooltip = withProvider(styled(TooltipPrimitive.Root), "root");
export const TooltipTrigger = withContext(styled(TooltipPrimitive.Trigger), "trigger");
export const TooltipContent = withContext(styled(TooltipPrimitive.Content), "content");

// src/App.tsx
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "~/components/ui/tooltip";

function App() {
	return (
		<TooltipProvider>
        <Tooltip>
          <TooltipTrigger>
            <Button variant="outline" size="sm">
              Hover Me
            </Button>
          </TooltipTrigger>
          <TooltipContent sideOffset={4}>
            <p>I'm a tooltip</p>
          </TooltipContent>
        </Tooltip>
      </TooltipProvider>
	)
}

كما يتضح من الأمثلة المذكورة أعلاه، فإننا نعتمد على مكتبات المكونات الخارجية لبناء مكوناتنا الخاصة بدلا من تطويرها من الصفر. في هذا السياق، يبرز دور نمط التصميم HOC كأداة حيوية تمكننا من إضافة منطق برمجي خاص بنا، مثل ادارة التنسيقات، دون الحاجة إلى التدخل في الكود الأساسي للمكونات، والذي قد لا يكون قابلا للتعديل أصلاً.

هذا المقال مخصص للأعضاء المنتسبين لخطط الاشتراك المدفوعة فقط

اشترك الآن بنشرة اقرأ-تِك الأسبوعية

لا تدع أي شيء يفوتك. واحصل على أحدث المقالات المميزة مباشرة إلى بريدك الإلكتروني وبشكل مجاني!