آشنایی با ژنریک در تایپ اسکریپت — به زبان ساده

۲۲۷ بازدید
آخرین به‌روزرسانی: ۱۵ مهر ۱۴۰۲
زمان مطالعه: ۶ دقیقه
آشنایی با ژنریک در تایپ اسکریپت — به زبان ساده

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

اگر مدتی است که شروع به استفاده از تایپ اسکریپت کرده‌اید، ممکن است متوجه شده باشید که «ژنریک‌ها» (Generics)، به خصوص اگر تازه‌کار باشید، موجب سردرگمی هستند. با این حال، پس از این که آن‌ها را بهتر شناختید، متوجه خواهید شد که درک آن‌ها دشواری خاصی ندارد.

ژنریک‌ها

پیاده‌سازی و استفاده از ژنریک‌ها در تایپ اسکریپت، هم کاری جذاب و هم چالش‌برانگیز محسوب می‌شود. با این که ممکن است در زمان استفاده از ژنریک‌ها فکر کنید که پیچیده هستند، اما اگر به صورت صحیحی در کد پیاده‌سازی شده باشند، موجب افزایش «امنیت نوع» (Type Safety) در برنامه می‌شوند و کارهایی که به طور روزمره انجام می‌شوند را تسهیل می‌کنند، به طوری که امنیت کد تضمین می‌شود.

ژنریک چیست؟

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

زمانی که بتوان از چیزی استفاده مجدد کرد، به این معنی است که می‌توان از آن برای بیش از یک منظور یا در موقعیت‌های چندگانه بهره گرفت.

چرا باید از ژنریک استفاده کنیم؟

در صورتی که از ژنریک‌ها استفاده نکنید، می‌توانند کاملاً بی‌مصرف باشند، اما زمانی که از آن‌ها بهره بگیرید نه تنها «امنیت نوع» را تضمین می‌کنند، ‌بلکه امکان تصمیم‌گیری بهتر را فراهم می‌سازند و ایده‌های بیشتری در موارد مختلف برای نمونه در مورد شیوه استفاده از تابع‌های نوع ارائه می‌کنند.

چگونه از ژنریک‌ها استفاده کنیم؟

در این بخش به این سؤال پاسخ می‌دهیم که ژنریک‌ها چه کمکی به ما می‌کنند و چه ارتباطی با قابلیت استفاده مجدد دارند. فرض کنید در حال ساخت تابعی هستید که وقتی فراخوانی می‌شود، ‌‌typeof را روی آرگومان فرامی‌خواند و آرگومان را بازگشت می‌دهد. البته این کارکرد کاملاً بیهوده است، اما صرفاً به عنوان مثال مطرح شده است.

در ادامه این کد را در زمانی که در تایپ اسکریپت نوشته می‌شود، ملاحظه می‌کنید:

1function logAndReturnIt(
2  valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction: string,
3): string {
4  console.log(typeof valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction)
5  
6  return valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction
7}

علی رغم این که این یک تابع بی‌مصرف است، می‌توانیم ببینیم که یک نوع string روی آرگومان قرار می‌دهد. اینک می‌توانیم در زمان بهره‌گیری از تایپ اسکریپت، ‌از هر نوع رشته با این تابع استفاده کنیم:

1const greeting = 'Good morning'
2logAndReturnIt(greeting) // logs 'Good morning'
3logAndReturnIt('cat') // logs 'cat'

اگر لازم باشد که نوع داده دیگری را نیز پشتیبانی کنیم، باید کد را به صورت زیر تغییر دهیم:

1function logAndReturnIt(
2  valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction: null,
3): null {
4  console.log(typeof valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction)
5  
6  return valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction
7}
8
9logAndReturnIt(null) // valid
10logAndReturnIt(5) // invalid
11// TypeScript error: Argument of type '5' is not assignable to parameter of type 'null'.ts(2345)

در عمل، ‌زمانی که اپلیکیشن‌ها را توسعه می‌دهیم و از این تابع بهره می‌گیریم، تنها کاربرد آن در مورد مقادیر null است. این به آن معنی است که تابع قابلیت استفاده مجدد ندارد. بنابراین در ادامه کاری می‌کنیم که قابلیت استفاده مجدد بیابد. به این منظور چندین گزینه در اختیار داریم.

گزینه نخست این است که از نوع any استفاده کنیم. این نوعی است که همه ما در ابتدای کار با تایپ اسکریپت دوست داشتیم مورد استفاده قرار دهیم.

1function logAndReturnIt(
2  valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction: any,
3): any {
4  console.log(typeof valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction)
5  
6  return valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction
7}
8
9logAndReturnIt(null) // valid
10logAndReturnIt(5) // valid
11logAndReturnIt('abc123') // valid
12logAndReturnIt(undefined) // valid
13logAndReturnIt(false) // valid

استفاده از نوع any این امکان را به ما می‌دهد که با هر نوع داده‌ای کار کنیم که موجب می‌شود به هدف مورد نظر خود برسیم. اما مشکل این رویکرد آن است که مزیت امنیت نوع از دست می‌رود. یعنی اگر مقادیر عجیبی از قبیل وهله‌های خطا را ارسال کنیم، کامپایلر مشکلی با این قضیه نخواهد داشت، ‌چون تایپ اسکریپت مسئولیت کار را به جای شما بر عهده گرفته است.

یک روش قدرتمندتر برای نوع‌بندی قوی تابع وجود دارد که موجب می‌شود امنیت نوع آن تضمین شود و قابلیت استفاده مجدد بیشتری داشته باشد. می‌توان یک نوع ژنریک اعلان کرد و از آن به صورت زیر استفاده نمود:

1function logAndReturnIt<T>(
2  valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction: T,
3): T {
4  console.log(typeof valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction)
5  
6  return valueThatIsGoingToBeLoggedAndReturnedFromAUselessFunction
7}

ساختار کد فوق کمی عجیب و ناآشنا است، اما درک آن در عمل کاملاً آسان است. در جاوا اسکریپت می‌توان متغیرها را اعلان کرده و سپس به صورت زیر بی‌درنگ مورد استفاده قرار داد:

1let fruit = 'apple'
2
3function sayStuff(stuff) {
4  window.alert(stuff)
5}
6
7sayStuff(`i love eating ${fruit}s`) // implicit
8// or
9sayStuff < number > 500 // explicit

در مورد مثال اعلان نوع، مفهوم کار دقیقاً همان است. ما یک نوع ژنریک T را با قرار دادن آن درون <> اعلان کرده‌ایم، که درست پیش از نخستین پرانتز آمده است.

ژنریک در تایپ اسکریپت

ژنریک در تایپ اسکریپت

اینک زمانی که تابع مورد استفاده قرار گیرد، می‌بینید که روش بهتری برای استفاده مجدد از تابع فراهم شده است و همچنین linter تایپ اسکریپت حفاظت بهتری برای جلوگیری از بروز خطا یافته است.

ضمناً باید اشاره کنیم که همانند تابع‌های نرمال جاوا اسکریپت، امکان اعلان هر تعدادی از نوع که لازم باشد وجود دارد، بنابراین تنها به اعلان یک نوع منفرد محدود نیستیم:

1function combine<T, W>(arg1: W, arg2: T): T {
2  return arg2
3}

بدین ترتیب کامپایلر تایپ اسکریپت استنباط می‌کند که چه نوعی به arg2 ارسال شده است و همچنین مقدار بازگشتی باید از چه نوع باشد:

ژنریک در تایپ اسکریپت

بنابراین به جای این که لازم باشد نوع را مستقیماً و صراحتاً به صورت زیر اعلان کنیم:

1function combine(arg1: string, arg2: string): string {
2  return arg2
3}

محدود به استفاده از این تابع تنها روی انواع رشته‌ای هستیم:

استفاده مجدد از تابع‌ها در تایپ اسکریپت

برای استفاده مجدد از تابع‌ها، ‌بهره‌گیری از یک روش عمومی‌تر (ژنریک‌تر) بهتر است، زیرا می‌توان نوع ارسالی را به دست آورد و به تایپ اسکریپت اجازه داد که در ادامه بسته به کاری که برای تابع تعریف شده است این انواع را همواره بررسی کند.

بدین ترتیب ژنریک‌ها بسیار مفیدتر واقع می‌شوند. احتمالاً شنیده‌اید که افراد مختلف در مورد شیوه استفاده از تایپ اسکریپت برای جلوگیری از بروز باگ‌ها، حتی پیش از رخ دادن آن‌ها، صحبت می‌کنند. این حرف درستی است و استفاده از ژنریک‌ها یک روش برای تحقق این واقعیت است:

1function something<T, K extends keyof T>(arg1: T, arg2: K) {
2  //
3}

فرض کنید می‌خواهیم با تابع فوق کار کنیم. می‌بینیم که نوع T و نوع K اعلان شده است که دومی کلیدی برای T محسوب می‌شود. ما نوع T را به arg1 و نوع K را به arg2 انتساب داده‌ایم.

اکنون می‌دانیم که وقتی از این تابع استفاده کنیم، آرگومان نخست احتمالاً از همان نوع است که می‌تواند از سوی یک اندیس یا مشخصه مورد استفاده قرار گیرد، در حالی که آرگومان دوم ممکن است نوع رشته یا عددی داشته باشد. ما این واقعیت را از روی K extends keyof T می‌دانیم زیرا K یک مشخصه/اندیس/کلید برای T محسوب می‌شود. از این تابع می‌توان به صورت زیر استفاده کرد:

1// Completely valid -- TypeScript will not complain
2const result = something({ goodwill: 'a' }, 'goodwill')

از آنجا که goodwill یک مشخصه شیء است که به آرگومان نخست ارسال می‌شود، کاملاً معتبر است. ما آن را به یک شیء با نوع قوی مانند { goodwill: string } می‌دهیم تا با آن کار کند.

تایپ اسکریپت همواره بررسی می‌کند که از آن به صورت صحیحی استفاده کنیم:

1const result = something({ goodwill: 'a' }, 'toString')

ژنریک در تایپ اسکریپت

این قابلیت بسیار قدرتمندی است، ‌زیرا از بروز باگ‌ها پیش از آن که مجال ظهور بیابند جلوگیری می‌کند. برای این که ثابت کنیم امکان استفاده مجدد از تابع را به روش‌های مختلف در عین حفظ امنیت نوع داریم، در ادامه برخی مثال‌ها را ارائه کرده‌ایم.

1// Perfectly valid because in JavaScript you can access characters in a string by index
2// example:
3//    const x = 'coffee'
4//    const letterF = x[3]   // result: 'f'
5const result = something('hello', 50)
6
7// NOT valid because in JavaScript you can't access characters in a string by a string
8const result = something('hello', '50')

ژنریک‌ها به همراه کلاس‌ها

انواع ژنریک را می‌توان روی ‌تابع‌ها اعمال کرد، اما امکان انجام این کار روی کلاس‌ها نیز وجود دارد که ‌به خصوص برای مصرف‌کنندگان کد شما کاملاً مفید است.

در زمان استفاده از کلاس‌ها می‌توان انواع را مانند <T>، به همان روشی که در مورد تابع‌ها عمل کردیم، اعلان کرد. با این حال از آنجا که ساختار کلاس متفاوت است، کمی متفاوت به نظر می‌رسد:

1class SomeElement<T> {
2  private element: T
3  
4  constructor(element: T) {
5    this.element = element
6  }
7  
8  getElement() {
9    return this.element
10  }
11}
12
13const elem = document.createElement('button')
14const button = new SomeElement<HTMLButtonElement>(elem)

زمانی که از button پس از ساخت مقداردهی وهله استفاده می‌کنیم، تایپ اسکریپت با یادآوری مداوم این نکته که مشخصه element یک عنصر button است، موجب حفظ امنیت شما می‌شود. بنابراین صرفاً باید به خصوصیت‌ها، ‌متدها، مشخصه‌ها و موارد دیگر مرتبط با button محدود شود. به این ترتیب به پایان این مقاله می‌رسیم و امیدواریم این راهنما برای شما ارزشمند بوده باشد و اطلاعات خوبی از آن کسب کرده باشید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *