در این دوره تایپ اسکریپت را از اولین نوع تا جنریکها و تایپهای شرطی یاد میگیرید. هر کدی که مینویسید با tsc در حالت strict کامپایل میشود و خطای نوع را همان لحظه میبینید.
// هر سفارش فقط در یکی از این حالتهاست و هر حالت فیلدهای خودش را دارد
type Order =
| { status: 'pending'; id: number }
| { status: 'shipped'; id: number; tracking: string }
| { status: 'delivered'; id: number; days: number }
| { status: 'cancelled'; id: number; reason: string };
function describe(order: Order): string {
switch (order.status) {
case 'pending':
return `#${order.id} waiting for payment`;
case 'shipped':
return `#${order.id} on the way, tracking ${order.tracking}`;
case 'delivered':
return `#${order.id} delivered in ${order.days} days`;
case 'cancelled':
return `#${order.id} cancelled: ${order.reason}`;
default: {
// اگر حالت تازهای اضافه شود و اینجا جا بماند، tsc این خط را رد میکند
const missed: never = order;
return missed;
}
}
}
const orders: Order[] = [
{ status: 'pending', id: 101 },
{ status: 'shipped', id: 102, tracking: 'IR-58213' },
{ status: 'delivered', id: 103, days: 3 },
{ status: 'cancelled', id: 104, reason: 'out of stock' },
];
orders.forEach((o) => console.log(describe(o)));
// @ts-expect-error: «shiped» یکی از حالتها نیست و tsc آن را نمیپذیرد
const typo: Order = { status: 'shiped', id: 105 };
#101 waiting for payment #102 on the way, tracking IR-58213 #103 delivered in 3 days #104 cancelled: out of stock
هر سفارش فقط در یکی از چهار حالت است و هر حالت فیلدهای خودش را دارد. اگر حالتی در switch جا بماند یا نام حالتی اشتباه نوشته شود، tsc برنامه را کامپایل نمیکند. نمونههای دیگر
دوره از نوعهای روزمره شروع میشود و تا برنامهنویسی در سطح نوع پیش میرود.
نوع متغیر، تابع، آرایه و تاپل را مینویسید، فرق any و unknown را یاد میگیرید و میدانید حالت strict چه بررسیهایی را روشن میکند.
با interface و type شیء نوعدار میسازید و با یونیون، literal، as const و satisfies دامنهٔ مقدارها را محدود میکنید. جای enum را هم میشناسید.
پارامتر اختیاری و پیشفرض، پارامتر rest، overload و نوع this را مینویسید و میفهمید callback نوعش را از کجا میگیرد.
با typeof، in، instanceof و گاردهای is و asserts نوع را باریک میکنید و با never مطمئن میشوید هیچ حالتی جا نمانده است.
تابع، اینترفیس و کلاسی مینویسید که با هر نوعی کار کند و قید، پیشفرض و استنتاج را در آنها به کار میبرید.
با keyof، typeof و دسترسی اندیسی نوع را از داده میسازید و Partial، Pick، Omit، Record و ReturnType را هم به کار میبرید و هم از صفر مینویسید.
تایپهای نگاشتی، تایپهای شرطی با infer و تایپهای قالبرشتهای مینویسید، مثلاً نوعی که پارامترهای یک مسیر را از متن آن بیرون میکشد.
null و undefined را پیش از استفاده حذف میکنید، خطا را با نوع Result برمیگردانید و از دامهایی مثل as ناامن و any پنهان دوری میکنید.
کلاس با private، readonly، abstract و implements مینویسید، Promise و async/await را با نوع دقیق به کار میبرید و با import type و namespace کار میکنید.
یک ردیاب وظایف نوعدار را قدمبهقدم میسازید و در «باشگاه نوع» چالشهایی مثل DeepReadonly و تجزیهٔ مسیر را در سطح نوع حل میکنید.
این پنج نمونه را برای همین صفحه نوشتهایم. هر کدام با همان کامپایلر و همان Node درسها اجرا شده و خروجی کنارش همان چیزی است که چاپ میکند. خطی که زیر @ts-expect-error آمده عمداً غلط است و tsc آن را رد میکند.
هر سفارش فقط در یکی از چهار حالت است و هر حالت فیلدهای خودش را دارد. اگر حالتی در switch جا بماند یا نام حالتی اشتباه نوشته شود، tsc برنامه را کامپایل نمیکند.
// هر سفارش فقط در یکی از این حالتهاست و هر حالت فیلدهای خودش را دارد
type Order =
| { status: 'pending'; id: number }
| { status: 'shipped'; id: number; tracking: string }
| { status: 'delivered'; id: number; days: number }
| { status: 'cancelled'; id: number; reason: string };
function describe(order: Order): string {
switch (order.status) {
case 'pending':
return `#${order.id} waiting for payment`;
case 'shipped':
return `#${order.id} on the way, tracking ${order.tracking}`;
case 'delivered':
return `#${order.id} delivered in ${order.days} days`;
case 'cancelled':
return `#${order.id} cancelled: ${order.reason}`;
default: {
// اگر حالت تازهای اضافه شود و اینجا جا بماند، tsc این خط را رد میکند
const missed: never = order;
return missed;
}
}
}
const orders: Order[] = [
{ status: 'pending', id: 101 },
{ status: 'shipped', id: 102, tracking: 'IR-58213' },
{ status: 'delivered', id: 103, days: 3 },
{ status: 'cancelled', id: 104, reason: 'out of stock' },
];
orders.forEach((o) => console.log(describe(o)));
// @ts-expect-error: «shiped» یکی از حالتها نیست و tsc آن را نمیپذیرد
const typo: Order = { status: 'shiped', id: 105 };
#101 waiting for payment #102 on the way, tracking IR-58213 #103 delivered in 3 days #104 cancelled: out of stock
فهرستی از JSON میرسد که بعضی ردیفهایش ناقص یا غلط است. یک گارد نوع ردیفهای سالم را جدا میکند و از آن به بعد tsc میداند سن هر نفر حتماً عدد است.
interface User {
name: string;
age: number;
}
// گارد نوع: اگر true برگرداند، tsc از آن به بعد value را User میداند
function isUser(value: unknown): value is User {
return (
typeof value === 'object' && value !== null &&
'name' in value && typeof value.name === 'string' &&
'age' in value && typeof value.age === 'number'
);
}
// دادهای که از بیرون میرسد. JSON.parse شکل آن را تضمین نمیکند
const raw = '[{"name":"Sara","age":28},{"name":"Reza"},{"name":"Mina","age":"31"},null,{"name":"Ali","age":40}]';
const data: unknown = JSON.parse(raw);
if (Array.isArray(data)) {
for (const item of data) {
if (isUser(item)) {
// بدون گارد، سن مینا که رشته است بهجای 32 متن "311" میشد
console.log(`OK ${item.name.padEnd(6)}${item.age + 1} next year`);
} else {
console.log(`SKIP ${JSON.stringify(item)}`);
}
}
}
OK Sara 29 next year
SKIP {"name":"Reza"}
SKIP {"name":"Mina","age":"31"}
SKIP null
OK Ali 41 next yearتابع جنریک groupBy هر آرایهای را بر اساس یک کلید دستهبندی میکند. tsc از ورودی میفهمد خروجی چه کلیدهایی دارد و کلید ناموجود را نمیپذیرد.
// یک تابع جنریک برای هر نوع آرایه؛ نوع خروجی هم از ورودی ساخته میشود
function groupBy<T, K extends string>(items: T[], keyOf: (item: T) => K): Record<K, T[]> {
const groups = {} as Record<K, T[]>;
for (const item of items) {
const key = keyOf(item);
(groups[key] ??= []).push(item);
}
return groups;
}
interface Book {
title: string;
level: 'beginner' | 'advanced';
}
const books: Book[] = [
{ title: 'First Steps', level: 'beginner' },
{ title: 'Deep Types', level: 'advanced' },
{ title: 'Hello Code', level: 'beginner' },
{ title: 'Compilers', level: 'advanced' },
];
// نوع byLevel را tsc خودش میفهمد: Record<'beginner' | 'advanced', Book[]>
const byLevel = groupBy(books, (b) => b.level);
console.log('beginner:', byLevel.beginner.map((b) => b.title).join(', '));
console.log('advanced:', byLevel.advanced.map((b) => b.title).join(', '));
// همان تابع، این بار روی عدد
const parity = groupBy([3, 8, 1, 6, 5], (n) => (n % 2 === 0 ? 'even' : 'odd'));
console.log(parity);
// @ts-expect-error: «expert» یکی از کلیدهای این گروهها نیست
byLevel.expert;
beginner: First Steps, Hello Code
advanced: Deep Types, Compilers
{ odd: [ 3, 1, 5 ], even: [ 8, 6 ] }یک نوع نگاشتی از فیلدهای هر شیء، متدهای getTitle و getPages را در سطح نوع میسازد. tsc نوع خروجی هر متد را میداند و متدی را که ساخته نشده نمیپذیرد.
// از نام فیلدهای یک نوع، نام متدها را در سطح نوع میسازد: title → getTitle
type Getters<T> = {
[K in keyof T & string as `get${Capitalize<K>}`]: () => T[K];
};
function makeGetters<T extends object>(obj: T): Getters<T> {
const out: Record<string, () => unknown> = {};
for (const key of Object.keys(obj) as (keyof T & string)[]) {
out[`get${key[0].toUpperCase()}${key.slice(1)}`] = () => obj[key];
}
return out as Getters<T>;
}
const book = { title: 'Deep Types', pages: 320, available: true };
const g = makeGetters(book);
// tsc نوع g را میداند: { getTitle(): string; getPages(): number; getAvailable(): boolean }
console.log(Object.keys(g));
console.log(g.getTitle().toUpperCase());
console.log(g.getPages() + 10);
console.log(g.getAvailable());
// @ts-expect-error: book فیلد author ندارد، پس getAuthor هم ساخته نمیشود
type Missing = Getters<typeof book>['getAuthor'];
[ 'getTitle', 'getPages', 'getAvailable' ] DEEP TYPES 330 true
سه کار ناهمگام با هم شروع میشوند و هر کدام که کوتاهتر است زودتر تمام میشود. Promise.all نتیجهها را به ترتیب درخواست برمیگرداند و tsc نوع هر کدام را جدا میداند.
const wait = (ms: number) => new Promise<void>((done) => setTimeout(done, ms));
async function loadUser(id: number): Promise<{ id: number; name: string }> {
await wait(30);
console.log('user ready');
return { id, name: 'Sara' };
}
async function loadOrders(userId: number): Promise<number[]> {
await wait(20);
console.log('orders ready');
return [120_000, 85_000, 240_000];
}
async function loadPoints(userId: number): Promise<number> {
await wait(10);
console.log('points ready');
return 1200;
}
async function main() {
// هر سه درخواست با هم شروع میشوند؛ tsc نوع هر خانه را جدا میداند
const [user, orders, points] = await Promise.all([loadUser(7), loadOrders(7), loadPoints(7)]);
const total = orders.reduce((sum, x) => sum + x, 0);
console.log(`${user.name}: ${orders.length} orders, total ${total}, ${points} points`);
}
main();
points ready orders ready user ready Sara: 3 orders, total 445000, 1200 points
| موضوع | جاوااسکریپت | تایپ اسکریپت |
|---|---|---|
| خطای نوع | هنگام اجرا پیدا میشود، اگر آن خط اجرا شود. | پیش از اجرا، هنگام کامپایل پیدا میشود. |
| اشتباه در نام یک فیلد | مقدار undefined میشود و برنامه ادامه میدهد. | کامپایلر همان خط را نشان میدهد. |
| تغییر شکل یک داده | جاهایی را که باید عوض شوند خودتان پیدا میکنید. | کامپایلر همهٔ آن جاها را فهرست میکند. |
| تکمیل خودکار در ویرایشگر | ویرایشگر فقط حدس میزند. | از روی نوعها دقیق است. |
| هزینهٔ یادگیری | کمتر | نوشتن و خواندن نوعها را هم یاد میگیرید. |
نوعها فقط هنگام کامپایل وجود دارند و در جاوااسکریپت ساختهشده پاک میشوند. برای همین برنامه کندتر نمیشود.
هر درس یک مفهوم را با مثال توضیح میدهد و یک کار مشخص از شما میخواهد.
با دکمهٔ اجرا، کد شما کامپایل و روی Node اجرا میشود. اگر نوعی اشتباه باشد، پیام کامپایلر را همانجا میبینید.
دکمهٔ بررسی، کد را با تستهای تمرین میسنجد. اگر گیر کردید، راهنمای همان تمرین قدمبهقدم کمکتان میکند.
۲۴ سرفصل، ۲۲۰ درس، ۱۸۶ تمرین
درسهای اول رایگان است و برای شروع پرداختی لازم نیست. هر وقت خواستید ادامه دهید، همین دوره را بخرید یا با اشتراک پرو همهٔ دورهها را باز کنید.
۱۲ درس ابتدایی رایگان است. برای ادامه، اشتراک بگیرید یا دوره را بخرید.
بدون نیاز به پرداخت مبلغی برای شروع
اگر جاوااسکریپت مینویسید و میخواهید در پروژههایی کار کنید که با تایپ اسکریپت نوشته شدهاند.
اگر با React، Angular، Node یا NestJS کار میکنید، چون پروژههای این ابزارها اغلب با تایپ اسکریپت نوشته میشوند.
اگر جنریکها و تایپهای شرطی را در کد دیگران میبینید و میخواهید خودتان آنها را بنویسید.
پیشنیاز: آشنایی با مبانی جاوااسکریپت، مثل متغیر، تابع، آرایه و شیء.
اولین برنامهٔ نوعدارتان را همین امروز در مرورگر بنویسید. شروع رایگان است و در کمتر از دو دقیقه وارد محیط یادگیری میشوید.