Generic Type های آماده و تفاوت آن‌ها با Union Type ها

Ready Generic Types and Their Differences with Union Types

13 مرداد 1399
Generic type های آماده و تفاوت آن ها با Union types

در این قسمت می خواهم برخی از تایپ های آماده شده را به شما معرفی کنم که درون خود تایپ اسکریپت وجود داشته و از نوع generic type محسوب می شوند. ما به این نوع از تایپ ها utility type می گوییم. شما می توانید از لینک زیر لیستی از تمام utility type ها را مشاهده نمایید:

https://www.typescriptlang.org/docs/handbook/utility-types.html

ما فقط چند مورد از آن ها را بررسی می کنیم. برای شروع فرض کنید یک interface به شکل زیر داشته باشیم:

interface CourseGoal {
  title: string;
  description: string;
  completeUntil: Date;
}

این interface یک title (عنوان) و یک Description (توضیحات) دارد که هر دو رشته ای هستند و نهایتا زمان پایان دوره را داریم که از نوع Date می باشد. حالا فرض کنید تابعی داشته باشیم که بخواهد یک شیء را بر اساس این interface تولید کند اما نخواهیم این کار را به صورت یکجا انجام بدهیم. مثلا کد زیر این کار را در یک خط و یکجا انجام می دهد:

function createCourseGoal(
  title: string,
  description: string,
  date: Date
): CourseGoal {
  return {title: title, description: description, completeUntil: date};
}

من برای خواناتر شدن کد قسمت پارامتر های آن را در خط های جداگانه گذاشته ام و غیر از آن تابع بالا چیز خاصی ندارد. به جای اینکه به صورت بالا کار کنیم من می خواهم این کار را قدم به قدم و در چند دستور جداگانه انجام دهم:

function createCourseGoal(
  title: string,
  description: string,
  date: Date
): CourseGoal {
  let courseGoal = {};
  courseGoal.title = title;
  courseGoal.description = description;
  courseGoal.completeUntil = date;
  return courseGoal as CourseGoal;
}

منطق کد بالا درست است اما طبیعتا خطا دریافت می کنیم چرا که شیء courseGoal هیچ تایپ مشخصی ندارد و تایپ اسکریپت نیز از مقداردهی بدون مشخص کردن تایپ خوشش نمی آید. مثلا ما title را به courseGoal اضافه کرده ایم اما اصلا معلوم نیست courseGoal چه نوع تایپی است و ممکن است مقدار غلط درون title قرار بگیرد، همچنین اصلا مشخص نیست courseGoal دارای خصوصیت title باشد یا خیر و الی آخر.

برای حل این مشکل می توان گفت:

function createCourseGoal(
  title: string,
  description: string,
  date: Date
): CourseGoal {
  let courseGoal: CourseGoal = {};
  courseGoal.title = title;
  courseGoal.description = description;
  courseGoal.completeUntil = date;
  return courseGoal as CourseGoal;
}

یعنی شیء courseGoal را روی interface خودمان تنظیم کرده ایم. با این کار خطاهای مربوط به اضافه کردن title و description و completeUntil از بین می روند اما خطای جدیدی می گیریم. تایپ اسکریپت می گوید اگر courseGoal از نوع اینترفیس CourseGoal می باشد، چرا شیء خالی است؟ باید title و description و completeUntil داشته باشد اما ما یک شیء خالی را به آن منسوب کرده ایم!

اینجاست که یکی از utility type ها به نام Partial (به معنی «قسمتی از») به کار ما می آید:

function createCourseGoal(
  title: string,
  description: string,
  date: Date
): CourseGoal {
  let courseGoal: Partial<CourseGoal> = {};
  courseGoal.title = title;
  courseGoal.description = description;
  courseGoal.completeUntil = date;
  return courseGoal as CourseGoal;
}

در اینجا با استفاده از Generic type ها و Partial کاری کرده ایم که تمام خصوصیات CourseGoal اختیاری شود و خطا از بین برود. همچنین برای برگرداندن courseGoal از type casting استفاده کرده ایم و as CourseGoal را اضافه کرده ام. اگر این کار را نکنید با خطا روبرو می شوید چرا که متغیر courseGoal در نهایت از تایپ partial خواهد بود و برای تصحیح آن حتما باید دوباره آن را به اینترفیس CourseGoal تعریف کنیم. بنابراین اگر بخواهیم خصوصیات یک شیء به صورت موقت اختیاری شود می توانیم از Partial استفاده کنیم. مثال های بیشتر از Partial را می توانید در سایت تایپ اسکریپت ببینید.

یکی دیگر از utility type های کاربردی ما readonly است. فرض کنید آرایه زیر را داشته باشیم:

const names = ['Max', 'Anna'];

طبیعتا این یک آرایه عادی است و ما می توانیم مانند آرایه های دیگر چیزی به آن اضافه یا از آن کم کنیم:

const names = ['Max', 'Anna'];
names.push('Manu');
names.pop();

اما فرض کنید که بخواهیم این آرایه را قفل کنیم تا نتوانیم بعدا آن را مانند کد بالا تغییر دهیم. برای انجام این کار می توانیم از readonly استفاده کنیم:

const names: Readonly<string[]> = ['Max', 'Anna'];

حالا اگر از دستوراتی مثل push و pop استفاده کنید با خطا مواجه می شوید. تمام این utility type ها فقط در تایپ اسکریپت وجود دارند بنابراین بعد از کامپایل شدن به طور کامل از کدهای جاوا اسکریپت حذف می شوند اما هنگام کدنویسی در تایپ اسکریپت به ما کمک خواهند کرد.

تفاوت بین Generic types و union types

آیا کلاسی که برای ذخیره داده ها نوشتیم را یادتان است؟

class DataStorage<T extends string | number | boolean> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }

  removeItem(item: T) {
    if (this.data.indexOf(item) === -1) {
      return;
    }
    this.data.splice(this.data.indexOf(item), 1); // -1
  }

  getItems() {
    return [...this.data];
  }
}

این کلاس از generic type ها استفاده می کند اما اگر از union type ها برای آن استفاده کنیم چه می شود؟

class DataStorage {
  private data: (string | number | boolean)[] = [];

  addItem(item: string | number | boolean) {
    this.data.push(item);
  }

  removeItem(item: string | number | boolean) {
    if (this.data.indexOf(item) === -1) {
      return;
    }
    this.data.splice(this.data.indexOf(item), 1); // -1
  }

  getItems() {
    return [...this.data];
  }
}

کد بالا را با union types نوشته ایم و شاید در نگاه اول اینطور به نظر برسد که دقیقا همان کار generic type ها را انجام می دهد اما اصلا اینطور نیست. مثلا در کد بالا برای data گفته ایم که یک آرایه رشته ای، عددی یا دارای بولین را قبول می کنیم. سپس برای متد addItem گفته ایم هر داده ای که رشته ای، عددی و بولین باشد را قبول می کنیم. مشکل اینجاست که برای Data نگفته ایم آرایه ما باید از نوع رشته ای یا عددی یا بولین باشد، بلکه گفته ایم آرایه ما می تواند از نوع رشته و عددی و بولین باشد! به زبان ساده تر در کد بالا گفته ایم که آرایه ما می تواند اول یک بولین بگیرد، سپس یک عدد باشد، سپس یک بولین، سپس یک رشته و الی آخر، در صورتی که generic type ها می گفتند تمام اعضای آرایه ما یا باید رشته ای باشند یا یا تمام اعضای آرایه عددی باشند یا تمام اعضای آرایه بولین باشند. بنابراین اگر بخواهیم این مسئله را در کد بالا نیز پیاده سازی کنیم می گوییم:

  private data: string[] | number[] | boolean[] = [];

با این کار به خطا می خوریم:

  addItem(item: string | number | boolean) {
    this.data.push(item);
  }

تابع بالا می گوید من رشته یا عدد یا بولین قبول می کنم اما این مسئله باید به کد قبل از آن بستگی داشته باشد. ما نمی دانیم که data چه نوعی می گیرد و آخر چه نوعی از آرایه می شود. مثلا اگر data یک آرایه رشته ای شود یعنی تمام اعضای آن باید رشته باشند و در این حالت ما می توانیم در AddItem یک عدد دریافت کنیم و قطعا چنین چیزی مجاز نیست!

تفاوت اصلی بین generic type ها و union type ها همین است.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جامع تایپ اسکریپت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو