رایج ترین نوع (تایپ) ها در تایپ اسکریپت [TypeScript]

رایج ترین نوع های تایپ اسکریپت

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

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

نوع های پایه: string ، number و boolean

در جاوا اسکریپت سه مقدار پایه یا primitive بسیار پر استفاده و رایج وجود دارد: string ، number و boolean و هر کدام در تایپ اسکریپت نوع مخصوص خودشان را دارند.

  • string مقادیر رشته ای مثل "hello world" است.

  • number مقادیر عددی مثل 15 و 5.4 است. (عدد صحیح و اعشاری در جاوا اسکریپت جدا نیستند و همه اعداد صرفا number هستند.)

  • boolean برای فقط شامل دو مقدار true و false است.

در تایپ اسکریپت نوع های String ، Number و Boolean (شروع با حرف بزرگ) هم هستند که برای نوع های خاصی استفاده می شوند. برای مقادیر پایه همیشه باید از string ، number و boolean (حروف کوچک) استفاده کنیم.

آرایه ها

نوع یک آرایه مثل [1, 2, 3] را می توانیم با number[] مشخص کنیم. در واقع این ترکیب را می توانیم برای همه نوع ها (مثل string[] آرایه از رشته ها) استفاده کنیم. ممکن است به صورت Array<number> هم نوشته شود که هر دو معنای یکسانی دارند. به ترکیب T<U> جنریک (Generic) گفته می شود که در مطالب بعدی بررسی خواهیم کرد.

توجه کنید که نوع [number] متفاوت است و به آن چندتایی یا Tuple گفته می شود. این مورد را نیز در مطالب بعدی بررسی خواهیم کرد.

نوع any

در تایپ اسکریپت یک نوع خاص به اسم any وجود دارد و زمانی استفاده می شود که نمی خواهیم سیستم بررسی نوع، برای یک مقدار خاص خطا دهد. زمانی که نوع یک مقدار any است، هر عملیاتی (فراخوانی مثل تابع، دسترسی به ویژگی ها، دادن هر نوع مقدار و …) می توانیم روی آن انجام دهیم:

let obj: any = { x: 0 };
// هیچ کدام از موارد زیر خطا نمی دهند
// تایپ اسکریپت برای این مقدار عملا غیر فعال است
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

توجه: استفاده از نوع any در تضاد با تمام دلایلی است که از تایپ اسکریپت استفاده می کنیم. فقط زمانی باید از any استفاده کنیم که راه حل دیگری نداریم.

تنظیم noImplicitAny

زمانی که یک نوع را خودمان مشخص نمی کنیم و تایپ اسکریپت هم نمی تواند از روی کد، نوع را تشخیص دهد، به صورت پیشفرض نوع any را انتخاب می کند و خطا نمی دهد.

معمولا برای جلوگیری از اشتباه، بهتر است با استفاده از آپشن noImplicitAny کامپایلر تایپ اسکریپت را تنظیم کنیم تا اگر نوع any را صراحتا خودمان ننوشتیم، خطا دهد. تنظیمات و TSConfig تایپ اسکریپت را در مطالب بعدی بررسی خواهیم کرد.

متغیر ها

وقتی یک متغیر با const ، var یا let تعریف می کنیم، می توانیم نوع آن را صراحتا بنویسیم:

let myName: string = "Reza";

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

// نیازی به نوشتن string نیست
let myName = "Reza";

لازم نیست همه قوانین تشخیص خودکار را بلد باشیم ولی بهتر است نوع های بدیهی را که خود تایپ اسکریپت به درستی تشخیص می دهد مجددا خودمان هم اضافه نکنیم.

توابع

در تایپ اسکریپت می توانیم هم نوع ورودی و هم نوع خروجی توابع را مشخص کنیم.

پارامتر های ورودی تابع

موقع تعریف تابع، نوع هر یک از پارامتر های ورودی آن را می توانیم بعد از اسم پارامتر تعیین کنیم:

function greet(name: string) {
  console.log("Hello, " + name.toUpperCase() + "!!");
}

// ⚠ خطا! باید رشته می دادیم
greet(42);

حتی اگر نوع پارامتر ها را هم مشخص نکنیم تایپ اسکریپت حداقل تعداد پارامتر ها را کنترل می کند.

خروجی تابع

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

function getAge(): number {
  return 26;
}

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

توابع Anonymous یا ناشناس

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

const names = ["Reza", "Ali", "Mohammad"];

در هر دو تابع ناشناس زیر (تعریف عادی و تعریف arrow) نوع پارامتر name رشته تشخیص داده می شود:

names.forEach(function (name) {
  ...
});

names.forEach((name) => {
  ...
});

حتی با وجود اینکه نوع name را خودمان ننوشتیم، تایپ اسکریپت از طریق متد forEach می داند که نوع تابع و ورودی آن رشته است.

آبجکت (شیء) ها

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

مثلا این یک تابع است که یک نوع کاربر مانند را دریافت می کند:

function saveUser (user: { id: number; name: string }) {
  ...
}

// فراخوانی
saveUser({ id: 123, name: "Reza" });

در مثال فوق پارامتر user نوع آبجکت دارد که دارای ویژگی های name رشته و id عدد است. ویژگی ها را هم با ; و هم با , می توانیم جدا کنیم.

ویژگی (property) های اختیاری

برخی از ویژگی های آبجکت ها می توانند اختیاری باشند و این حالت را می توانیم با اضافه کردن علامت سوال ? بعد از اسم ویژگی، مشخص کنیم:

function saveUser (user: { id: number; name?: string }) {
  ...
}

// هر دو اوکی هستند
saveUser({ id: 123, name: "Reza" });
saveUser({ id: 123 });

در جاوا اسکریپت در صورت دسترسی به یک ویژگی که وجود ندارد بجای خطا، مقدار undefined برگردانده می شود. به همین خاطر قبل از دسترسی به ویژگی های اختیاری باید undefined بودن آن را نیز کنترل کنیم:

function saveUser (user: { id: number; name?: string }) {
  // ⚠ خطا! نام ممکن است تعریف نشده باشد
  if (user.name.length < 5) {
    ...
  }

  // اوکی
  if (user.name && user.name.length < 5) {
    ...
  }
}

نوع های Union (اتحاد)

در تایپ اسکریپت می تواند نوع های جدیدی با ترکیب کردن چند نوع مختلف ساخت. یکی از روش های اینکار استفاده از union است.

ایجاد نوع های Union

یک نوع union از دو یا چند نوع دیگر تشکیل شده و مجموع این نوع ها را نشان می دهد. یعنی مقدار موردنظر می تواند هر کدام از این نوع ها را داشته باشد. برای مثال:

function print (id: string | number) {
  console.log("ID is " + id);
}

// اوکی
print(123);

// اوکی
print("abc");

// ⚠ خطا
print({ id: 123 });

پارامتر id در تابع فوق هم می تواند رشته باشد و هم عدد. نوع های union را با کاراکتر | از هم جدا می کنیم.

کار با نوع های Union

با نحوه تعریف نوع union آشنا شدیم. ولی چه عملیاتی می تواند روی یک مقدار با چنین نوعی انجام داد؟ فقط عملیات مشترک که در همه نوع های تعیین شده موجود باشند را می تواند انجام داد. به عنوان مثال:

function print (id: string | number) {
  // خطا!
  id.toUpperCase();

  // اوکی
  id.toString();
}

در تابع بالا موقع فراخوانی ویژگی toUpperCase کامپایلر خطا می دهد چون id ممکن است عدد باشد و اعداد چنین ویژگی ای ندارند. ولی فراخوانی ویژگی toString بدون خطا است چون این ویژگی هم در عدد و هم در رشته وجود دارد.

راه حل انجام عملیاتی که فقط در یکی از اعضا نوع union وجود دارد، محدود کردن یا narrowing است. در مطالب محدود کردن نوع ها را به طور کامل بررسی خواهیم کرد ولی فعلا برای مثال فوق به این صورت می تواند انجام شود:

function print (id: string | number) {
  if (typeof id === "string") {
    // اوکی، اینجا تایپ اسکریپت می داند که رشته است
    id.toUpperCase();
  }
}

مثال دیگر محدود کردن با استفاده از Array.isArray:

function myFn (value: string[] | string) {

  // اوکی، در رشته و آرایه وجود دارد
  value.slice(2, 6);

  // خطا! در رشته وجود ندارد
  value.join(" - ");

  // خطا! در آرایه وجود ندارد
  value.charAt(3);

  if (Array.isArray(value)) {
    // اوکی، چون حتما آرایه است
    value.join(" - ");
  }
  else {
    // اوکی، چون حتما رشته است
    value.charAt(3);
  }

}

تعریف نام نوع ها (Alias)

تا الان نوع آبجکت ها و union هایی که در قسمت های مختلف کد استفاده کردیم را مستقیما نوشته و هر بار تعریف کردم. خیلی از مواقع نیاز است که از یک نوع به خصوص چندین بار استفاده کنیم و هر بار تکرار آن کار جالبی نیست.

با استفاده از نام یا نام مستعار (Alias Name) نوع ها می توانیم یکبار نوع را تعریف و در قسمت های مختلف کد استفاده کنیم. این کار مثل تعریف متغیر ها است. برای مثال:

type ID = string | number;

type User  = {
  id: ID;
  name?: string;
};


function saveUser (user: User) {
  ...
}

const reza: User = { id: 123 };
saveUser(reza);

رابط یا اینترفیس (Interface)

تعریف اینترفیس یا interface یک روش دیگر برای نام گذاری نوع ها است:

interface Post {
  id: number;
  title: string;
}

function savePost (p: Post) {
  ...
}

savePost({ id: 123, title: "Hello world" });

سیستم نوع در تایپ اسکریپت یک سیستم نوع ساختاری یا structural typing است و این یعنی چه مستقیما، چه از اینترفیس و چه از نام مستعار استفاده کنیم، ملاک یکسان بودن نوع مقادیر در تایپ اسکریپت، ساختار و شکل مقادیر است. یعنی این سه نوع یکسان هستند و می توانند به جای یکدیگر استفاده شوند:

type A = { title: string };

interface B {
  title: string
};

let a: A;
let b: B;
let c: { title: string };

// اوکی
a = b = c = { title: "Hello" };

تفاوت نام مستعار نوع و اینترفیس

اینترفیس و نام مستعار بسیار شبیه هم هستند و در بسیاری از موارد به طور دلخواه می توانیم هر کدام را انتخاب کنیم. تنها تفاوتی که وجود دارد در صورتی که از type استفاده کنیم دیگر امکان اضافه کردن ویژگی های بیشتر به آن وجود ندارد در حالی که interface همیشه extendable و قابل گسترش است.

نحوه extend کردن type:

type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

const bear = getBear();
bear.name;
bear.honey;

نحوه extend کردن interface:

interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear = getBear() 
bear.name
bear.honey

بعد از تعریف type امکان تغییر آن نیست:

type Window = {
  title: string
}

type Window = {
  items: string[]
}

// خطا! نام نوع تکرای است

افزودن فیلد جدید به interface ممکن است:

interface Window {
  title: string
}

interface Window {
  items: string[]
}

window.items.join(' ') // اوکی

اظهار نوع ها (Assertion)

گاهی اوقات ما اطلاعاتی در مورد نوع یک مقدار داریم که تایپ اسکریپت قادر به تشخیص آن نیست. مثلا برای تابع document.getElementById تایپ اسکریپت فقط می داند که این تابع یک نوع عنصر HTMLElement یا null بر می گرداند، ولی ما ممکن است با اطمینان نوع دقیق عنصر مدنظر را بدانیم.

مثلا فرض کنید یک عنصر img با آیدی main_image داریم:

const myImg = document.getElementById("main_image");

console.log(myImg?.src); // خطا! ویژگی src ندارد

به این صورت می توانیم نوع صحیح را خودمان تعیین کنیم:

const myImg = document.getElementById("main_image") as HTMLImageElement;

console.log(myImg.src); // اوکی

نکته: باید دقت کنیم در صورتی که نوع اشتباهی را از طریق as اعمال کنیم هیچ اخطار خاصی دریافت نخواهیم کرد. فقط در برخی از موارد که تایپ اسکریپت مطمئن است تغییر نوع اشتباه است، اخطار می دهد:

const x = "hello" as number; // خطا نوع رشته به عدد قابل تغییر نیست

در واقع تایپ اسکریپت فقط اجازه تغییر به نوع محدودتر و اختصاصی تر را می دهد. اگر بخواهیم حتما این تغییر را انجام دهیم، باید اول نوع را به any تغییر دهیم:

const x = ("hello" as any) as number; // بدون اخطار

همچنین می توانیم بجای any اول به unknown تبدیل کنیم. این نوع را هم در مطالب بعدی بررسی خواهیم کرد.

نوع های دقیق

علاوه بر نوع های کلی تر string یا number می توانیم به رشته ها یا اعداد دقیق و به خصوص هم در تعیین یک نوع، اشاره کنیم. به این مثال توجه کنید:

let action: 'open' | 'close' = 'open';

action = 'view'; // خطا!

action = 'close'; // اوکی
action = 'open'; // اوکی

یا مثلا:

interface Post {
  id: number;
  type: 'article' | 'video' | null;
}

// از 1 تا 5 عددی بر می گرداند
function ratePost (p: Post): 1 | 2 | 3 | 4 | 5 {
  ...
};

ratePost({ id: 1, type: null }); // اوکی
ratePost({ id: 1, type: 'video' }); // اوکی

ratePost({ id: 1, type: 'audio' }); // خطا
ratePost({ id: 1 }); // خطا

خود نوع boolean در واقع یک alias برای یونین true | false است.

تشخیص به عنوان نوع دقیق

وقتی به یک متغیر مقدار اولیه آبجکت می دهیم، تایپ اسکریپت در نظر می گیرد که بعدا ویژگی های آن آبجکت را قرار است تغییر دهیم:

const obj = { counter: 0 };
if (...) {
  obj.counter = 1;
}

تایپ نوع counter را به طور کلی number در نظر می گیرد که بعدا عدد دیگری بتوانیم به آن اختصاص دهیم.

در مثال فوق اگر بخواهیم نوع counter دقیقا برابر 0 باشد باید آن را به این صورت تعیین کنیم:

const obj = { counter: 0 as 0 }

همچنین بجای نوشتن as روی تک تک ویژگی ها، می توانیم کل یک آبجکت را با as const تبدیل کنیم:

const CONFIG = {
  base: "/api",
  method: "POST",
  headers: { authorization: "aaa" },
} as const;

CONFIG.method = "GET"; // خطا!
CONFIG.headers.authorization = "bbb"; // خطا!

پسوند as const برای نوع ها مثل const برای متغیر ها است.

نوع null و undefined

در جاوا اسکریپت دو مقدار پایه null و undefined برای نشان دادن “نبودن” و “مقدار نداشتن” وجود دارد. در تایپ اسکریپت هم نوع های متناظر این مقادیر، دقیقا همین اسم ها را دارند.

رفتار این نوع ها در تایپ اسکریپت بستگی به تنظیم strictNullChecks دارد که در ادامه بررسی می کنیم:

غیرفعال بودن تنظیم strictNullChecks

در صورت غیرفعال و off بودن تنظیم strictNullChecks مقادیری که احتمال null و undefined بودن آن ها وجود دارد، همچنان به طور عادی قابل دسترسی خواهند بود و null و undefined را می توانیم به همه ویژگی ها با هر نوعی، اختصاص دهیم.

بررسی و کنترل نکردن این دو مقدار می تواند باعث بروز باگ های بی شماری شود. بهتر است همیشه در صورت امکان strictNullChecks را فعال کنیم.

فعال بودن تنظیم strictNullChecks

در صورت فعال و on بودن strictNullChecks ، موقع دسترسی به مقادیری که احتمال null و undefined بودن آن ها وجود دارد، باید اول این موارد را تست و کنترل کنیم. مثلا از محدود سازی یا عملگر اختیاری (Optional chaining) می توانیم استفاده کنیم.

function doSomething(x: string | null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, " + x.toUpperCase());
  }
}

عملگر اظهار null نبودن (پسوند !)

تایپ اسکریپت یک ترکیب و syntax خاص برای حذف و نادیده گرفتن null و undefined دارد. با نوشتن ! بعد از یک متغیر یا expression به تایپ اسکریپت می گوییم که مقدار موردنظر قطعا null یا undefined نیست. دقیقا مثل اظهار نوع با as باید دقت کنیم تا اشتباهی مرتکب نشویم چون تایپ اسکریپت را مجبور می کنیم که به ما اعتماد کند.

function liveDangerously(x?: number | null) {
  // اوکی
  console.log(x!.toFixed());
}

نوع های Enum

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

سایر نوع های اولیه

خالی از لطف نیست که سایر مقادیر اولیه موجود در جاوا اسکریپت و نوع های متناظر آن ها را نیز در تایپ اسکریپت بررسی کنیم. البته فعلا وارد جزئیاتشان نمی شویم:

bigint

از ES2020 به بعد، یک مقدار اولیه جدید برای نشان دادن اعداد صحیح بسیار بزرگ به جاوا اسکریپت اضافه شده است، BigInt:

const oneHundred: bigint = BigInt(100);
const anotherHundred: bigint = 100n;

اطلاعات بیشتر در مورد نوع bigint

symbol

یک مقدار اولیه در جاوا اسکریپت وجود دارد که برای ساخت رفرنس های منحصر به فرد استفاده شده و با تابع Symbol ساخته می شود:

const nameOne = Symbol("name");
const nameTwo = Symbol("name");

if (nameOne === nameTwo) {
  // این قسمت هیچ وقت اجرا نمی شود
}

رفرنس نوع symbol

قدم بعدی

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

برای یادگیری بهتر حتما آموزش های تایپ اسکریپتی رسانیکا و سایر سایت ها را هم ببینید. البته صرفا با مطالعه یا دیدن دوره های آموزشی نمی توان مهارت لازم را کسب کرد و سعی کنید همراه با این مطالعات پروژه های کاملی هم بسازید.

برای تست کد تایپ اسکریپت در مرورگر هم می توانید از TS Playground استفاده کنید.


کامنت ها