روشی که forwardRef در تایپ اسکریپت پیادهسازی میشود دارای محدودیتهایی میباشد که بزرگترین آن این است که استنتاج تایپ را در کامپوننتهای Generic غیرفعال میکند. در این مقاله قصد داریم تا استفاده از forwardRef در کامپوننتهای 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
// 1. Data is a string here...
data={["a", "b"]}
// 2. So ends up inferring as a string in renderRow.
renderRow={(row) => {
(parameter) row: string
return <tr>{row}</tr>;
}}
/>;
<Table
// 3. Data is a number here...
data={[1, 2]}
// 4. So ends up inferring as a number in renderRow.
renderRow={(row) => {
(parameter) row: number
return <tr>{row}</tr>;
}}
/>;
این موضوع واقعا مفید است، زیرا به این معنی میباشد که ما میتوانیم بدون نوشتن هیچگونه annotation اضافی، استنتاج تایپ را در تابع renderRow انجام دهیم.
زمانی که ما سعی میکنیم تا یک 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
// 1. Data is a string here...
data={["a", "b"]}
// 2. But ends up being inferred as unknown.
renderRow={(row) => {
(parameter) row: unknown
return <tr />;
}}
/>;
<ForwardReffedTable
// 3. Data is a number here...
data={[1, 2]}
// 4. 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 در تایپ اسکریپت دارای محدودیت میباشد، به عنوان راه حل پیشنهاد شده است که آن را به یک تابع جدید و با یک تایپ دیگر مجددا تعریف نماییم و به این صورت محدودیت استفاده از آن را برطرف کنیم.