رایج ترین نوع (تایپ) ها در تایپ اسکریپت [TypeScript]
منبع: https://rasanika.com
در این مطلب می خواهیم نوع های تعدادی از رایج ترین مقادیر جاوا اسکریپت را بررسی کرده و ببینیم این نوع ها چطور در تایپ اسکریپت تعریف می شوند. از نوع های پایه شروع می کنیم و سپس با استفاده از آن ها نوع های پیچیده تری می سازیم.
این دومین مقاله از سری مقالات راهنمای جامع تایپ اسکریپت است. برای مشاهده بقیه بخش ها، فهرست مقالات را ببینید.
نوع های پایه: 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) {
// این قسمت هیچ وقت اجرا نمی شود
}
قدم بعدی
در این مطلب با برخی از رایج ترین نوع ها آشنا شدیم. در مقاله بعدی از سری آموزش جامع تایپ اسکریپت، محدود کردن یا Narrowing را بررسی خواهیم کرد:
محدود سازی یا Narrowing در تایپ اسکریپت [TypeScript]
rasanika.comتایپ اسکریپت مسیر های مختلف اجرای کد را دنبال می کند و می تواند با بررسی و تحلیل کد ما، محدود ترین و دقیق ترین نوع ممکن را برای یک مقدار در یک موقعیت معین، تشخیص دهد. به این بررسی ها (یا به اصطلاح: گارد یا محافظ نوع) و تبدیل نوع ها به نوع های محدودتر و دقیق تر، narrowing یا محدود سازی گفته می شود. این سومین مقاله از سری مقالات راهنمای جامع تایپ اسکریپت است. برای مشاهده بقیه بخش ها، فهرست مقالات را ببینید. تصور کنید یک تابع به اسم padLeft داریم: function padLeft(padding: number | string, input: s
برای یادگیری بهتر حتما آموزش های تایپ اسکریپتی رسانیکا و سایر سایت ها را هم ببینید. البته صرفا با مطالعه یا دیدن دوره های آموزشی نمی توان مهارت لازم را کسب کرد و سعی کنید همراه با این مطالعات پروژه های کاملی هم بسازید.
برای تست کد تایپ اسکریپت در مرورگر هم می توانید از TS Playground استفاده کنید.