تایپ‌های Intersection و Type Guard ها

Intersection Types and Type Guards

12 مرداد 1399
تایپ های Intersection و type guard ها

تایپ های Intersection

حالا که با مفاهیم پایه و ساده تایپ اسکریپت آشنا شدیم، زمان بررسی مفاهیم پیچیده تر شده است. یکی از تایپ های جالب در تایپ اسکریپت Intersection type است که به ما اجازه می دهد انواع تایپ ها را با هم ادغام کنیم. فرض کنید یک تایپ به شکل زیر داشته باشیم:

type Admin = {
  name: string;
  privileges: string[];
};

توجه کنید که این یک شیء نیست بلکه یک تایپ است که ساختار شیء های آینده را مشخص می کند (البته در صورتی که بر اساس این تایپ نوشته شوند). تایپ Admin دو خصوصیت دارد که یکی نام و دیگری اختیارات و دسترسی ها (privilages) می باشد. حالا یک تایپ دیگر نیز برای کارمندان می نویسیم:

type Employee = {
  name: string;
  startDate: Date;
};

این تایپ می گوید کارمندان ما باید یک خصوصیت نام و یک خصوصیت startDate (تاریخ شروع به کار در شرکت) داشته باشند. حالا فرض ما این است که می خواهیم این دو تایپ را در هم ادغام کنیم، یعنی کارمندی در شرکت ما که ادمین هم باشد. بله می توانستیم این کار را به صورت دستی نیز انجام بدهیم و مثل موارد بالا یک تایپ کامل را از ابتدا برای آن بنویسیم اما برای جلوگیری از زیاده نویسی و تکرار می توانیم به راحتی دو تایپ قبلی را در هم ادغام کنیم:

type ElevatedEmployee = Admin & Employee;

علامت & (معمولا شیفت و عدد 7) نشان می دهد که دو تایپ قبلی باید با هم ادغام شده و در تایپ جدیدی به نام ElevatedEmployee قرار بگیرند. حالا اگر بخواهیم شیء جدیدی ب اساس elevatedEmployee بسازیم باید هر سه خصوصیت نام، تاریخ شروع به کار و دسترسی ها را برایش تعریف کنم. به طور مثال:

const e1: ElevatedEmployee = {
  name: 'Max',
  privileges: ['create-server'],
  startDate: new Date()
};

اگر توجه کرده باشید این مبحث به وراثت در interface ها شباهت بسیار زیادی دارد. شما می توانید از هر کدام از این راه ها که خواستید استفاده کنید. همچنین من اینجا از اشیاء جاوا اسکریپتی مثال زدم اما شما می توانید از Intersection ها برای هر نوعی از تایپ ها استفاده کنید. به طور مثال من با استفاده از تایپ های union دو تایپ زیر را تعریف کرده ام:

type Combinable = string | number;
type Numeric = number | boolean;

حالا می توانیم این دو مقدار را با هم ادغام کنیم:

type Combinable = string | number;
type Numeric = number | boolean;

type Universal = Combinable & Numeric;

حالا Universal می گوید تایپ ما باید یا رشته یا عدد یا Boolean باشد (ادغام موارد قبلی).

Type Guard چیست؟

یکی از خصوصیاتی که بیشتر از Intersection type ها از آن استفاده می شود، type guard ها هستند. من از مثال قبلی استفاده می کنم تا کارکرد type guard ها را برایتان توضیح دهم. به کد زیر نگاه کنید:

type Combinable = string | number;

ما قبلا یاد گرفته ایم که این تایپ یک union type است که می گوید داده اصلی یا باید رشته ای و یا باید عددی باشد اما در بسیاری از مواقع باید بدانیم در نهایت دقیقا داده ای را دریافت می کنیم. مثلا اگر تابع add خودمان را داشته باشیم تا دو عدد را جمع بزنیم باید به شکل زیر عمل کنیم:

function add(a: Combinable, b: Combinable) {
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
  return a + b;
}

تابع ما دو پارامتر را می گیرد که یا رشته است و یا عدد ( تایپ Combinable) به همین دلیل باید مشخص کنیم که بالاخره کدام تایپ را گرفته ایم چرا که عملیات جمع زدن ریاضی با جمع زدن رشته ها متفاوت است. به همین دلیل چک کرده ایم که اگر یکی از این پارامترها رشته ای بود باید هر دو به رشته تبدیل شوند و جمع زده شوند و اگر از نوع عددی بودند آن ها را به صورت ریاضی جمع کنیم.

به همین شرط if می گوییم «نگهبان تایپ» یا type guard. ما به وسیله type guard ها اجرای بدون خطای کدهایمان را تضمین می کنیم. البته شرط if بالا فقط یک نوع type guard بود که از دستور typeof استفاده می کند و انواع مختلف دیگری از type guard ها را نیز داریم. به طور مثال تا الان تایپی به نام ElevatedEmployee داشتیم که هر دو تایپ Admin و Employee را ادغام می‌کرد. حالا می خواهیم تایپی داشته باشیم که فقط یا Employee باشد یا Admin (نمی تواند هر دو باشد):

type UnknownEmployee = Employee | Admin;

حالا تابعی می نویسم که اطلاعات کارمندان ما را چاپ کند:

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log('Name: ' + emp.name);
  console.log('Privileges: ' + emp.privileges);
  console.log('Start Date: ' + emp.startDate);
}

پارامتر ورودی ما از نوع UnknownEmployee است که یعنی یا Admin است یا کارمند عادی (Employee). کد بالا با خطا مواجه می شود چرا که تایپ اسکریپت می گوید معلوم نیست emp ادمین باشد یا کارمند عادی. اگر ادمین باشد privilages دارد اما اگر ادمین نباشد log کردن privilages برای آن باعث خطا در برنامه می شود چرا که تعریف نشده است. اینجاست که باید از type guard ها استفاده کنیم تا تایپ دقیق ورودی را مشخص کنیم. از آنجایی که می خواهیم وجود یا عدم وجود یک خصوصیت در یک شیء را بررسی کنیم typeof چیزی را به ما برنمی گرداند بنابراین نمی توانیم از آن استفاده کنیم. راه حل استفاده از کلیدواژه in است:

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log('Name: ' + emp.name);
  if ('privileges' in emp) {
    console.log('Privileges: ' + emp.privileges);
  }
  if ('startDate' in emp) {
    console.log('Start Date: ' + emp.startDate);
  }
}

کلیدواژه in (به معنای «درون») می گوید آیا Privilages درون emp.privilages وجود دارد یا خیر. بدین ترتیب می توانیم از وجود آن مطمئن شده و هر کدی را که خواستیم به شکل زیر اجرا کنیم:

printEmployeeInformation({ name: 'Manu', startDate: new Date() });

نوع دیگری از type guard ها نیز وجود دارد که در کلاس ها استفاده می شود و آن استفاده از کلیدواژه instanceof است. فرض کنید دو کلاس زیر را داشته باشیم:

class Car {
  drive() {
    console.log('Driving...');
  }
}

class Truck {
  drive() {
    console.log('Driving a truck...');
  }

  loadCargo(amount: number) {
    console.log('Loading cargo ...' + amount);
  }
}

توجه کنید که هر دو کلاس یک متد با نام یکسان به نام drive دارند. حالا یک union type به شکل زیر داریم:

type Vehicle = Car | Truck;

const v1 = new Car();
const v2 = new Truck();

تایپ Vehicle (به معنی «وسیله نقلیه») می گوید که داده یا باید از نوع Car باشد یا از نوع Truck. سپس از هر دو کلاس یک نمونه ساخته ایم. حالا اگر بگوییم:

function useVehicle(vehicle: Vehicle) {
  vehicle.drive();
}

این تابع به راحتی اجرا می شود چرا که vehicle (پارامتر ورودی) چه از نوع کلاس Car باشد چه از کلاس Truck، متد drive را خواهد داشت و مشکلی نداریم اما loadCargo فقط در کلاس Truck است بنابراین کد زیر خطا خواهد بود:

function useVehicle(vehicle: Vehicle) {
  vehicle.drive();
  vehicle.loadCargo(1000);
}

به دلیل اینکه اگر پارامتر ورودی از نوع Car باشد دیگر loadCargo را ندارد و کد ما به خطا برخورد کرده و متوقف خواهد شد. ما در اینجا می توانیم از type guard جدیدی به شکل زیر استفاده کنیم:

function useVehicle(vehicle: Vehicle) {
  vehicle.drive();
  if (vehicle instanceof Truck) {
    vehicle.loadCargo(1000);
  }
}

Instanceof می گوید آیا vehicle (پارامتر ورودی) یک نمونه (instance) از (of) کلاس Truck است یا خیر. البته شما می توانید کد بالا را با استفاده از in نیز بنویسید:

function useVehicle(vehicle: Vehicle) {
  vehicle.drive();
  if ('loadCargo' in vehicle) {
    vehicle.loadCargo(1000);
  }
}

در هر دو حالت بالا می توانیم از کدها استفاده کنیم:

useVehicle(v1);
useVehicle(v2);

هر دوی این کدها بدون خطا اجرا می شوند.

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

دیدگاه‌های شما (1 دیدگاه)

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

Reza
18 اسفند 1399
> حالا Universal می گوید تایپ ما باید یا رشته یا عدد یا Boolean باشد (ادغام موارد قبلی). جمله‌ی فوق، دقیق نیست و نیاز است اصلاح شود. در واقع چون از & استفاده شده است، اشتراکات دو union type را محاسبه میکند (در اینجا بین این دو تایپ، number مشترک است) و تایپ Universal برابر number در نظر گرفته میشود.

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

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