در این مقاله قصد داریم تا همکاری قدرتمند بین 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 ما را قدرتمند میکند، فراهم کنیم.