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

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

یکی از موارد استفاده رایج از کامپوننت‌های Generic، مفهوم 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 ارسال می‌کنیم، آن آرایه در آرگومان ارسال شده به تابع renderRow استنتاج تایپ را انجام می‌دهد.

<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 انجام دهیم.

بررسی مشکل ForwardRef

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

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
  // ۱٫ 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 را دوباره تعریف نماییم و در نتیجه، محدودیت استفاده از آن را برطرف کنیم.

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

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 تغییر دهیم:

const ForwardReffedTable = fixedForwardRef(Table);

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

<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 در تایپ اسکریپت دارای محدودیت می‌باشد، به عنوان راه حل پیشنهاد شده است که آن را به یک تابع جدید و با یک تایپ دیگر مجددا تعریف نماییم و به این صورت محدودیت استفاده از آن را برطرف کنیم.