آموزش Union Types و Discriminated Union در TypeScript با مثال عملی

انتشار در ۲ خرداد ۱۴۰۵
برنامه نویسی

📌 محاسبه مساحت اشکال هندسی با استفاده از Type Narrowing در تایپ‌اسکریپت

فرض کنید در یک برنامه‌ی گرافیکی، باید اشکال هندسی مختلف را مدیریت کنید.

مرحله 1 – تعریف تایپ‌ها:
یک تایپ به نام Triangle یعنی چند ضلعتعریف کنید که شامل:

  • خاصیت kind با مقدار ثابت 'triangle'
  • خاصیت base (قاعده) از نوع عدد
  • خاصیت height (ارتفاع) از نوع عدد

یک تایپ به نام Square تعریف کنید که شامل:

  • خاصیت kind با مقدار ثابت 'square'
  • خاصیت side (ضلع) از نوع عدد

مرحله 2 – تعریف Union Type:
یک تایپ به نام Shape تعریف کنید که برابر Triangle یا Square یا Circle باشد.

توجه: تایپ Circle قبلاً به این شکل تعریف شده است:

type Circle = { kind: 'circle'; radius: number; }

مرحله 3 – تابع محاسبه مساحت:
تابعی به نام getArea بنویسید که یک پارامتر از نوع Shape بگیرد و بر اساس خاصیت kind، مساحت شکل مربوطه را محاسبه و برگرداند.

مرحله 4 – تابع تشخیص نوع (چالشی):
تابعی به نام getShapeType بنویسید که یک Shape بگیرد و یک رشته برگرداند:

  • اگر kind برابر 'circle' باشد، برگرداند "This is a circle"
  • اگر 'square' باشد، برگرداند "This is square"
  • اگر 'triangle' باشد، برگرداند "This is a triangle"

مرحله 5 (اختیاری – پیشرفته):
یک تابع به نام isBigShape بنویسید که یک Shape بگیرد و اگر مساحت آن بیشتر از ۵۰ بود، true و در غیر این صورت false برگرداند.
(از تابع getArea داخل آن استفاده کنید)


✅ پاسخ

// مرحله 1 - تعریف تایپ‌ها
type Circle = {
  kind: 'circle';
  radius: number;
};

type Triangle = {
  kind: 'triangle';
  base: number;
  height: number;
};

type Square = {
  kind: 'square';
  side: number;
};

// مرحله 2 - Union Type
type Shape = Circle | Triangle | Square;

// مرحله 3 - تابع محاسبه مساحت
function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius * shape.radius;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    case 'square':
      return shape.side * shape.side;
    default:
      // این خط هرگز اجرا نمی‌شود (برای satisfaction TypeScript)
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

// مرحله 4 - تابع تشخیص نوع
function getShapeType(shape: Shape): string {
  switch (shape.kind) {
    case 'circle':
      return 'This is a circle';
    case 'triangle':
      return 'This is a triangle';
    case 'square':
      return 'This is a square';
    default:
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

// مرحله 5 (اختیاری) - تشخیص شکل بزرگ
function isBigShape(shape: Shape): boolean {
  return getArea(shape) > 50;
}

// مثال استفاده
const myCircle: Circle = { kind: 'circle', radius: 5 };
const myTriangle: Triangle = { kind: 'triangle', base: 10, height: 5 };
const mySquare: Square = { kind: 'square', side: 7 };

console.log(getArea(myCircle));     // 78.5398...
console.log(getArea(myTriangle));   // 25
console.log(getArea(mySquare));     // 49

console.log(getShapeType(myCircle));    // "This is a circle"
console.log(getShapeType(myTriangle));  // "This is a triangle"

console.log(isBigShape(myCircle));   // true (78 > 50)
console.log(isBigShape(myTriangle)); // false (25 <= 50)

export { getArea, getShapeType, isBigShape, Shape };

📚 توضیح نکات جدید در این مساله

نکتهتوضیح
never typeدر default کیس‌ها از never استفاده کردیم تا اگر در آینده عضو جدیدی به Shape اضافه شد، TypeScript به ما خطا بدهد و فراموش نکنیم که آن را مدیریت کنیم.
حالت triangleمساحت مثلث = (قاعده × ارتفاع) / 2
حالت squareمساحت مربع = ضلع × ضلع
استفاده مجدد از توابعدر isBigShape از getArea استفاده کردیم تا کد خشک (DRY) بماند.