در این مقاله قصد داریم تا همکاری قدرتمند بین Genericهای تایپ اسکریپت و کامپوننتهای فانکشنال React را باهم بررسی کنیم. یادگیری را با درک مفهوم Generic در تایپ اسکریپت و اینکه چگونه آنها میتوانند در زمینه توسعه React مفید باشند، شروع میکنیم. سپس با کمک مثالهای عملی نحوه تعریف کامپوننتهای فانکشنال generic در React را با استفاده از تایپ اسکریپت بررسی میکنیم. همچنین در مورد موارد استفاده مختلف هم صحبت خواهیم کرد که در آن generic propsها بسیار پررنگ هستند و ما را قادر میسازند تا برنامههای React همه کاره و مقیاسپذیر بسازیم.
Genericها این امکان را به ما می دهند تا کامپوننتهای انعطافپذیری را تعریف کنیم که میتوانند با ساختارهای دادهای مختلف سازگار شوند و type safety را در سراسر پایگاه کد ما اعمال کنند.
همینطور با استفاده از تایپ اسکریپت و ایجاد Genericها در کامپوننتهای فانکشنال React، میتوانیم المنتهای UI با قابلیت استفاده مجدد و سازگاری بالا ایجاد کنیم که میتوانند به طور یکپارچه نیازهای دادههای مختلف را مدیریت کنند.
برای بررسی کامپوننتهای فانکشنال با Genericها ابتدا باید اصول توابع generic از جمله تعریف و اجرای آنها را در تایپ اسکریپت درک کنیم. در این بخش، مروری مختصر از اینکه generic چیست و چگونه باید آنها را در تایپ اسکریپت پیادهسازی کنیم، خواهیم داشت.
برای تعریف یک تابع generic در تایپ اسکریپت، یک پارامتر تایپ generic را که با <T>نشان داده میشود، در ابتدای signature تابع قرار میدهیم. این <T> نشان دهنده تایپی است که هنگام استفاده از تابع ارسال میشود و به عنوان یک placeholder برای یک تایپ خاص عمل میکند که مشابه نحوه عملکرد پارامترها در توابع regular، در طول زمان اجرا تعیین میشود.
به عنوان مثال:
function func<T>(value: T):T[] {
return [value];
}
func('Hello John');
در این مثال، پارامتر تایپ generic Tبرای نشان دادن اینکه پارامتر value و تایپ بازگشتی از یک نوع هستند، مورد استفاده قرار میگیرد.
وقتی تابع funcبا آرگومان Hello Johnفراخوانی میشود، تایپ استنباط شده برای Tتبدیل به stringمیگردد. در نتیجه، تایپ بازگشتی تابع string[]است و نشان میدهد که یک آرایه حاوی مقدار رشتهای ارائهشده، برگردانده خواهد شد.
برای تعریف یک تابع generic با arrow functionها کمی متفاوت از سینتکس تابعی که قبلا داشتیم عمل میکنیم:
export const func = <T>(value: T):T[] => {
return [value];
}
func('Hello John');
با توجه به مثالی که داریم، میبینیم که هنگام استفاده از arrow functionها برای تعریف توابع generic در تایپ اسکریپت، از دستور <T>قبل از لیست پارامترها برای نشان دادن یک پارامتر تایپ generic استفاده میکنیم.
در ادامه، توجه به این نکته مهم است که پارامتر تایپ generic Tاستفاده شده در مثالهای قبلی صرفاً یک نام توصیفی است و میتواند با هر شناسه معتبر دیگری جایگزین شود. مثلا:
export const func = <K>(value: K):K[] => {
return [value];
}
func('Hello John');
پارامتر تایپ generic Tبا Kجایگزین شده است و این موضوع نشان میدهد که انتخاب نام برای تایپ generic، انعطافپذیر است و میتواند برای مطابقت با context یا ترجیح توسعهدهنده، حالت شخصیسازی شده داشته باشد.
تابع funcبه صورت generic باقی میماند و قادر به پذیرش هر تایپ تعیین شده در طول استفاده و return کردن آرایهای از همان تایپ است.
در بخش بعدی، توضیحی قدم به قدم درباره نحوه ایجاد و استفاده از کامپوننتهای generic ارائه خواهیم داد. این کار به ما کمک میکند تا از قدرت genericها در توسعه React استفاده کنیم.
درست مانند توابع generic، میتوانیم با طراحی کامپوننتهای generic و ادغام با genericهای تایپ اسکریپت، قابلیت استفاده مجدد کامپوننتها را به سطح بعدی ارتقا دهیم.
تعریف generic props که یک کامپوننت generic را میپذیرد، اولین قدم در طراحی کامپوننت است. ما میتوانیم در react یک props interface پارامتری ایجاد کنیم که از انواع دادههای مختلف با استفاده از genericهای تایپ اسکریپت پشتیبانی میکند.
interface MyComponentProps<T> {
data: T;
// Add additional props specific to your component
}
هنگامی که generic props interface را تعریف کردیم، میتوانیم خود کامپوننت generic را پیادهسازی کنیم. یعنی قصد داریم استفاده از generic propsهایی که تعریف کردیم و ادغام آنها در ساختار کامپوننت را بررسی کنیم.
این موضوع مشابه توضیحات تابع تایپ اسکریپت در بخش قبل، پیادهسازی مشابهی دارد.
function MyComponent<T>({ data }: MyComponentProps<T>) {
return (
<div>
{/* JSX content */}
</div>
);
}
همچنین میتوانیم کامپوننتهای generic را با استفاده از سینتکس arrow functionها تعریف کنیم. اما زمانی که در حال نوشتن کد خود در فایل JSX.Elementهستیم، این موضوع متفاوت میباشد.
برای جلوگیری از ایجاد ابهام بین پارامترهای تایپ generic و یک کامپوننت jsx، یک , به انتهای پارامتر تایپ اضافه میکنیم تا آن را از یک JSX.Elementمتمایز کنیم. مثلا:
interface MyComponentProps<T> {
data: T;
}
// notice the trailing comma after <T
const MyComponent = <T,>({ data }: MyComponentProps<T>) => {
return (
<div>{JSX content}</div>;
)
};
export default MyComponent;
فرض کنید میخواهیم یک داشبورد data visualization ایجاد کنیم که قادر به ارائه دادهها از منابع مختلف است که هر کدام ساختار و ویژگیهای منحصربهفرد خود را دارند. اما میخواهیم یک کامپوننت با قابلیت استفاده مجدد بسازیم که بتواند خلاصهای از هر data objectای را با تمرکز بر یک ویژگی خاص ایجاد کند.
با extend شدن پارامتر تایپ به یک objectو استفاده از عملگر keyofدر interfaceها، میتوانیم کامپوننتی بسازیم که هم انعطافپذیری و هم type safety را ارائه میدهد. این رویکرد از قابلیتهای سیستم تایپ قوی تایپ اسکریپت بهره میبرد و این امکان را به ما میدهد که با انواع آبجکتها به طور یکپارچه کار کنیم.
برای رسیدن به این هدف، یک کامپوننت generic به نام Summary<T>تعریف میکنیم که یک پارامتر تایپ Tرا که یک آبجکت را extend میکند، میپذیرد. همچنین از عملگر keyofبرای مشخص کردن ویژگی آبجکتی که میخواهیم در خلاصه، نمایش داده شود استفاده میکنیم:
interface SummaryProps<T extends object, K extends keyof T> {
data: T;
property: K;
}
export function Summary<T extends object, K extends keyof T>({
data,
property,
}: SummaryProps<T, K>) {
const value = data[property] as string;
return (
<div>
{(typeof property === "string") ? (
<p>
{property}: {value}{" "}
</p>
) : (
""
)}
</div>
);
}
در کامپوننت Summary، یک پارامتر تایپ generic T را تعریف کردیم که objectرا extend کرده و اطمینان حاصل میکند که دادههای ارسال شده به کامپوننت، دارای تایپ objectاست.
پارامتر دوم تایپ Kاز علامت keyof T استفاده میکند، که نشان میدهد باید یک key از نوع آبجکتTباشد. به این ترتیب، میتوانیم بر اساس value ویژگی ارائه شده، به یک ویژگی خاص از data object دسترسی پیدا کنیم.
در کامپوننت، value را از objectداده با استفاده از propertyبه عنوان key استخراج کردیم. برای اطمینان از اینکه value به عنوان stringدر نظر گرفته میشود، از کلمه کلیدی asبرای اجرای یک نوع تاکید برای جلوگیری از خطاهای تخصیص استفاده کردیم:data[property] as string. این نوع تاکید به تایپ اسکریپت میگوید که value را به عنوان یک stringدر نظر بگیرد.
سپس کامپوننت Summary یک المنت <div>را برمیگرداند که حاوی یک رندر شرطی از المنت <p>است. پس از آن کد با استفاده از typeof property === "string"بررسی میکند که آیا propertyاز نوع stringاست یا خیر. به این ترتیب از اشتباهات مربوط به انتساب نیز جلوگیری میکند.
اگر نتیجه ارزیابی trueباشد، کد المنت <p>را نشان داده و propertyو valueرا نمایش میدهد. در غیر این صورت، یک stringخالی return میشود.
اکنون مثالی را در نظر میگیریم که در آن دو data source متفاوت داریم: Userو Product. هر data source، ویژگیهای خاص مربوط به خود را دارد. ما قصد داریم تا خلاصهای را بر اساس یک ویژگی خاص نمایش دهیم.
میتوانیم با تعریف interface Userبرای نمایش دادههای کاربر و مشخص کردن ویژگیهایی مانند id، nameو emailشروع کنیم. به طور مشابه، interface Productرا نیز برای نمایش دادههای محصول، از جمله ویژگیهایی مانند id، nameو priceایجاد میکنیم:
interface User {
id: number;
name: string;
email: string;
}
interface Product {
id: number;
name: string;
price: number;
}
سپس، میتوانیم نمونههایی از این data objectها ایجاد کنیم. به عنوان مثال، userDataمیتواند نشاندهنده یک userباشد و productDataمیتواند یک productرا با ویژگیهای مربوطه به نمایش بگذارد:
const userData: User = {
id: 1,
name: "John Doe",
email: "johndoe@example.com"
};
const productData: Product = {
id: 1,
name: "Smartphone",
price: 999
};
برای استفاده از کامپوننت Summary، data sourceهای متفاوتی را برای آن فراهم کرده و ویژگی را که میخواهیم در خلاصه نمایش دهیم را مشخص میکنیم.
به عنوان مثال، میتوانیم از <Summary<User, "name">>برای ارائه خلاصهای از نام کاربر و <Summary<Product, "price">>برای نمایش قیمت محصول استفاده کنیم:
<Summary<User, "name"> data={userData} property="name" />
<Summary<Product, "price"> data={productData} property="price" />
کد کامل به صورت زیر است:
interface User {
id: number;
name: string;
email: string;
}
interface Product {
id: number;
name: string;
price: number;
}
const userData: User = {
id: 1,
name: "John Doe",
email: "johndoe@example.com"
};
const productData: Product = {
id: 1,
name: "Smartphone",
price: 999
};
// Usage of Summary component with different data sources and properties
<Summary<User, "name"> data={userData} property="name" />
<Summary<Product, "price"> data={productData} property="price" />
مکانیزم قدرتمند استنتاج تایپ تایپ اسکریپت در این سناریو وارد عمل میشود. وقتی که ما dataو propertyرا به کامپوننت Summaryارائه میکنیم، تایپ اسکریپت تضمین میکند که ویژگی مشخصشده برای data source مربوطه معتبر است.
اگر به اشتباه سعی کنیم از یک ویژگی نامعتبر استفاده کنیم، مانند ارائه emailبه عنوان ویژگی برای دادههای مربوط به Product، کامپایلر تایپ اسکریپت یک خطای تایپ ایجاد میکند. این کار type safety را تضمین کرده و به جلوگیری از مشکلات احتمالی زمان اجرا کمک میکند.
با استفاده از بررسی تایپ استاتیک تایپ اسکریپت، هنگام کار با کامپوننتهای generic مانند Summaryاز مزایای type safety و اعتبار سنجی بهرهمند میشویم. همچنین به شناسایی خطاها در مراحل اولیه توسعه کمک کرده و اطمینان حاصل میکند که dataو propertyارائه شده صحیح هستند.
در این مقاله مفهوم genericهای تایپ اسکریپت بررسی کردیم و با و نحوه استفاده از آنها برای ایجاد کامپوننتهای قدرتمند با قابلیت استفاده مجدد در React آشنا شدیم. genericها به ما این اجازه را میدهد تا کامپوننتهایی را تعریف کنیم که به اندازه کافی انعطافپذیر باشند تا با انواع دادههای مختلف کار کنند و در عین حال type safety را حفظ نمایند.
همینطور اصول genericها، از جمله اجرای توابع generic را یاد گرفتیم. سپس به موضوع کامپوننتهای generic در React پرداختیم و نحوه تعریف و پیادهسازی آنها را با استفاده از arrow functionها و named function بررسی کردیم. در نهایت با استفاده از genericها، میتوانیم امکانات جدیدی را در ایجاد کامپوننتها با قابلیت استفاده مجدد و ایمن که برنامههای React ما را قدرتمند میکند، فراهم کنیم.