استفاده از کامپوننت Higher Order در React

کامپوننت‌های Higher Order (یا به اختصار HOC) از الگوهای قدرتمند در React محسوب می‌شوند که به توسعه‌دهندگان اجازه می‌دهند بدون نیاز به ویرایش مستقیم کامپوننت اصلی، قابلیت‌های جدیدی به آن اضافه کنند.
این الگوها راهکاری قابل‌استفاده‌ مجدد برای مدیریت دغدغه‌های مشترکی مانند احراز هویت، ثبت لاگ یا مدیریت state سراسری فراهم می‌کنند.

با وجود اینکه هوک‌ها تا حد زیادی جایگزین HOCها برای بازاستفاده از منطق شده‌اند، کامپوننت‌های Higher Order همچنان در برخی شرایط مزایای منحصربه‌فردی دارند؛ به ویژه هنگام کار با کدهای قدیمی یا زمانی که نیاز به ایجاد تغییرات پیچیده در رفتار کامپوننت‌ها وجود دارد.

HOC چیست و چه زمانی باید از آن استفاده کنیم؟

کامپوننت Higher Order در React، تابعی است که یک کامپوننت را به عنوان ورودی دریافت می‌کند و یک کامپوننت جدید و توسعه‌یافته را به عنوان خروجی بازمی‌گرداند.

هر دو الگوی HOC و هوک، منطق دارای state را در خود نگه‌ می‌دارند، اما این کار را به روش‌های متفاوتی انجام می‌دهند و برای موارد استفاده مختلفی مناسب هستند.

برای درک بهتر تفاوت میان این دو الگو، در ادامه یک ویژگی شمارنده ساده را با دو روش پیاده‌سازی کرده‌ایم: یکی با استفاده از HOC و دیگری با استفاده از یک هوک سفارشی.

روش اول: استفاده از HOC

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// HOC that adds counter functionality to a component
const withCounter = (WrappedComponent) => {
return function CounterWrapper(props) {
const [count, setCount] = useState(0);
return (
<WrappedComponent
count={count}
increment={() => setCount(prev => prev + 1)}
{...props}
/>
);
};
};
// HOC that adds counter functionality to a component const withCounter = (WrappedComponent) => { return function CounterWrapper(props) { const [count, setCount] = useState(0); return ( <WrappedComponent count={count} increment={() => setCount(prev => prev + 1)} {...props} /> ); }; };
// HOC that adds counter functionality to a component
const withCounter = (WrappedComponent) => {
  return function CounterWrapper(props) {
    const [count, setCount] = useState(0);
    return (
      <WrappedComponent 
        count={count}
        increment={() => setCount(prev => prev + 1)}
        {...props}
      />
    );
  };
};

روش دوم: استفاده از هوک سفارشی

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Custom Hook that provides counter functionality
const useCounter = () => {
const [count, setCount] = useState(0);
return {
count,
increment: () => setCount(prev => prev + 1)
};
};
// Usage
const Counter = () => {
const {count, increment} = useCounter();
return (
<>
<button>Increment</button>
<p>Clicked:{count}</p>
</>
)
}
// Custom Hook that provides counter functionality const useCounter = () => { const [count, setCount] = useState(0); return { count, increment: () => setCount(prev => prev + 1) }; }; // Usage const Counter = () => { const {count, increment} = useCounter(); return ( <> <button>Increment</button> <p>Clicked:{count}</p> </> ) }
// Custom Hook that provides counter functionality
const useCounter = () => {
  const [count, setCount] = useState(0);
  return {
    count,
    increment: () => setCount(prev => prev + 1)
  };
};

// Usage

const Counter = () => {
  const {count, increment} = useCounter();
  return (
      <>
        <button>Increment</button>
        <p>Clicked:{count}</p>
      </>
)
}

در حالی که هر دو روش عملکردی مشابه دارند، تفاوت اصلی آن‌ها در ساختار پیاده‌سازی است:
الگوی HOC یک کامپوننت موجود را درون یک کامپوننت دیگر قرار می‌دهد تا قابلیت‌های بیشتری به آن بیفزاید، در حالی که هوک سفارشی منطق قابل استفاده مجدد را جدا می‌کند و به شکلی تمیز، بدون تغییر در ساختار سلسله مراتبی کامپوننت، مورد استفاده قرار می‌گیرد.

ساختار یک کامپوننت Higher Order در React

طبق مستندات رسمی React، یک کامپوننت Higher Order معمولاً این‌گونه تعریف می‌شود:

«کامپوننت Higher Order در React تابعی است که یک کامپوننت را به عنوان ورودی دریافت کرده و یک کامپوننت جدید بازمی‌گرداند.»

اگر بخواهیم این تعریف را به صورت کدی بیان کنیم، به شکل زیر خواهد بود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const newComponent = higherFunction(WrappedComponent);
const newComponent = higherFunction(WrappedComponent);
const newComponent = higherFunction(WrappedComponent);

در این خط کد:

  • newComponent
    newComponent: کامپوننت جدید و توسعه‌یافته است
  • higherFunction
    higherFunction: تابعی است که
    WrappedComponent
    WrappedComponent را توسعه می‌دهد
  • WrappedComponent
    WrappedComponent: کامپوننت پایه‌ای است که قصد افزودن قابلیت‌های جدید به آن داریم

ایجاد یک کامپوننت Higher Order در React

برای ساخت یک HOC، ابتدا باید تابعی تعریف کنیم که کامپوننت پایه را به عنوان آرگومان دریافت کرده و یک کامپوننت جدید با قابلیت‌های اضافه‌شده را بازگرداند.
در یک HOC فانکشنال می‌توانیم از هوک‌ها برای مدیریت state و side effectها استفاده کنیم. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React, { useState, useEffect } from 'react';
const withEnhancement = (BaseComponent) => {
return function EnhancedComponent(props) {
// HOC-specific logic using hooks
return <BaseComponent {...props} />;
};
};
import React, { useState, useEffect } from 'react'; const withEnhancement = (BaseComponent) => { return function EnhancedComponent(props) { // HOC-specific logic using hooks return <BaseComponent {...props} />; }; };
import React, { useState, useEffect } from 'react';

const withEnhancement = (BaseComponent) => {
  return function EnhancedComponent(props) {
    // HOC-specific logic using hooks
    return <BaseComponent {...props} />;
  };
};

توسعه کامپوننت

درون تابع

EnhancedComponent
EnhancedComponent می‌توانیم از هوک‌هایی مانند
useState
useState
،
useEffect
useEffect
 و
useRef
useRef
 برای مدیریت state و انجام عملیات جانبی استفاده کنیم تا رفتارهای جدیدی به کامپوننت اضافه شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const withEnhancement = (BaseComponent) => {
return function EnhancedComponent(props) {
const [count, setCount] = useState(0);
useEffect(() => {
// Perform side effects here
}, [count]);
return <BaseComponent count={count} setCount={setCount} {...props} />;
};
};
const withEnhancement = (BaseComponent) => { return function EnhancedComponent(props) { const [count, setCount] = useState(0); useEffect(() => { // Perform side effects here }, [count]); return <BaseComponent count={count} setCount={setCount} {...props} />; }; };
const withEnhancement = (BaseComponent) => {
  return function EnhancedComponent(props) {
    const [count, setCount] = useState(0);

    useEffect(() => {
      // Perform side effects here
    }, [count]);

    return <BaseComponent count={count} setCount={setCount} {...props} />;
  };
};

نحوه استفاده از HOC

برای استفاده از HOC ساخته شده، کافی است کامپوننت پایه را به عنوان ورودی به تابع HOC ارسال کنیم. نتیجه، یک کامپوننت جدید با قابلیت‌های توسعه‌یافته خواهد بود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const EnhancedComponent = withEnhancement(BaseComponent);
const EnhancedComponent = withEnhancement(BaseComponent);
const EnhancedComponent = withEnhancement(BaseComponent);

استفاده از کامپوننت توسعه یافته

کامپوننتی که با استفاده از HOC ساخته شده، مانند سایر کامپوننت‌های React قابل استفاده است، با این تفاوت که قابلیت‌های جدیدی از طریق HOC به آن اضافه شده‌اند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function App() {
return <EnhancedComponent />;
}
function App() { return <EnhancedComponent />; }
function App() {
  return <EnhancedComponent />;
}

در بخش بعدی مقاله، به بررسی یک نمونه کاربردی از HOCها در عمل خواهیم پرداخت.

استفاده از کامپوننت‌های Higher Order در React

در این بخش به یک مثال عملی از استفاده از HOCها در پروژه‌های React می‌پردازیم.

راه‌اندازی repository پروژه

ابتدا باید یک پروژه خالی React ایجاد کنیم. برای این کار، دستورات زیر را اجرا می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npx create-react-app hoc-tutorial
cd hoc-tutorial #navigate to the project folder.
cd src #go to codebase
mkdir components #will hold all our custom components
npx create-react-app hoc-tutorial cd hoc-tutorial #navigate to the project folder. cd src #go to codebase mkdir components #will hold all our custom components
npx create-react-app hoc-tutorial 
cd hoc-tutorial #navigate to the project folder.
cd src #go to codebase
mkdir components #will hold all our custom components

در این مقاله، دو کامپوننت سفارشی ایجاد می‌کنیم تا کاربرد HOC را به صورت عملی نمایش دهیم:

  • ClickIncrease.js
    ClickIncrease.js: این کامپوننت شامل یک دکمه و یک متن است. با کلیک روی دکمه (event
    onClick
    onClick)، مقدار ویژگی
    fontSize
    fontSize متن افزایش می‌یابد.
  • HoverIncrease.js
    HoverIncrease.js: مشابه
    ClickIncrease
    ClickIncrease است، با این تفاوت که با رویداد
    onMouseOver
    onMouseOver واکنش نشان می‌دهد.

وارد پوشه

components
components می‌شویم و این دو فایل را ایجاد می‌کنیم. در پایان، ساختار فایل‌های پروژه باید به شکل زیر باشد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
📁 public
📁 src
📁 components
📄 ClickIncrease.js
📄 HoverIncrease.js
📄 App.js
📄 index.js
📄 styles.css
📄 package.json
📁 public 📁 src 📁 components 📄 ClickIncrease.js 📄 HoverIncrease.js 📄 App.js 📄 index.js 📄 styles.css 📄 package.json
📁 public
📁 src
  📁 components
    📄 ClickIncrease.js
    📄 HoverIncrease.js
📄 App.js
📄 index.js
📄 styles.css
📄 package.json

کدنویسی کامپوننت‌ها

در فایل

ClickIncrease.js
ClickIncrease.js کد زیر را می‌نویسیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// File: components/ClickIncrease.js
import React, { useState } from 'react';
function ClickIncrease() {
const [fontSize, setFontSize] = useState(10); // Set initial value to 10.
return (
<button onClick={() => setFontSize(size => size + 1)}>
Increase with click
</button>
<p style={{ fontSize: `${fontSize}px` }}>
Size of font: {fontSize}px
</p>
);
}
export default ClickIncrease;
// File: components/ClickIncrease.js import React, { useState } from 'react'; function ClickIncrease() { const [fontSize, setFontSize] = useState(10); // Set initial value to 10. return ( <button onClick={() => setFontSize(size => size + 1)}> Increase with click </button> <p style={{ fontSize: `${fontSize}px` }}> Size of font: {fontSize}px </p> ); } export default ClickIncrease;
// File: components/ClickIncrease.js
import React, { useState } from 'react';

function ClickIncrease() {
  const [fontSize, setFontSize] = useState(10); // Set initial value to 10.

  return (
    <button onClick={() => setFontSize(size => size + 1)}>
      Increase with click
    </button>
    <p style={{ fontSize: `${fontSize}px` }}>
      Size of font: {fontSize}px
    </p>
  );
}

export default ClickIncrease;

سپس در فایل

HoverIncrease.js
HoverIncrease.js کد زیر را قرار می‌دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// File: components/HoverIncrease.js
import React, { useState } from 'react';
function HoverIncrease() {
const [fontSize, setFontSize] = useState(10);
return (
<div onMouseOver={() => setFontSize(size => size + 1)}>
<p style={{ fontSize: `${fontSize}px` }}>
Size of font: {fontSize}px
</p>
</div>
);
}
export default HoverIncrease;
// File: components/HoverIncrease.js import React, { useState } from 'react'; function HoverIncrease() { const [fontSize, setFontSize] = useState(10); return ( <div onMouseOver={() => setFontSize(size => size + 1)}> <p style={{ fontSize: `${fontSize}px` }}> Size of font: {fontSize}px </p> </div> ); } export default HoverIncrease;
// File: components/HoverIncrease.js
import React, { useState } from 'react';

function HoverIncrease() {
  const [fontSize, setFontSize] = useState(10);

  return (
    <div onMouseOver={() => setFontSize(size => size + 1)}>
      <p style={{ fontSize: `${fontSize}px` }}>
        Size of font: {fontSize}px
      </p>
    </div>
  );
}

export default HoverIncrease;

استفاده از کامپوننت‌ها در فایل App.js

در نهایت، برای نمایش این دو کامپوننت در رابط کاربری، آن‌ها را در فایل

App.js
App.js رندر می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// File: App.js
import React from 'react';
import ClickIncrease from './components/ClickIncrease';
import HoverIncrease from './components/HoverIncrease';
function App() {
return (
<div>
<ClickIncrease />
<HoverIncrease />
</div>
);
}
export default App;
// File: App.js import React from 'react'; import ClickIncrease from './components/ClickIncrease'; import HoverIncrease from './components/HoverIncrease'; function App() { return ( <div> <ClickIncrease /> <HoverIncrease /> </div> ); } export default App;
// File: App.js
import React from 'react';
import ClickIncrease from './components/ClickIncrease';
import HoverIncrease from './components/HoverIncrease';

function App() {
  return (
    <div>
      <ClickIncrease />
      <HoverIncrease />
    </div>
  );
}

export default App;

ساخت و استفاده از تابع HOC

در پوشه

components
components، فایلی با نام
withCounter.js
withCounter.js ایجاد کرده، سپس کد ابتدایی زیر را در آن قرار می‌دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from "react";
const UpdatedComponent = (OriginalComponent) => {
function NewComponent(props) {
//render OriginalComponent and pass on its props.
return ;
}
return NewComponent;
};
export default UpdatedComponent;
import React from "react"; const UpdatedComponent = (OriginalComponent) => { function NewComponent(props) { //render OriginalComponent and pass on its props. return ; } return NewComponent; }; export default UpdatedComponent;
import React from "react";
const UpdatedComponent = (OriginalComponent) => {
function NewComponent(props) {
//render OriginalComponent and pass on its props.
return ;
}
return NewComponent;
};
export default UpdatedComponent;

بررسی جزئیات کد

در ابتدا، تابعی به نام

UpdatedComponent
UpdatedComponent تعریف شده است که به عنوان ورودی، یک آرگومان به نام
OriginalComponent
OriginalComponent دریافت می‌کند. این آرگومان همان کامپوننت React است که قصد داریم آن را با قابلیت‌های جدید wrap کنیم.

در مرحله بعد، به React دستور داده‌ایم که

OriginalComponent
OriginalComponent را در رابط کاربری رندر کند. پیاده‌سازی قابلیت‌های افزوده را در ادامه مقاله انجام خواهیم داد.

استفاده از تابع HOC در کامپوننت‌ها

برای استفاده از HOC ایجاد شده، ابتدا وارد فایل

HoverIncrease.js
HoverIncrease.js می‌شویم و کد زیر را به آن اضافه می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import withCounter from "./withCounter.js" //import the withCounter function
//..further code ..
function HoverIncrease() {
//..further code
}
//replace your 'export' statement with:
export default withCounter(HoverIncrease);
//We have now converted HoverIncrease to an HOC function.
import withCounter from "./withCounter.js" //import the withCounter function //..further code .. function HoverIncrease() { //..further code } //replace your 'export' statement with: export default withCounter(HoverIncrease); //We have now converted HoverIncrease to an HOC function.
import withCounter from "./withCounter.js" //import the withCounter function
//..further code ..
function HoverIncrease() {
//..further code
}
//replace your 'export' statement with:
export default withCounter(HoverIncrease);
//We have now converted HoverIncrease to an HOC function.

در این قطعه کد،

HoverIncrease
HoverIncrease را با تابع
withCounter
withCounter wrap کرده‌ایم تا به یک کامپوننت Higher Order تبدیل شود.

سپس، دقیقاً همان مراحل را برای ماژول

ClickIncrease
ClickIncrease نیز انجام می‌دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//file name: components/ClickIncrease.js
import withCounter from "./withCounter";
function ClickIncrease() {
//...further code
}
export default withCounter(ClickIncrease);
//ClickIncrease is now a wrapped component of the withCounter method.
//file name: components/ClickIncrease.js import withCounter from "./withCounter"; function ClickIncrease() { //...further code } export default withCounter(ClickIncrease); //ClickIncrease is now a wrapped component of the withCounter method.
//file name: components/ClickIncrease.js
import withCounter from "./withCounter";
function ClickIncrease() {
//...further code
}
export default withCounter(ClickIncrease);
//ClickIncrease is now a wrapped component of the withCounter method.

اشتراک‌گذاری props بین کامپوننت‌ها

یکی از ویژگی‌های کلیدی یک کامپوننت Higher Order در React، قابلیت اشتراک‌گذاری props میان کامپوننت‌های wrap شده است.

افزودن prop به HOC

در فایل

withCounter.js
withCounter.js، مقدار
name
name را به صورت prop به کامپوننت داخلی منتقل می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// File: components/withCounter.js
const UpdatedComponent = (OriginalComponent) => {
function NewComponent(props) {
return <OriginalComponent name="LogRocket" {...props} />;
}
return NewComponent;
};
export default UpdatedComponent;
// File: components/withCounter.js const UpdatedComponent = (OriginalComponent) => { function NewComponent(props) { return <OriginalComponent name="LogRocket" {...props} />; } return NewComponent; }; export default UpdatedComponent;
// File: components/withCounter.js
const UpdatedComponent = (OriginalComponent) => {
  function NewComponent(props) {
    return <OriginalComponent name="LogRocket" {...props} />;
  }
  return NewComponent;
};
export default UpdatedComponent;

استفاده از prop در کامپوننت‌های child

اکنون فایل‌های

HoverIncrease.js
HoverIncrease.js و
ClickIncrease.js
ClickIncrease.js را به گونه‌ای ویرایش می‌کنیم که مقدار prop جدید را نمایش دهند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// File: components/HoverIncrease.js
function HoverIncrease(props) {
return (
<div>
Value of 'name' in HoverIncrease: {props.name}
</div>
);
}
export default withCounter(HoverIncrease);
// File: components/ClickIncrease.js
function ClickIncrease(props) {
return (
<div>
Value of 'name' in ClickIncrease: {props.name}
</div>
);
}
export default withCounter(ClickIncrease);
// File: components/HoverIncrease.js function HoverIncrease(props) { return ( <div> Value of 'name' in HoverIncrease: {props.name} </div> ); } export default withCounter(HoverIncrease); // File: components/ClickIncrease.js function ClickIncrease(props) { return ( <div> Value of 'name' in ClickIncrease: {props.name} </div> ); } export default withCounter(ClickIncrease);
// File: components/HoverIncrease.js
function HoverIncrease(props) {
  return (
    <div>
      Value of 'name' in HoverIncrease: {props.name}
    </div>
  );
}
export default withCounter(HoverIncrease);
// File: components/ClickIncrease.js
function ClickIncrease(props) {
  return (
    <div>
      Value of 'name' in ClickIncrease: {props.name}
    </div>
  );
}
export default withCounter(ClickIncrease);

همان‌طور که مشاهده می‌کنیم، با استفاده از HOCها می‌توان به ساده‌ترین شکل ممکن props مشترک را میان چندین کامپوننت به اشتراک گذاشت. این رویکرد در توسعه کامپوننت‌های مقیاس‌پذیر و قابل استفاده مجدد، نقش کلیدی ایفا می‌کند.

اشتراک‌گذاری متغیرهای state با استفاده از هوک‌ها

مشابه props، ما می‌توانیم state و توابع تغییر آن را هم از طریق HOC بین کامپوننت‌ها به اشتراک بگذاریم. این کار باعث می‌شود تا منطق مشترک به صورت ماژولار و قابل استفاده مجدد نوشته شود.

پیاده‌سازی HOC

در فایل

components/withCounter.js
components/withCounter.js، یک HOC تعریف می‌کنیم که یک state
counter
counter و یک تابع
incrementCounter
incrementCounter را مدیریت می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// File: components/withCounter.js
import React, { useState } from 'react';
const withCounter = (OriginalComponent) => {
function NewComponent(props) {
const [counter, setCounter] = useState(10) // Initialize counter state
return (
<OriginalComponent
counter={counter}
incrementCounter={() => setCounter(counter + 1)}
{...props}
/>
)
}
return NewComponent
};
export default withCounter;
// File: components/withCounter.js import React, { useState } from 'react'; const withCounter = (OriginalComponent) => { function NewComponent(props) { const [counter, setCounter] = useState(10) // Initialize counter state return ( <OriginalComponent counter={counter} incrementCounter={() => setCounter(counter + 1)} {...props} /> ) } return NewComponent }; export default withCounter;
// File: components/withCounter.js
import React, { useState } from 'react';

const withCounter = (OriginalComponent) => {
  function NewComponent(props) {
    const [counter, setCounter] = useState(10) // Initialize counter state

    return (
      <OriginalComponent
        counter={counter}
        incrementCounter={() => setCounter(counter + 1)}
        {...props}
      />
    )
  }
  return NewComponent
};

export default withCounter;

توضیح کد:

  • مقدار اولیه state: مقدار پیش‌فرض
    counter
    counter برابر
    ۱۰
    ۱۰ است.
  • تابع افزایش:
    incrementCounter
    incrementCounter با هر بار اجرا، مقدار
    counter
    counter را به اندازه
    increaseCount
    increaseCount افزایش می‌دهد.
  • ارسال props: مقدار state و تابع تغییر آن به عنوان props به کامپوننت wrap شده ارسال می‌شوند.

استفاده از HOC در کامپوننت‌های child

کامپوننت‌های

HoverIncrease
HoverIncrease و
ClickIncrease
ClickIncrease را برای استفاده از state و تابع مشترک تغییر می‌دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// File: components/HoverIncrease.js
import withCounter from './withCounter'
function HoverIncrease(props) {
return (
<div onMouseOver={props.incrementCounter}>
<p>Value of 'counter' in HoverIncrease: {props.counter}</p>
</div>
)
}
export default withCounter(HoverIncrease)
// File: components/ClickIncrease.js
import withCounter from './withCounter'
function ClickIncrease(props) {
return (
<button onClick={props.incrementCounter}>
Increment counter
</button>
<p>Value of 'counter' in ClickIncrease: {props.counter}</p>
)
}
export default withCounter(ClickIncrease)
// File: components/HoverIncrease.js import withCounter from './withCounter' function HoverIncrease(props) { return ( <div onMouseOver={props.incrementCounter}> <p>Value of 'counter' in HoverIncrease: {props.counter}</p> </div> ) } export default withCounter(HoverIncrease) // File: components/ClickIncrease.js import withCounter from './withCounter' function ClickIncrease(props) { return ( <button onClick={props.incrementCounter}> Increment counter </button> <p>Value of 'counter' in ClickIncrease: {props.counter}</p> ) } export default withCounter(ClickIncrease)
// File: components/HoverIncrease.js
import withCounter from './withCounter'

function HoverIncrease(props) {
  return (
    <div onMouseOver={props.incrementCounter}>
      <p>Value of 'counter' in HoverIncrease: {props.counter}</p>
    </div>
  )
}

export default withCounter(HoverIncrease)
// File: components/ClickIncrease.js
import withCounter from './withCounter'

function ClickIncrease(props) {
  return (
    <button onClick={props.incrementCounter}>
      Increment counter
    </button>
    <p>Value of 'counter' in ClickIncrease: {props.counter}</p>
  )
}

export default withCounter(ClickIncrease)

با وجود این‌که HOCها امکان اشتراک منطق و state را فراهم می‌کنند، اما بین نسخه‌های مختلف یک کامپوننت wrap شده، state به اشتراک گذاشته نمی‌شود.
اگر نیاز به state سراسری در کل اپلیکیشن داریم، بهتر است از Context API استفاده کنیم.

ارسال پارامترها

اگرچه در حال حاضر کد ما به درستی عمل می‌کند، اما یک سناریو را در نظر می‌گیریم؛ اگر بخواهیم مقدار متغیر

counter
counter را با یک عدد دلخواه افزایش دهیم، چه کاری باید انجام دهیم؟
خوشبختانه، با استفاده از HOCها، می‌توانیم داده‌های خاصی مانند یک مقدار عددی مشخص را نیز به کامپوننت‌های child منتقل کنیم. این قابلیت از طریق ارسال پارامترها به تابع HOC فراهم می‌شود.

فعال‌سازی پشتیبانی از پارامترها

برای افزودن این قابلیت، ابتدا فایل

components/withCounter.js
components/withCounter.js را به گونه‌ای تغییر می‌دهیم که یک پارامتر جدید به نام
increaseCount
increaseCount را بپذیرد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//This function will now accept an 'increaseCount' parameter.
const UpdatedComponent = (OriginalComponent, increaseCount) => {
function NewComponent(props) {
return (
//this time, increment the 'size' variable by 'increaseCount'
incrementCounter={() => setCounter((size) => size + increaseCount)}
/>
);
//further code..
//This function will now accept an 'increaseCount' parameter. const UpdatedComponent = (OriginalComponent, increaseCount) => { function NewComponent(props) { return ( //this time, increment the 'size' variable by 'increaseCount' incrementCounter={() => setCounter((size) => size + increaseCount)} /> ); //further code..
//This function will now accept an 'increaseCount' parameter.
const UpdatedComponent = (OriginalComponent, increaseCount) => { 
function NewComponent(props) {
return (
//this time, increment the 'size' variable by 'increaseCount'
incrementCounter={() => setCounter((size) => size + increaseCount)}
/>
);
//further code..

در این قطعه کد، به React اطلاع داده‌ایم که تابع ما اکنون علاوه بر کامپوننت اصلی (

OriginalComponent
OriginalComponent)، یک پارامتر عددی به نام
increaseCount
increaseCount نیز دریافت خواهد کرد.
از این پارامتر برای تعیین میزان افزایش مقدار
counter
counter استفاده می‌شود.

استفاده از پارامترها در کامپوننت‌های child

کامپوننت‌های

HoverIncrease
HoverIncrease و
ClickIncrease
ClickIncrease را برای استفاده از این پارامتر، به‌روزرسانی می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//In HoverIncrease, change the 'export' statement:
export default withCounter(HoverIncrease, 10); //value of increaseCount is 10.
//this will increment the 'counter' Hook by 10.
//In ClickIncrease:
export default withCounter(ClickIncrease, 3); //value of increaseCount is 3.
//will increment the 'counter' state by 3 steps.
//In HoverIncrease, change the 'export' statement: export default withCounter(HoverIncrease, 10); //value of increaseCount is 10. //this will increment the 'counter' Hook by 10. //In ClickIncrease: export default withCounter(ClickIncrease, 3); //value of increaseCount is 3. //will increment the 'counter' state by 3 steps.
//In HoverIncrease, change the 'export' statement:
export default withCounter(HoverIncrease, 10); //value of increaseCount is 10.
//this will increment the 'counter' Hook by 10.
//In ClickIncrease:
export default withCounter(ClickIncrease, 3); //value of increaseCount is 3.
//will increment the 'counter' state by 3 steps.

با ارسال یک مقدار سفارشی (

increaseCount
increaseCount) به HOC، می‌توانیم رفتار افزایش را در هر کامپوننت wrap شده به صورت داینامیک کنترل نماییم.

در نهایت، فایل

withCounter.js
withCounter.js باید به شکل زیر باشد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from "react";
import { useState } from "react";
const UpdatedComponent = (OriginalComponent, increaseCount) => {
function NewComponent(props) {
const [counter, setCounter] = useState(10);
return (
name="LogRocket"
counter={counter}
incrementCounter={() => setCounter((size) => size + increaseCount)}
/>
);
}
return NewComponent;
};
export default UpdatedComponent;
import React from "react"; import { useState } from "react"; const UpdatedComponent = (OriginalComponent, increaseCount) => { function NewComponent(props) { const [counter, setCounter] = useState(10); return ( name="LogRocket" counter={counter} incrementCounter={() => setCounter((size) => size + increaseCount)} /> ); } return NewComponent; }; export default UpdatedComponent;
import React from "react";
import { useState } from "react";
const UpdatedComponent = (OriginalComponent, increaseCount) => {
function NewComponent(props) {
const [counter, setCounter] = useState(10);
return (
name="LogRocket"
counter={counter}
incrementCounter={() => setCounter((size) => size + increaseCount)}
/>
);
}
return NewComponent;
};
export default UpdatedComponent;

فایل

HoverIncrease.js
HoverIncrease.js نیز باید به صورت زیر باشد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useState } from "react";
import withCounter from "./withCounter";
function HoverIncrease(props) {
const [fontSize, setFontSize] = useState(10);
const { counter, incrementCounter } = props;
return (
setFontSize((size) => size + 1)}>
Increase on hover
Size of font in onMouseOver function: {fontSize}
Value of 'name' in HoverIncrease: {props.name}
incrementCounter()}>Increment counter
Value of 'counter' in HoverIncrease: {counter}
);
}
export default withCounter(HoverIncrease, 10);
import { useState } from "react"; import withCounter from "./withCounter"; function HoverIncrease(props) { const [fontSize, setFontSize] = useState(10); const { counter, incrementCounter } = props; return ( setFontSize((size) => size + 1)}> Increase on hover Size of font in onMouseOver function: {fontSize} Value of 'name' in HoverIncrease: {props.name} incrementCounter()}>Increment counter Value of 'counter' in HoverIncrease: {counter} ); } export default withCounter(HoverIncrease, 10);
import { useState } from "react";
import withCounter from "./withCounter";
function HoverIncrease(props) {
const [fontSize, setFontSize] = useState(10);
const { counter, incrementCounter } = props;
return (
setFontSize((size) => size + 1)}>
Increase on hover
Size of font in onMouseOver function: {fontSize}
Value of 'name' in HoverIncrease: {props.name}
incrementCounter()}>Increment counter
Value of 'counter' in HoverIncrease: {counter}
);
}
export default withCounter(HoverIncrease, 10);

و در نهایت، کامپوننت

ClickIncrease
ClickIncrease ما باید کد زیر را داشته باشد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useEffect, useState } from "react";
import withCounter from "./withCounter";
function ClickIncrease(props) {
const { counter, incrementCounter } = props;
const [fontSize, setFontSize] = useState(10);
return (
setFontSize((size) => size + 1)}>
Increase with click
Size of font in onClick function: {fontSize}
Value of 'name' in ClickIncrease: {props.name}
incrementCounter()}>Increment counter
Value of 'counter' in ClickIncrease: {counter}
);
}
export default withCounter(ClickIncrease, 3);
import { useEffect, useState } from "react"; import withCounter from "./withCounter"; function ClickIncrease(props) { const { counter, incrementCounter } = props; const [fontSize, setFontSize] = useState(10); return ( setFontSize((size) => size + 1)}> Increase with click Size of font in onClick function: {fontSize} Value of 'name' in ClickIncrease: {props.name} incrementCounter()}>Increment counter Value of 'counter' in ClickIncrease: {counter} ); } export default withCounter(ClickIncrease, 3);
import { useEffect, useState } from "react";
import withCounter from "./withCounter";
function ClickIncrease(props) {
const { counter, incrementCounter } = props;
const [fontSize, setFontSize] = useState(10);
return (
setFontSize((size) => size + 1)}>
Increase with click
Size of font in onClick function: {fontSize}
Value of 'name' in ClickIncrease: {props.name}
incrementCounter()}>Increment counter
Value of 'counter' in ClickIncrease: {counter}
);
}
export default withCounter(ClickIncrease, 3);

مقایسه HOC و Hook: کدام را انتخاب کنیم؟

انتخاب بین کامپوننت Higher Order و هوک‌ها، بستگی به دو عامل کلیدی دارد:

  • نوع تغییر در کامپوننت‌ها
  • نحوه سازمان‌دهی کد

تغییر در ساختار کامپوننت

از HOC استفاده می‌کنیم زمانی که نیاز داریم:

  • رفتار رندرینگ کامپوننت را تغییر دهیم، مانند رندر مشروط بر اساس سطح دسترسی یا مجوز کاربر
  • دغدغه‌های عمومی مانند احراز هویت، ثبت لاگ، مدیریت خطا، یا رندرینگ شرطی را مدیریت کنیم

از هوک استفاده می‌کنیم زمانی که نیاز داریم:

  • منطق دارای state را بین کامپوننت‌ها به اشتراک بگذاریم، مانند مدیریت فرم‌ها یا دریافت داده، بدون اینکه سلسله مراتب کامپوننت‌ها را تغییر دهیم
  • باside effectها مانند اشتراک‌گذاری داده، تایمرها یا فراخوانی API که باید در زمان Mount یا Update کامپوننت اجرا شوند، کار کنیم

سازمان‌دهی کد

  • HOCها برای ایجاد Wrapperهای یکپارچه و مدیریت تغییرات پیچیده در ساختار کامپوننت‌ها بسیار مناسب هستند.
  • هوک‌ها به ما این امکان را می‌دهند تا منطق‌های دارای state را به صورت کارآمد و قابل ترکیب بنویسیم، بدون نیاز به افزودن لایه‌های اضافی در درخت کامپوننت‌ها.

الگوهای مدرن پیاده‌سازی

در بسیاری از پروژه‌های امروزی، از ترکیب HOC و هوک برای ساختاردهی بهتر و انعطاف‌پذیری بیشتر استفاده می‌شود.

نمونه واقعی: احراز هویت

در مثال زیر، از یک HOC با استفاده از یک هوک سفارشی (

useAuth
useAuth) برای کنترل دسترسی به یک داشبورد ادمین استفاده شده است:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Authentication HOC
const withAuth = (WrappedComponent, requiredRole) => {
return function AuthWrapper(props) {
const { isAuthenticated, userRole } = useAuth(); // Custom hook for auth state
const navigate = useNavigate();
useEffect(() => {
if (!isAuthenticated) {
navigate('/login');
} else if (requiredRole && userRole !== requiredRole) {
navigate('/unauthorized');
}
}, [isAuthenticated, userRole, navigate]);
if (!isAuthenticated) {
return null; // Optionally return a loader while determining authentication
}
return <WrappedComponent {...props} />;
};
};
// Usage with a protected component
const AdminDashboard = ({ data }) => {
return <div>Admin Dashboard Content</div>;
};
export default withAuth(AdminDashboard, 'admin');
// Authentication HOC const withAuth = (WrappedComponent, requiredRole) => { return function AuthWrapper(props) { const { isAuthenticated, userRole } = useAuth(); // Custom hook for auth state const navigate = useNavigate(); useEffect(() => { if (!isAuthenticated) { navigate('/login'); } else if (requiredRole && userRole !== requiredRole) { navigate('/unauthorized'); } }, [isAuthenticated, userRole, navigate]); if (!isAuthenticated) { return null; // Optionally return a loader while determining authentication } return <WrappedComponent {...props} />; }; }; // Usage with a protected component const AdminDashboard = ({ data }) => { return <div>Admin Dashboard Content</div>; }; export default withAuth(AdminDashboard, 'admin');
// Authentication HOC
const withAuth = (WrappedComponent, requiredRole) => {
  return function AuthWrapper(props) {
    const { isAuthenticated, userRole } = useAuth(); // Custom hook for auth state
    const navigate = useNavigate();

    useEffect(() => {
      if (!isAuthenticated) {
        navigate('/login');
      } else if (requiredRole && userRole !== requiredRole) {
        navigate('/unauthorized');
      }
    }, [isAuthenticated, userRole, navigate]);

    if (!isAuthenticated) {
      return null; // Optionally return a loader while determining authentication
    }

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

// Usage with a protected component
const AdminDashboard = ({ data }) => {
  return <div>Admin Dashboard Content</div>;
};

export default withAuth(AdminDashboard, 'admin');

مثال دیگر: بهینه‌سازی عملکرد با ترکیب هوک‌ها درون HOC

در مثال‌های پیچیده‌تر، می‌توانیم از هوک‌ها برای بهینه‌سازی عملکرد یا مدیریت منطق خاص درون HOC استفاده کنیم. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Performance optimization HOC using hooks
const withDataFetching = (WrappedComponent, fetchConfig) => {
return function DataFetchingWrapper(props) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const { cache } = useCacheContext();
const { notify } = useNotification();
useEffect(() => {
const fetchData = async () => {
try {
const cachedData = cache.get(fetchConfig.key);
if (cachedData) {
setData(cachedData);
setLoading(false);
return;
}
const response = await fetch(fetchConfig.url);
const result = await response.json();
cache.set(fetchConfig.key, result);
setData(result);
} catch (err) {
setError(err);
notify({
type: 'error',
message: 'Failed to fetch data',
});
} finally {
setLoading(false);
}
};
fetchData();
}, [fetchConfig.url, fetchConfig.key]);
return <WrappedComponent {...props} data={data} loading={loading} error={error} />;
};
};
// Performance optimization HOC using hooks const withDataFetching = (WrappedComponent, fetchConfig) => { return function DataFetchingWrapper(props) { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const { cache } = useCacheContext(); const { notify } = useNotification(); useEffect(() => { const fetchData = async () => { try { const cachedData = cache.get(fetchConfig.key); if (cachedData) { setData(cachedData); setLoading(false); return; } const response = await fetch(fetchConfig.url); const result = await response.json(); cache.set(fetchConfig.key, result); setData(result); } catch (err) { setError(err); notify({ type: 'error', message: 'Failed to fetch data', }); } finally { setLoading(false); } }; fetchData(); }, [fetchConfig.url, fetchConfig.key]); return <WrappedComponent {...props} data={data} loading={loading} error={error} />; }; };
// Performance optimization HOC using hooks
const withDataFetching = (WrappedComponent, fetchConfig) => {
  return function DataFetchingWrapper(props) {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    const [loading, setLoading] = useState(true);

    const { cache } = useCacheContext();
    const { notify } = useNotification();

    useEffect(() => {
      const fetchData = async () => {
        try {
          const cachedData = cache.get(fetchConfig.key);
          if (cachedData) {
            setData(cachedData);
            setLoading(false);
            return;
          }

          const response = await fetch(fetchConfig.url);
          const result = await response.json();

          cache.set(fetchConfig.key, result);
          setData(result);
        } catch (err) {
          setError(err);
          notify({
            type: 'error',
            message: 'Failed to fetch data',
          });
        } finally {
          setLoading(false);
        }
      };

      fetchData();
    }, [fetchConfig.url, fetchConfig.key]);

    return <WrappedComponent {...props} data={data} loading={loading} error={error} />;
  };
};

ملاحظات عملکردی

اگر HOC ما شامل محاسبات سنگین یا زمان‌بر باشد، پیشنهاد می‌شود از تکنیک‌هایی مانند حافظه‌سازی (Memoization) برای جلوگیری از رندرهای غیرضروری استفاده نماییم.

در مثال زیر، از

useMemo و
React.memo
React.memo برای بهینه‌سازی عملکرد بهره گرفته‌ایم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Assume expensiveDataProcessing is an expensive function that processes props.data
const expensiveDataProcessing = (data) => {
// ...expensive computations...
return data; // Replace with the actual processed result
};
const withOptimizedData = (WrappedComponent) => {
function OptimizedDataWrapper(props) {
const memoizedProps = useMemo(() => ({
...props,
processedData: expensiveDataProcessing(props.data),
}), [props.data]);
return <WrappedComponent {...memoizedProps} />;
}
return React.memo(OptimizedDataWrapper);
};
export default withOptimizedData;
// Assume expensiveDataProcessing is an expensive function that processes props.data const expensiveDataProcessing = (data) => { // ...expensive computations... return data; // Replace with the actual processed result }; const withOptimizedData = (WrappedComponent) => { function OptimizedDataWrapper(props) { const memoizedProps = useMemo(() => ({ ...props, processedData: expensiveDataProcessing(props.data), }), [props.data]); return <WrappedComponent {...memoizedProps} />; } return React.memo(OptimizedDataWrapper); }; export default withOptimizedData;
// Assume expensiveDataProcessing is an expensive function that processes props.data

const expensiveDataProcessing = (data) => { 
    // ...expensive computations... 
    return data; // Replace with the actual processed result 
};

const withOptimizedData = (WrappedComponent) => { 
    function OptimizedDataWrapper(props) { 
      const memoizedProps = useMemo(() => ({ 
          ...props, 
          processedData: expensiveDataProcessing(props.data), 
      }), [props.data]); 
      return <WrappedComponent {...memoizedProps} />; 
  }
  return React.memo(OptimizedDataWrapper); 
}; 
export default withOptimizedData;

الگوهای رایج HOC و بهترین روش‌ها

ترکیب چند HOC

زمانی که نیاز داریم یک کامپوننت پایه را با چندین موضوع مشترک (مانند احراز هویت، دریافت داده، مدیریت خطا، و آنالیتیکس) توسعه دهیم، می‌توانیم چندین HOC را ترکیب کنیم.

روش مستقیم ترکیب HOCها به شکل زیر می‌باشد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const composedComponent = withAuth(withData(withLogging(BaseComponent)));
const composedComponent = withAuth(withData(withLogging(BaseComponent)));
const composedComponent = withAuth(withData(withLogging(BaseComponent)));

استفاده از تابع کمکی compose

همچنین، برای ترکیب توابع از راست به چپ، می‌توانیم از یک utility به‌نام

compose
compose استفاده کنیم (مشابه آنچه در کتابخانه‌هایی مانند Redux وجود دارد):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Utility
const compose = (...functions) => x =>
functions.reduceRight((acc, fn) => fn(acc), x);
// Usage
const composedComponent = compose(withAuth, withData, withLogging)(BaseComponent);
// Utility const compose = (...functions) => x => functions.reduceRight((acc, fn) => fn(acc), x); // Usage const composedComponent = compose(withAuth, withData, withLogging)(BaseComponent);
// Utility
const compose = (...functions) => x =>
  functions.reduceRight((acc, fn) => fn(acc), x);

// Usage
const composedComponent = compose(withAuth, withData, withLogging)(BaseComponent);

نکات کلیدی در ترکیب HOCها

Order matters

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// These will behave differently:
const enhance1 = compose(withAuth, withDataFetching);
const enhance2 = compose(withDataFetching, withAuth);
// These will behave differently: const enhance1 = compose(withAuth, withDataFetching); const enhance2 = compose(withDataFetching, withAuth);
// These will behave differently:
const enhance1 = compose(withAuth, withDataFetching);
const enhance2 = compose(withDataFetching, withAuth);

Props flow

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Props flow through each HOC in the chain
const withProps = compose(
withAuth, // Adds isAuthenticated
withDataFetching // Adds data, loading
);
// Final component receives: { isAuthenticated, data, loading, ...originalProps }
// Props flow through each HOC in the chain const withProps = compose( withAuth, // Adds isAuthenticated withDataFetching // Adds data, loading ); // Final component receives: { isAuthenticated, data, loading, ...originalProps }
// Props flow through each HOC in the chain
const withProps = compose(
  withAuth,        // Adds isAuthenticated
  withDataFetching // Adds data, loading
);
// Final component receives: { isAuthenticated, data, loading, ...originalProps }

ملاحظات عملکردی

استفاده بیش از حد از ترکیب HOCها می‌تواند به افزایش پیچیدگی در درخت کامپوننت‌ها و کاهش عملکرد منجر شود.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const tooManyHOCs = compose(
withAuth,
withData,
withLogging,
withTheme,
withTranslation,
withRouter,
withRedux
);
// Each layer adds complexity and potential performance impact
const tooManyHOCs = compose( withAuth, withData, withLogging, withTheme, withTranslation, withRouter, withRedux ); // Each layer adds complexity and potential performance impact
const tooManyHOCs = compose(
  withAuth,
  withData,
  withLogging,
  withTheme,
  withTranslation,
  withRouter,
  withRedux
);
// Each layer adds complexity and potential performance impact

روش بهتر این است که دغدعه‌های مرتبط را در یک HOC ترکیب نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const withDataFeatures = compose(
withData,
withLoading,
withError
);
const withAppFeatures = compose(
withAuth,
withAnalytics
);
const withDataFeatures = compose( withData, withLoading, withError ); const withAppFeatures = compose( withAuth, withAnalytics );
const withDataFeatures = compose(
  withData,
  withLoading,
  withError
);

const withAppFeatures = compose(
  withAuth,
  withAnalytics
);

Debugging

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const withDebug = (WrappedComponent) => {
return function DebugWrapper(props) {
console.log('Component:', WrappedComponent.name);
console.log('Props:', props);
return <WrappedComponent {...props} />;
};
};
const enhance = compose(
withDebug, // Add at different positions to debug specific layers
withAuth,
withDebug,
withDataFetching
);
const withDebug = (WrappedComponent) => { return function DebugWrapper(props) { console.log('Component:', WrappedComponent.name); console.log('Props:', props); return <WrappedComponent {...props} />; }; }; const enhance = compose( withDebug, // Add at different positions to debug specific layers withAuth, withDebug, withDataFetching );
const withDebug = (WrappedComponent) => {
  return function DebugWrapper(props) {
    console.log('Component:', WrappedComponent.name);
    console.log('Props:', props);
    return <WrappedComponent {...props} />;
  };
};

const enhance = compose(
  withDebug, // Add at different positions to debug specific layers
  withAuth,
  withDebug,
  withDataFetching
);

ترکیب‌های قابل استفاده مجدد

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const withDataProtection = compose(
withAuth,
withErrorBoundary,
withLoading
);
const withAnalytics = compose(
withTracking,
withMetrics,
withLogging
);
// Use them together or separately
const EnhancedComponent = compose(
withDataProtection,
withAnalytics
)(BaseComponent);
const withDataProtection = compose( withAuth, withErrorBoundary, withLoading ); const withAnalytics = compose( withTracking, withMetrics, withLogging ); // Use them together or separately const EnhancedComponent = compose( withDataProtection, withAnalytics )(BaseComponent);
const withDataProtection = compose(
  withAuth,
  withErrorBoundary,
  withLoading
);

const withAnalytics = compose(
  withTracking,
  withMetrics,
  withLogging
);

// Use them together or separately
const EnhancedComponent = compose(
  withDataProtection,
  withAnalytics
)(BaseComponent);

افزودن Type-Safety

استفاده از تایپ اسکریپت در پیاده‌سازی HOCها، خوانایی و نگه‌داری کد را به شکل چشمگیری بهبود می‌بخشد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React, { useState, useEffect } from 'react';
interface WithDataProps<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
interface FetchConfig {
url: string;
}
function withData<T, P extends object>(
WrappedComponent: React.ComponentType<P & WithDataProps<T>>,
fetchConfig: FetchConfig
): React.FC<P> {
return function WithDataComponent(props: P) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(fetchConfig.url)
.then((response) => response.json())
.then((result: T) => {
setData(result);
setLoading(false);
})
.catch((err: Error) => {
setError(err);
setLoading(false);
});
}, [fetchConfig.url]);
return (
<WrappedComponent {...props} data={data} loading={loading} error={error} />
);
};
}
export default withData;
import React, { useState, useEffect } from 'react'; interface WithDataProps<T> { data: T | null; loading: boolean; error: Error | null; } interface FetchConfig { url: string; } function withData<T, P extends object>( WrappedComponent: React.ComponentType<P & WithDataProps<T>>, fetchConfig: FetchConfig ): React.FC<P> { return function WithDataComponent(props: P) { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState<boolean>(true); const [error, setError] = useState<Error | null>(null); useEffect(() => { fetch(fetchConfig.url) .then((response) => response.json()) .then((result: T) => { setData(result); setLoading(false); }) .catch((err: Error) => { setError(err); setLoading(false); }); }, [fetchConfig.url]); return ( <WrappedComponent {...props} data={data} loading={loading} error={error} /> ); }; } export default withData;
import React, { useState, useEffect } from 'react';

interface WithDataProps<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

interface FetchConfig {
  url: string;
}

function withData<T, P extends object>(
  WrappedComponent: React.ComponentType<P & WithDataProps<T>>,
  fetchConfig: FetchConfig
): React.FC<P> {
  return function WithDataComponent(props: P) {
    const [data, setData] = useState<T | null>(null);
    const [loading, setLoading] = useState<boolean>(true);
    const [error, setError] = useState<Error | null>(null);

    useEffect(() => {
      fetch(fetchConfig.url)
        .then((response) => response.json())
        .then((result: T) => {
          setData(result);
          setLoading(false);
        })
        .catch((err: Error) => {
          setError(err);
          setLoading(false);
        });
    }, [fetchConfig.url]);

    return (
      <WrappedComponent {...props} data={data} loading={loading} error={error} />
    );
  };
}

export default withData;

مشکل رایج در HOC: نحوه صحیح ارسال props

یکی از نکات مهم در استفاده از HOCها، نحوه صحیح ارسال props به کامپوننت child است. فرایند ارسال props در HOCها کمی متفاوت از کامپوننت‌های معمولی است.

برای مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function App() {
return (
{/*Pass in a 'secretWord' prop*/}
);
}
function HoverIncrease(props) {
//read prop value:
console.log("Value of secretWord: " + props.secretWord);
//further code..
}
function App() { return ( {/*Pass in a 'secretWord' prop*/} ); } function HoverIncrease(props) { //read prop value: console.log("Value of secretWord: " + props.secretWord); //further code.. }
function App() {
return (
{/*Pass in a 'secretWord' prop*/}

);
}
function HoverIncrease(props) {
//read prop value:
console.log("Value of secretWord: " + props.secretWord);
//further code..
}

در این تئوری، باید در کنسول پیامی مشابه زیر مشاهده نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Value of secretWord: pineapple
Value of secretWord: pineapple
Value of secretWord: pineapple

اما در واقعیت، چنین چیزی چاپ نمی‌شود و

Value of secretWord: undefined
Value of secretWord: undefined را در کنسول مشاهده می‌کنیم. علت این است که prop با نام
secretWord
secretWord به جای اینکه به کامپوننت
HoverIncrease
HoverIncrease برسد، به تابع
withCounter
withCounter داده می‌شود و در ادامه به child منتقل نمی‌شود.

راه‌حل: اصلاح جزئی در فایل withCounter.js

برای حل این مشکل، تنها کافی است تمام props ورودی را به کامپوننت اصلی منتقل کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const UpdatedComponent = (OriginalComponent, increaseCount) => {
function NewComponent(props) {
return (
//Pass down all incoming props to the HOC's children:
{...props}
/>
);
}
return NewComponent;
};
const UpdatedComponent = (OriginalComponent, increaseCount) => { function NewComponent(props) { return ( //Pass down all incoming props to the HOC's children: {...props} /> ); } return NewComponent; };
const UpdatedComponent = (OriginalComponent, increaseCount) => {
function NewComponent(props) {
return (
//Pass down all incoming props to the HOC's children:
{...props}
/>
);
}
return NewComponent;
};

این تغییر کوچک، مشکل را به طور کامل برطرف می‌کند.

جمع‌بندی

در این مقاله، مفاهیم پایه‌ای و پیشرفته مربوط به کامپوننت‌های Higher Order در React را بررسی کردیم. HOCها ابزار قدرتمندی برای ساخت اپلیکیشن‌های React با منطق‌های قابل‌استفاده مجدد هستند. با رعایت اصول بهینه‌سازی، ترکیب صحیح، و ساختاردهی حرفه‌ای، می‌توانیم از HOCها در کنار هوک‌ها برای ایجاد معماری‌های مدرن و مقیاس‌پذیر بهره ببریم.

دیدگاه‌ها:

افزودن دیدگاه جدید