نوع های آبجکت در تایپ اسکریپت [TypeScript]
منبع: https://rasanika.com
در جاوا اسکریپت روش اساسی گروه بندی و انتقال داده ها، شیء یا object ها هستند. در تایپ اسکریپت هم برای توصیف آن ها از نوع شیء یا object type استفاده می کنیم.
این پنجمین مقاله از سری مقالات راهنمای جامع تایپ اسکریپت است. برای مشاهده بقیه بخش ها، فهرست مقالات را ببینید.
نوع آبجکت ها می توانند ناشناس (anonymous):
function greet(person: { name: string; age: number }) {
return "Hello " + person.name;
}
یا نام گذاری شده باشند، از طریق interface:
interface Person {
name: string;
age: number;
}
function greet(person: Person) {
return "Hello " + person.name;
}
و یا از طریق type alias:
type Person = {
name: string;
age: number;
};
function greet(person: Person) {
return "Hello " + person.name;
}
ویژگی ها
هر ویژگی در یک نوع آبجکت می تواند چند چیز را مشخص کند: نوع ویژگی، اینکه آیا اختیاری است و اینکه آیا امکان اختصاص مقدار به آن وجود دارد یا نه.
ویژگی های اختیاری
برای توصیف نوع یک ویژگی اختیاری می توانیم از علامت سوال (?
) استفاده کنیم:
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
function paintShape(opts: PaintOptions) {
// ...
}
const shape = getShape();
paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });
در مثال بالا xPos
و yPos
اختیاری هستند و هنگام فراخوانی تابع paintShape
می توانیم آنها را وارد نکنیم. ولی اگر وارد شوند، حتما باید نوع number
را داشته باشند.
همچنین می توانیم به آنها دسترسی داشته باشیم ولی با فعال بودن strictNullChecks
تایپ اسکریپت به ما می گوید که ممکن است undefined
باشند.
function paintShape(opts: PaintOptions) {
// xPos: number | undefined
let xPos = opts.xPos;
// yPos: number | undefined
let yPos = opts.yPos;
// ...
}
به این صورت با چک کردن undefined
بودن آنها، می توانیم نوع محدودتر فقط number
را داشته باشیم:
function paintShape(opts: PaintOptions) {
// xPos: number
let xPos = opts.xPos === undefined ? 0 : opts.xPos;
// yPos: number
let yPos = opts.yPos === undefined ? 0 : opts.yPos;
// ...
}
همچنین می توانیم کد بالا را به این شکل هم با مشخص کردن مقدار پیش فرض برای پارامتر ها، بنویسیم:
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
console.log("x coordinate at", xPos); // xPos: number
console.log("y coordinate at", yPos); // yPos: number
// ...
}
در کد بالا ما آبجکت paintShape
را destructure کردیم و مقادیر پیش فرضی به ویژگی های yPos
و xPos
دادیم که یعنی حالت undefined
را از این دو ویژگی حذف کردیم و نوع آنها در داخل تابع فقط number
شد.
توجه کنید که فعلا هیچ راهی برای مشخص کردن نوع داخل destructure کردن نیست، چون چنین عبارتی در جاوا اسکریپت معنی دیگری دارد:
function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
render(shape); // خطا!
render(xPos); // خطا!
}
در کد بالا shape: Shape
یعنی اسم پارامتر shape
در داخل تابع به Shape
تغییر پیدا کرد و به همین شکل اسم xPos
هم به number
تغییر پیدا کرد. در اینجا number
یک متغیر است نه نوع.
ویژگی های فقط خواندنی
همچنین ویژگی های آبجکت ها را می توانیم readonly
تعریف کنیم که اجازه اختصاص و تغییر مقدار آن ویژگی را ندهیم. البته readonly
فقط در سیستم بررسی نوع تاثیر می گذارد و در خود اجرای کد تاثیری ندارد:
interface SomeType {
readonly prop: string;
}
function doSomething(obj: SomeType) {
// می توانیم مقدارش را بخوانیم
console.log(`prop has the value '${obj.prop}'.`);
// خطا! نمی توانیم به آن مقدار اختصاص دهیم
obj.prop = "hello";
}
همچنین readonly
مقدار ویژگی را کلا غیر قابل ویرایش نمی کند و برای مثال ویژگی های داخلی آن قابل ویرایش هستند:
interface Home {
readonly resident: { name: string; age: number };
}
function visitForBirthday(home: Home) {
console.log(`Happy birthday ${home.resident.name}!`);
// اوکی (ویژگی داخلی قابل تغییر است)
home.resident.age++;
}
function evict(home: Home) {
// نمی توانیم مستقیما خودش را تغییر دهیم
home.resident = {
name: "Victor the Evictor",
age: 42,
};
}
یا مثال دیگری از تغییر یک ویژگی readonly
به صورت غیر مستقیم:
interface Person {
name: string;
age: number;
}
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
let writablePerson: Person = {
name: "Person McPersonface",
age: 42,
};
// اوکی هست
let readonlyPerson: ReadonlyPerson = writablePerson;
console.log(readonlyPerson.age); // prints '42'
writablePerson.age++; // با این کار مقدارش در هر دو آبجکت تغییر می کند
console.log(readonlyPerson.age); // prints '43'
تعریف به روش ایندکس (Index Signatures)
گاهی اوقات نام ویژگی های یک آبجکت را نمی دانیم ولی نوع مقدارشان را می دانیم. در این صورت می توانیم آن ها را به روش ایندکس تعریف کنیم:
interface StringArray {
[myIndex: number]: string;
}
const myArray: StringArray = ...;
const secondItem = myArray[1]; // secondItem: string
در مثال بالا [myIndex: number]: string;
در اینترفیس StringArray
یعنی یک آبجکت با ویژگی های عدد که مقدار آن ها رشته است (که می شود همان آرایه رشته ها).
فقط این از نوع ها را می توانیم به عنوان ایندکس انتخاب کنیم: string
، number
، symbol
، union ها ، template string و ترکیبات این ها.
وقتی یک آبجکت را به روش ایندکس تعریف می کنیم همه ویژگی های آن باید با ایندکس تعریف شده سازگار باشند. به مثال زیر توجه کنید:
interface NumberDictionary {
[myProperty: string]: number;
length: number; // ok
name: string; // خطا!
}
در کد بالا چون تعریف کردیم همه ویژگی های NumberDictionary
باید مقدارشان نوع number
باشد، پس نمی توانیم ویژگی name
را طوری تعریف کنیم که مقدارش string
باشد.
البته می توانیم ایندکس را به این صورت تعریف کنیم که هم عدد و هم رشته را پوشش دهد:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok
name: string; // ok
}
در نهایت می توانیم از readonly
هم در تعریف ایندکس استفاده کنیم:
interface ReadonlyStringArray {
readonly [theIndex: number]: string;
}
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory"; // خطا!
گسترش (Extend) نوع ها
برای گسترش یا همان Extend کردن یک interface و اضافه کردن ویژگی های بیشتر به آن می توانیم از کلیدواژه extends
به این صورت استفاده کنیم:
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
interface AddressWithUnit extends BasicAddress {
unit: string;
}
همچنین با استفاده از extends
می توانیم چندین اینترفیس را انتخاب کنیم:
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
interface ColorfulCircle extends Colorful, Circle {}
const cc: ColorfulCircle = {
color: "red",
radius: 42,
};
اشتراک نوع ها (Intersection)
روش دیگری که برای گسترش و استفاده مجدد از نوع های تعریف شده وجود دارد، اشتراک یا Intersection است. برای اشتراک دو نوع می توانیم آن ها را با استفاده از &
به هم اضافه کنیم:
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
type ColorfulCircle = Colorful & Circle;
به صورت مستقیم بدون تعریف type
هم می توانیم از &
استفاده کنیم:
function draw(circle: Colorful & Circle) {
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}
// okay
draw({ color: "blue", radius: 42 });
// خطا!
draw({ color: "red", raidus: 42 });
نوع های آبجکت جنریک
به مثال زیر توجه کنید:
interface Box {
contents: any;
}
در حال حاضر نوع ویژگی contents
برابر any
است. با وجود اینکه این کد بدون خطا کار می کند ولی با فلسفه کلی استفاده از یک سیستم بررسی نوع، در تضاد است چون any
در حقیقت سیستم بررسی نوع را غیرفعال می کند.
استفاده از unknown
هم بی خطر نیست و مجبوریم یا صراحتا نوع دقیق را با جاوا اسکریپت چک کنیم و یا از as
استفاده کنیم:
interface Box {
contents: unknown;
}
let x: Box = {
contents: "hello world",
};
if (typeof x.contents === "string") {
console.log(x.contents.toLowerCase());
}
console.log((x.contents as string).toLowerCase());
البته یک راه بی خطر می تواند جداگانه تعریف کردن باشد:
interface NumberBox {
contents: number;
}
interface StringBox {
contents: string;
}
interface BooleanBox {
contents: boolean;
}
ولی این یعنی زحمت بیشتر چون مجبوریم همه جا اورلود ها و عملیات های مختلفی برای هندل کردن آنها بنویسیم.
function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
box.contents = newContents;
}
با پیچیده شدن کد طبیعتا تعریف نوع های مجزا به صورت بالا بسیار سخت تر هم خواهد شد. بنابراین در تایپ اسکریپت نوع های آبجکت را هم می توانیم به صورت جنریک (Generic) تعریف کنیم:
interface Box<Type> {
contents: Type;
}
و نحوه استفاده:
let box: Box<string>;
مثال دیگر:
interface Box<Type> {
contents: Type;
}
interface Apple {
// ....
}
// مثل این است که بنویسیم: '{ contents: Apple }'
type AppleBox = Box<Apple>;
در واقع می توانیم Box
را یک قالب و تمپلیت برای نوع Type
تصور کنیم. حتی می توانیم آن را با توابع جنریک هم ترکیب کرده و به کل از نوشتن اورلود های پیچیده دوری کنیم:
function setContents<Type>(box: Box<Type>, newContents: Type) {
box.contents = newContents;
}
همچنین غیر از interface می توانیم type alias جنریک هم مثل نمونه های زیر تعریف کنیم:
type Box<Type> = {
contents: Type;
};
type OrNull<Type> = Type | null;
type OneOrMany<Type> = Type | Type[];
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
نوع Array
خود تایپ اسکریپت هم دقیقا یک نوع آبجکت جنریک است. یک آرایه ساختاری است که بدون وابستگی به نوع آیتم های داخلش، ویژگی ها و متد های مختلفی برای انجام عملیات مختلف دارد.
interface Array<Type> {
length: number;
pop(): Type | undefined;
push(...items: Type[]): number;
// ...
}
در جاوا اسکریپت موارد دیگری هم مثل Map<K, V>
، Set<T>
و Promise<T>
وجود دارند که در تایپ اسکریپت با جنریک ها تعریف شده اند.
نوع های آرایه فقط خواندنی
نوع ReadonlyArray
نوع خاصی از همان نوع Array
است که در آن اجازه استفاده از متد های تغییر دهنده آرایه را نداریم:
function doStuff(values: ReadonlyArray<string>) {
// می توانیم مقدارش را بخوانیم
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
// خطا! نمی توانیم مقدارش را تغییر دهیم
values.push("hello!");
}
البته این فقط یک نوع در تایپ اسکریپت و چنین چیزی در خود جاوا اسکریپت نداریم:
new ReadonlyArray("red", "green", "blue"); // خطا!
نوع های چندتایی (Tuple)
نوع tuple یا چندتایی یکی دیگر از نوع های آرایه است که تعداد و نوع دقیق تمام آیتم هایش را می داند:
type StringNumberPair = [string, number];
در کد بالا StringNumberPair
دقیقا یک آرایه است که فقط دو آیتم دارد. نوع آیتم اول آن دقیقا string
و نوع آیتم دوم number
است. مثال استفاده:
function doSomething(pair: [string, number]) {
const a = pair[0]; // a: string
const b = pair[1]; // b: number
const c = pair[2]; // خطا!
}
doSomething(["hello", 42]);
همچنین آخرین آیتم های یک نوع چندتایی می توانند اختیاری باشند که با ?
مشخص می کنیم:
type Either2dOr3d = [number, number, number?];
function setCoordinate(coord: Either2dOr3d) {
const [x, y, z] = coord;
// x: number
// y: number
// z: number | undefined
// coord.length: 2 | 3
}
همچنین می توانیم نوع چندتایی و آرایه را ترکیب کنیم و نوع های پیشرفته تری بسازیم:
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];
و استفاده به این صورت:
const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];
نوع های چندتایی فقط خواندنی
یک نوع چندتایی هم می تواند فقط خواندنی یا readonly باشد که اجازه اختصاص و تغییر مقادیرش را ندهد:
function doSomething(pair: readonly [string, number]) {
pair[0] = "hello!"; // خطا!
}
قدم بعدی
در این مطلب با برخی از نوع های Object در تایپ اسکریپت آشنا شدیم. در ادامه این سری، ایجاد نوع از روی نوع های دیگر را با جزئیات بیشتری بررسی خواهیم کرد:
ایجاد نوع از روی نوع های دیگر در تایپ اسکریپت [TypeScript]
rasanika.comسیستم تایپ اسکریپت بسیار قدرتمند است زیرا امکان بیان انواع بر حسب انواع دیگر را فراهم می کند. ساده ترین شکل این حالت جنریک است، ولی عملگرهای متنوعی برای ترکیب نوع ها وجود دارد. همچنین با ترکیب عملگرهای مختلف، میتوانیم عملیات و مقادیر پیچیده را به روشی مختصر و قابل نگهداری بیان کنیم. این ششمین مقاله از سری مقالات راهنمای جامع تایپ اسکریپت است. برای مشاهده بقیه بخش ها، فهرست مقالات را ببینید. جنریک ها (Generics) جنریک ها یکی از ویژگی های اساسی زبان های دارای تایپ ایستا هستند که به توسعه دهندگان ا
همچنین جهت یادگیری می توانید آموزش های پروژه محور مرتبط با تایپ اسکریپت را هم بررسی کنید.
برای یادگیری بهتر، علاوه بر مطالعه و تحقیق، سعی کنید تمرینات پروژه محور هم انجام دهید.
برای تست کد تایپ اسکریپت در مرورگر هم می توانید از TS Playground استفاده کنید.