روشی که forwardRef در تایپ اسکریپت پیاده‌سازی می‌شود دارای محدودیت‌هایی می‌باشد که بزرگ‌ترین آن این است که استنتاج تایپ را در کامپوننت‌های Generic غیرفعال می‌کند. در این مقاله قصد داریم تا استفاده از forwardRef در کامپوننت‌های Generic را باهم بررسی نماییم.

کامپوننت Generic چیست؟

یکی از موارد استفاده رایج از کامپوننت‌های Generic، مفهوم

Table
Table است:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const Table = <T,>(props: {
data: T[];
renderRow: (row: T) => React.ReactNode;
}) => {
return (
<table>
<tbody>
{props.data.map((item, index) => (
<props.renderRow key={index} {...item} />
))}
</tbody>
</table>
);
};
const Table = <T,>(props: { data: T[]; renderRow: (row: T) => React.ReactNode; }) => { return ( <table> <tbody> {props.data.map((item, index) => ( <props.renderRow key={index} {...item} /> ))} </tbody> </table> ); };
const Table = <T,>(props: {
  data: T[];
  renderRow: (row: T) => React.ReactNode;
}) => {
  return (
    <table>
      <tbody>
        {props.data.map((item, index) => (
          <props.renderRow key={index} {...item} />
        ))}
      </tbody>
    </table>
  );
};

در مثال بالا، هنگامی که ما آرایه‌ای از هر چیزی را به

data
data ارسال می‌کنیم، آن آرایه در آرگومان ارسال شده به تابع
renderRow
renderRow استنتاج تایپ را انجام می‌دهد.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Table
// ۱٫ Data is a string here...
data={["a", "b"]}
// ۲٫ So ends up inferring as a string in renderRow.
renderRow={(row) => {
(parameter) row: string
return <tr>{row}</tr>;
}}
/>;
<Table
// ۳٫ Data is a number here...
data={[1, 2]}
// ۴٫ So ends up inferring as a number in renderRow.
renderRow={(row) => {
(parameter) row: number
return <tr>{row}</tr>;
}}
/>;
<Table // ۱٫ Data is a string here... data={["a", "b"]} // ۲٫ So ends up inferring as a string in renderRow. renderRow={(row) => { (parameter) row: string return <tr>{row}</tr>; }} />; <Table // ۳٫ Data is a number here... data={[1, 2]} // ۴٫ So ends up inferring as a number in renderRow. renderRow={(row) => { (parameter) row: number return <tr>{row}</tr>; }} />;
<Table
  // ۱٫ Data is a string here...
  data={["a", "b"]}
  // ۲٫ So ends up inferring as a string in renderRow.
  renderRow={(row) => {
              
(parameter) row: string
    return <tr>{row}</tr>;
  }}
/>;

<Table
  // ۳٫ Data is a number here...
  data={[1, 2]}
  // ۴٫ So ends up inferring as a number in renderRow.
  renderRow={(row) => {
              
(parameter) row: number
    return <tr>{row}</tr>;
  }}
/>;

این موضوع واقعا مفید است، زیرا به این معنی می‌باشد که ما می‌توانیم بدون نوشتن هیچگونه annotation اضافی، استنتاج تایپ را در تابع

renderRow
renderRow انجام دهیم.

بررسی مشکل ForwardRef

زمانی که ما سعی می‌کنیم تا یک

ref
ref را به کامپوننت
Table
Table خود اضافه کنیم، محدودیت و مشکل مربوط به forwardRef مطرح می‌شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const Table = <T,>(
props: {
data: T[];
renderRow: (row: T) => React.ReactNode;
},
ref: React.ForwardedRef<HTMLTableElement>
) => {
return (
<table ref={ref}>
<tbody>
{props.data.map((item, index) => (
<props.renderRow key={index} {...item} />
))}
</tbody>
</table>
);
};
const ForwardReffedTable = React.forwardRef(Table);
const Table = <T,>( props: { data: T[]; renderRow: (row: T) => React.ReactNode; }, ref: React.ForwardedRef<HTMLTableElement> ) => { return ( <table ref={ref}> <tbody> {props.data.map((item, index) => ( <props.renderRow key={index} {...item} /> ))} </tbody> </table> ); }; const ForwardReffedTable = React.forwardRef(Table);
const Table = <T,>(
  props: {
    data: T[];
    renderRow: (row: T) => React.ReactNode;
  },
  ref: React.ForwardedRef<HTMLTableElement>
) => {
  return (
    <table ref={ref}>
      <tbody>
        {props.data.map((item, index) => (
          <props.renderRow key={index} {...item} />
        ))}
      </tbody>
    </table>
  );
};

const ForwardReffedTable = React.forwardRef(Table);

کدی که داریم در نگاه اول درست به نظر می‌رسد، اما وقتی از کامپوننت

ForwardReffedTable
ForwardReffedTable استفاده می‌کنیم استنتاجی که قبلاً دیدیم دیگر کار نمی‌کند.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<ForwardReffedTable
// ۱٫ Data is a string here...
data={["a", "b"]}
// ۲٫ But ends up being inferred as unknown.
renderRow={(row) => {
(parameter) row: unknown
return <tr />;
}}
/>;
<ForwardReffedTable
// ۳٫ Data is a number here...
data={[1, 2]}
// ۴٫ But still ends up being inferred as unknown.
renderRow={(row) => {
(parameter) row: unknown
return <tr />;
}}
/>;
<ForwardReffedTable // ۱٫ Data is a string here... data={["a", "b"]} // ۲٫ But ends up being inferred as unknown. renderRow={(row) => { (parameter) row: unknown return <tr />; }} />; <ForwardReffedTable // ۳٫ Data is a number here... data={[1, 2]} // ۴٫ But still ends up being inferred as unknown. renderRow={(row) => { (parameter) row: unknown return <tr />; }} />;
<ForwardReffedTable
  // ۱٫ Data is a string here...
  data={["a", "b"]}
  // ۲٫ But ends up being inferred as unknown.
  renderRow={(row) => {
              
(parameter) row: unknown
    return <tr />;
  }}
/>;

<ForwardReffedTable
  // ۳٫ Data is a number here...
  data={[1, 2]}
  // ۴٫ But still ends up being inferred as unknown.
  renderRow={(row) => {
              
(parameter) row: unknown
    return <tr />;
  }}
/>;

این موضوع می‌تواند مشکلات زیادی را در برنامه ما ایجاد کند. اما ما می‌توانیم آن را برطرف نماییم.

بررسی راه حل

ما می‌توانیم با استفاده از یک تعریف تایپ دیگر، forwardRef را دوباره تعریف نماییم و در نتیجه، محدودیت استفاده از آن را برطرف کنیم.

در مثال زیر تعریف جدید را اضافه می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function fixedForwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactNode
): (props: P & React.RefAttributes<T>) => React.ReactNode {
return React.forwardRef(render) as any;
}
function fixedForwardRef<T, P = {}>( render: (props: P, ref: React.Ref<T>) => React.ReactNode ): (props: P & React.RefAttributes<T>) => React.ReactNode { return React.forwardRef(render) as any; }
function fixedForwardRef<T, P = {}>(
  render: (props: P, ref: React.Ref<T>) => React.ReactNode
): (props: P & React.RefAttributes<T>) => React.ReactNode {
  return React.forwardRef(render) as any;
}

می‌توانیم تعریف خود را برای استفاده از

fixedForwardRef
fixedForwardRef تغییر دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const ForwardReffedTable = fixedForwardRef(Table);
const ForwardReffedTable = fixedForwardRef(Table);
const ForwardReffedTable = fixedForwardRef(Table);

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<ForwardReffedTable
data={["a", "b"]}
renderRow={(row) => {
(parameter) row: string
return <tr />;
}}
/>;
<ForwardReffedTable
data={[1, 2]}
renderRow={(row) => {
(parameter) row: number
return <tr />;
}}
/>;
<ForwardReffedTable data={["a", "b"]} renderRow={(row) => { (parameter) row: string return <tr />; }} />; <ForwardReffedTable data={[1, 2]} renderRow={(row) => { (parameter) row: number return <tr />; }} />;
<ForwardReffedTable
  data={["a", "b"]}
  renderRow={(row) => {
              
(parameter) row: string
    return <tr />;
  }}
/>;

<ForwardReffedTable
  data={[1, 2]}
  renderRow={(row) => {
              
(parameter) row: number
    return <tr />;
  }}
/>;

جمع‌بندی

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