در JavaScript توابع بسیار انعطاف‌پذیر هستند؛ هر نوع داده‌ای می‌تواند وارد تابع شود و هر نوع خروجی‌ای برگردانده شود. این انعطاف، در پروژه‌های بزرگ معمولاً به خطاهای runtime ختم می‌شود (خطاهایی که تازه هنگام اجرا خودشان را نشان می‌دهند).

TypeScript با تایپ‌گذاری توابع کمک می‌کند:

  • ورودی‌ها (پارامترها) مشخص و محدود شوند
  • خروجی تابع شفاف باشد
  • خطاهای رایج قبل از اجرا (در زمان توسعه) تشخیص داده شوند
  • تجربه توسعه با autocomplete و hint بهتر شود

در این جلسه، تایپ‌گذاری توابع را از پایه تا کاربردی با مثال‌های کامل یاد می‌گیریم.


ساختار کلی تایپ‌گذاری تابع

در TypeScript می‌توانیم برای هر پارامتر و همچنین برای خروجی تابع نوع تعیین کنیم:

function functionName(param1: Type, param2: Type): ReturnType {
  // ...
}
  • param1: Type یعنی پارامتر اول چه نوعی است
  • : ReturnType یعنی تابع چه نوعی را برمی‌گرداند

۱) تایپ‌گذاری پارامترهای تابع

مثال: تابع جمع دو عدد

function add(a: number, b: number): number {
  return a + b;
}

const result = add(10, 20);
console.log(result); // 30

اگر اشتباهاً رشته ارسال کنیم، TypeScript خطا می‌دهد:

// add("10", 20); ❌ Error: Argument of type 'string' is not assignable to parameter of type 'number'

این یعنی خطای رایج JavaScript (مثل "10" + 20 که خروجی "1020" می‌شود) قبل از اجرا کنترل می‌شود.


۲) تایپ‌گذاری مقدار خروجی (Return Type)

گاهی TypeScript می‌تواند خروجی را حدس بزند (Type Inference)، اما تعیین دستی نوع خروجی در توابع مهم توصیه می‌شود.

مثال: خروجی رشته

function greet(name: string): string {
  return `Hello ${name}`;
}

اگر اشتباهاً خروجی اشتباهی برگردانیم:

function greet(name: string): string {
  // return 123; ❌ Type 'number' is not assignable to type 'string'
  return `Hello ${name}`;
}

۳) توابعی که چیزی برنمی‌گردانند: void

اگر تابع فقط کاری انجام دهد (مثل log گرفتن) و خروجی نداشته باشد، نوع خروجی void است:

function logMessage(message: string): void {
  console.log(message);
}

۴) پارامتر اختیاری (Optional Parameter)

گاهی لازم است پارامتر اختیاری باشد. برای این کار از ? استفاده می‌کنیم:

function printUser(name: string, age?: number): void {
  if (age !== undefined) {
    console.log(`${name} is ${age} years old`);
  } else {
    console.log(`${name} age is unknown`);
  }
}

printUser("Ali");
printUser("Sara", 28);

نکته مهم: پارامتر اختیاری باید بعد از پارامترهای اجباری بیاید.


۵) مقدار پیش‌فرض (Default Parameter)

وقتی می‌خواهید اگر کاربر مقداری نداد، یک مقدار پیش‌فرض استفاده شود:

function applyDiscount(price: number, discount: number = 10): number {
  return price - (price * discount) / 100;
}

console.log(applyDiscount(100));      // 90
console.log(applyDiscount(100, 25));  // 75

۶) تایپ تابع به‌عنوان مقدار (Function Type)

در TypeScript توابع می‌توانند داخل متغیرها ذخیره شوند. پس می‌توانیم برای خودِ تابع هم تایپ مشخص کنیم.

روش ۱: تعریف نوع امضای تابع

let operation: (a: number, b: number) => number;

operation = (x, y) => x + y;
console.log(operation(5, 3)); // 8

operation = (x, y) => x * y;
console.log(operation(5, 3)); // 15

در اینجا TypeScript اجازه نمی‌دهد تابعی با امضای متفاوت در operation قرار دهید.


۷) تایپ never برای توابعی که هرگز تمام نمی‌شوند

اگر تابع همیشه خطا پرتاب کند یا وارد حلقه بی‌نهایت شود، نوع خروجی آن never است.

مثال: پرتاب خطا

function throwError(message: string): never {
  throw new Error(message);
}

never کمک می‌کند TypeScript در تحلیل مسیرهای کد دقیق‌تر عمل کند.


۸) پارامترهای Rest (ورودی‌های نامحدود)

اگر تعداد ورودی‌ها مشخص نیست (مثل جمع کردن چند عدد):

function sum(...numbers: number[]): number {
  return numbers.reduce((acc, cur) => acc + cur, 0);
}

console.log(sum(1, 2, 3));      // 6
console.log(sum(10, 20, 30, 5)); // 65

اینجا numbers یک آرایه از number است.


۹) جلوگیری از خطاهای runtime با تایپ‌گذاری صحیح (مثال واقعی)

سناریوی رایج در JavaScript

فرض کنید تابعی داریم که روی ورودی toUpperCase() اجرا می‌کند:

function upper(text) {
  return text.toUpperCase();
}

upper(123); // ❌ Runtime Error: text.toUpperCase is not a function

این خطا فقط هنگام اجرا رخ می‌دهد.

نسخه TypeScript (ایمن)

function upper(text: string): string {
  return text.toUpperCase();
}

// upper(123); ❌ Error در زمان توسعه
console.log(upper("hello")); // "HELLO"

TypeScript جلوی ارسال نوع اشتباه را می‌گیرد، بنابراین احتمال خطای runtime کمتر می‌شود.


۱۰) نکته مهم: تایپ‌گذاری Callbackها

در دنیای واقعی، بسیاری از توابع ورودی‌شان یک تابع دیگر است (callback).

مثال: فیلتر کردن آرایه

const numbers: number[] = [1, 2, 3, 4, 5];

const evenNumbers = numbers.filter((n: number) => n % 2 === 0);
console.log(evenNumbers); // [2, 4]

TypeScript معمولاً نوع callback را خودش تشخیص می‌دهد، اما دانستن این موضوع مهم است که callback هم تایپ دارد.


جمع‌بندی جلسه هفتم

در این جلسه یاد گرفتیم:

  • چطور برای پارامترهای تابع تایپ تعریف کنیم
  • چطور نوع خروجی تابع را مشخص کنیم
  • تفاوت void و never چیست
  • پارامتر اختیاری (?) و مقدار پیش‌فرض چگونه کار می‌کنند
  • چگونه برای یک متغیر، Function Type تعریف کنیم
  • چرا تایپ‌گذاری توابع باعث کاهش خطاهای runtime می‌شود

در جلسه بعدی می‌توانیم وارد بحث Object Types و تایپ‌گذاری آبجکت‌ها و ساختارهای داده‌ای واقعی در پروژه‌ها شویم.


سوالات متداول

آیا تعیین نوع خروجی تابع همیشه لازم است؟

نه، TypeScript در بسیاری از مواقع می‌تواند نوع خروجی را حدس بزند؛ اما در توابع مهم و عمومی (Public API) بهتر است خروجی را صراحتاً مشخص کنید.

تفاوت void و never چیست؟

  • void: تابع اجرا می‌شود و خروجی معنی‌دار ندارد.
  • never: تابع هیچ‌وقت به انتها نمی‌رسد (یا خطا می‌دهد یا همیشه متوقف می‌ماند).

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

برای مشاهده ویدیوهای کامل هر جلسه، دریافت تمرین‌ها، فایل‌ها و پشتیبانی، می‌توانید دوره کامل را تهیه کنید.