آموزش تایپاسکریپت (TypeScript) برای برنامهنویس جاوااسکریپت
آموزش تایپاسکریپت برای کسانی که جاوااسکریپت بلدند: نصب، tsconfig، انواع داده، Union و Generic، و ساخت یک پروژهی کوچک که با tsc و Node.js اجرا میشود.

فهرست مطالب
آموزش تایپاسکریپت (TypeScript) برای کسی که جاوااسکریپت بلد است، بیشتر از آنکه یادگیری یک زبان تازه باشد، یاد گرفتن یک عادت تازه است: گفتن اینکه هر داده چه شکلی دارد. TypeScript همان جاوااسکریپت است بهعلاوهی یک سیستم نوع (Type System) که خطاها را پیش از اجرا پیدا میکند. در این آموزش از نصب شروع میکنیم، مفاهیم اصلی را با مثال یاد میگیریم و در پایان یک پروژهی کوچک میسازیم که هم با کامپایلر و هم مستقیم با Node.js اجرا میشود.
پیشنیاز: آشنایی با جاوااسکریپت مدرن و نصب بودن نسخهی LTS از Node.js.
TypeScript چیست و چه مشکلی را حل میکند؟
این کد جاوااسکریپت را ببینید:
const user = { name: "Sara", age: 21 };
console.log(user.nmae.toUpperCase());
یک غلط تایپی ساده، و برنامه فقط هنگام اجرا با خطای TypeError از کار میافتد؛ شاید روی سرور و جلوی چشم کاربر. TypeScript همین اشتباه را همان لحظهای که کد را مینویسید در ویرایشگر علامت میزند، چون میداند شیء user فیلدی به نام nmae ندارد.
TypeScript یک ابرمجموعه (Superset) از جاوااسکریپت است؛ یعنی هر کد جاوااسکریپت معتبر، از نظر نحوی TypeScript معتبر هم هست. کامپایلر tsc انواع را بررسی میکند و بعد آنها را حذف میکند تا جاوااسکریپت خالص باقی بماند. پس نوعها فقط در زمان توسعه وجود دارند و روی سرعت اجرا اثری ندارند.
نصب و راهاندازی پروژه
یک پوشهی تازه بسازید و TypeScript را بهعنوان وابستگی توسعه نصب کنید. اگر با خط فرمان راحت نیستید، راهنمای دستورات لینوکس برای برنامهنویسان کمکتان میکند. بستهی @types/node هم تعریف نوعهای Node.js را اضافه میکند:
mkdir ts-tasks && cd ts-tasks
npm init -y
npm install -D typescript @types/node
npx tsc -v
از TypeScript 7 به بعد، کامپایلر با زبان Go بازنویسی شده و طبق اعلام تیم TypeScript معمولاً حدود ده برابر سریعتر از نسخهی ۶ است. خبر خوب اینکه برای شما چیزی عوض نمیشود: نام بسته همچنان typescript و نام دستور همچنان tsc است.
نکته برای کاربران ایران: اگر نصب بستهها از رجیستری رسمی npm کند است یا قطع میشود، میتوانید با
npm config set registry <mirror-url>از یک آینهی (Mirror) معتبر و شناختهشده استفاده کنید و بعداً باnpm config delete registryبه رجیستری پیشفرض برگردید. فقط از آینههایی استفاده کنید که به آنها اعتماد دارید، چون هر بستهای که نصب میکنید روی سیستم شما کد اجرا میکند.
فایل tsconfig.json
تنظیمات کامپایلر در فایل tsconfig.json در ریشهی پروژه قرار میگیرد. این فایل را بسازید:
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"types": ["node"],
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"rewriteRelativeImportExtensions": true
},
"include": ["src"]
}
| گزینه | کاربرد |
|---|---|
target |
نسخهی جاوااسکریپت خروجی |
module |
سیستم ماژول؛ nodenext با رفتار Node.js هماهنگ است |
rootDir و outDir |
کد منبع در src و خروجی در dist؛ در TypeScript 7 پیشفرض rootDir ریشهی پروژه است، پس آن را صریح تنظیم کنید |
strict |
فعال کردن همهی بررسیهای سختگیرانه (در TypeScript 7 پیشفرض است) |
types |
بارگذاری تعریف نوعهای Node.js؛ در TypeScript 7 پیشفرض این گزینه خالی است |
| سه گزینهی آخر | سازگاری کد با اجرای مستقیم در Node.js (پایینتر توضیح میدهیم) |
حالا package.json را باز کنید. نسخههای جدید npm در npm init -y مقدار "type": "commonjs" را مینویسند؛ آن را به "module" تغییر دهید و این اسکریپتها را اضافه کنید:
{
"type": "module",
"scripts": {
"build": "tsc",
"check": "tsc --noEmit",
"start": "node dist/index.js"
}
}
ویرایشگر؛ نیمی از ارزش TypeScript
بخش بزرگی از سود TypeScript در ویرایشگر دیده میشود، نه در ترمینال. ویرایشگرهایی مثل VS Code از همان سرویس زبانی TypeScript استفاده میکنند و بدون افزونهی اضافه، خطاها را زیر کد خط میکشند، فیلدهای هر شیء را در تکمیل خودکار پیشنهاد میدهند و تغییر نام یک تابع را در همهی فایلها با خیال راحت انجام میدهند. پس از ساختن tsconfig.json، پوشهی پروژه را در ویرایشگر باز کنید تا همین تنظیمات روی تحلیل کد هم اعمال شود.
انواع پایه؛ اولین قدم آموزش تایپاسکریپت
فایل src/basics.ts را بسازید:
let username = "sara"; // inferred as string
let age: number = 21;
let isActive: boolean = true;
const scores: number[] = [18, 19.5, 20];
const point: [number, number] = [35.7, 51.4];
function greet(name: string, title?: string): string {
return title ? `Hello, ${title} ${name}` : `Hello, ${name}`;
}
function parseInput(input: unknown): number {
if (typeof input === "number") return input;
if (typeof input === "string") return Number.parseFloat(input);
throw new Error("Unsupported input");
}
console.log(greet(username), age, isActive, scores, point);
console.log(parseInput("3.5"));
چند نکتهی مهم در همین چند خط هست:
- استنتاج نوع (Type Inference): برای
usernameنوعی ننوشتیم، اما TypeScript از مقدار اولیه میفهمد کهstringاست. لازم نیست همهجا نوع بنویسید؛ پارامترهای توابع و مرزهای ماژول مهمترین جاها هستند. - پارامتر اختیاری: علامت
?درtitle?یعنی این پارامتر میتواند ارسال نشود. - تاپل (Tuple): نوع
[number, number]آرایهای با دقیقاً دو عدد است. unknownبهجایany: نوعanyبررسی نوع را خاموش میکند.unknownمیگوید «نمیدانم چیست» و شما را مجبور میکند پیش از استفاده، نوع را بررسی کنید.
type و interface
برای توصیف شکل اشیا دو ابزار دارید:
interface User {
readonly id: number;
name: string;
email?: string;
}
type Point = { x: number; y: number };
readonly جلوی تغییر فیلد را میگیرد و ? فیلد را اختیاری میکند. TypeScript نوعها را ساختاری (Structural) مقایسه میکند: هر شیئی که فیلدهای x و y از نوع عدد داشته باشد، یک Point است؛ لازم نیست صریحاً اعلام کند.
Union، Narrowing و Generic
نوع اجتماع (Union) یعنی مقداری که میتواند یکی از چند نوع باشد. وقتی هر عضو Union یک فیلد مشترک با مقدار ثابت داشته باشد، TypeScript بعد از بررسی آن فیلد نوع دقیق را تشخیص میدهد؛ به این کار محدودسازی (Narrowing) میگویند:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rect":
return shape.width * shape.height;
}
}
داخل case "circle" فقط radius در دسترس است و اگر به shape.width دست بزنید، کامپایلر خطا میدهد.
Generic تابعی میسازد که با هر نوعی کار میکند، بدون اینکه اطلاعات نوع از دست برود:
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([10, 20]); // number | undefined
const s = first(["a", "b"]); // string | undefined
پروژهی عملی: مدیر وظایف تایپشده
حالا همهی این مفاهیم را در یک پروژهی کوچک کنار هم میگذاریم. فایل src/types.ts:
export type Priority = "low" | "medium" | "high";
export interface Task {
readonly id: number;
title: string;
done: boolean;
priority: Priority;
dueDate?: string;
}
export type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
نوع Result بهجای پرتاب خطا، موفقیت یا شکست را صریحاً برمیگرداند. فایل src/tasks.ts:
import type { Priority, Result, Task } from "./types.ts";
const tasks: Task[] = [];
let nextId = 1;
export function addTask(title: string, priority: Priority = "medium"): Result<Task> {
const trimmed = title.trim();
if (trimmed.length === 0) {
return { ok: false, error: "Title must not be empty" };
}
const task: Task = { id: nextId++, title: trimmed, done: false, priority };
tasks.push(task);
return { ok: true, value: task };
}
export function completeTask(id: number): Result<Task> {
const task = tasks.find((t) => t.id === id);
if (!task) {
return { ok: false, error: `Task ${id} not found` };
}
task.done = true;
return { ok: true, value: task };
}
export function filterBy<T, K extends keyof T>(items: T[], key: K, value: T[K]): T[] {
return items.filter((item) => item[key] === value);
}
export function allTasks(): readonly Task[] {
return tasks;
}
تابع filterBy یک Generic پیشرفتهتر است: K extends keyof T یعنی key فقط میتواند نام یکی از فیلدهای واقعی T باشد و value هم باید همنوع همان فیلد باشد. در نهایت src/index.ts:
import { addTask, allTasks, completeTask, filterBy } from "./tasks.ts";
addTask("Learn TypeScript basics", "high");
addTask("Write unit tests");
const result = addTask(" ");
if (!result.ok) {
console.log("Error:", result.error);
}
const done = completeTask(1);
if (done.ok) {
console.log(`Completed: ${done.value.title}`);
}
const pending = filterBy([...allTasks()], "done", false);
console.log("Pending tasks:", pending.map((t) => t.title));
پروژه را بسازید و اجرا کنید:
npm run build
npm start
خروجی:
Error: Title must not be empty
Completed: Learn TypeScript basics
Pending tasks: [ 'Write unit tests' ]
ببینید TypeScript چه خطاهایی را میگیرد
این دو خط را موقتاً به src/index.ts اضافه کنید و npm run check را اجرا کنید:
addTask("Deploy", "urgent");
filterBy([...allTasks()], "status", "open");
کامپایلر هر دو را رد میکند: "urgent" جزو مقادیر مجاز Priority نیست و Task فیلدی به نام status ندارد. در جاوااسکریپت خالص، هر دو اشتباه بیصدا از کنارتان میگذشتند.
اجرای مستقیم فایل ts با Node.js
در نسخههای جدید Node.js (از ۲۲٫۱۸ و ۲۳٫۶ به بعد) میتوانید فایل TypeScript را بدون کامپایل اجرا کنید:
node src/index.ts
Node.js در این حالت فقط نوعها را حذف میکند و آنها را بررسی نمیکند. به همین دلیل سه گزینهی آخر tsconfig.json را اضافه کردیم:
verbatimModuleSyntaxشما را مجبور میکند واردات فقطنوعی را باimport typeبنویسید، چون Node.js نمیتواند تشخیص دهد کدام واردات فقط نوع است.erasableSyntaxOnlyقابلیتهایی مثلenumرا که کد اجرایی تولید میکنند ممنوع میکند؛ Node.js آنها را اجرا نمیکند.rewriteRelativeImportExtensionsاجازه میدهد در importها پسوند.tsبنویسید وtscهنگام ساخت خروجی، آن را به.jsتبدیل کند.
روال پیشنهادی: در توسعه با node src/index.ts سریع اجرا کنید و پیش از commit یا در CI دستور npm run check را بزنید تا خطاهای نوع هم بررسی شوند. اگر هنوز با Git راحت نیستید، آموزش Git و GitHub از صفر را ببینید.
مهاجرت تدریجی یک پروژهی جاوااسکریپتی
لازم نیست همهی پروژه را یکجا بازنویسی کنید:
- گزینههای
"allowJs": trueو"checkJs": trueرا فعال کنید تا TypeScript فایلهای.jsرا هم بررسی کند. - برای فایلهایی که هنوز تبدیل نشدهاند، نوعها را با توضیحات JSDoc مثل
/** @param {string} name */اعلام کنید. - فایلها را یکییکی از
.jsبه.tsتغییر نام دهید؛ از فایلهای کموابستگی مثل توابع کمکی شروع کنید. - اگر
strictدر ابتدا خطاهای زیادی میدهد، آن را موقتاً خاموش کنید و بعد از تبدیل همهی فایلها دوباره روشنش کنید.
فریمورکهای مدرن هم TypeScript را از ابتدا پشتیبانی میکنند؛ مثلاً در آموزش Next.js App Router همهی کدها TypeScript هستند. و وقتی داده را از یک API میگیرید، تعریف نوع برای پاسخ آن بیشترین سود را دارد؛ دربارهی انواع API در راهنمای API و مقایسهی REST، GraphQL و gRPC نوشتهایم.
اشتباههای رایج تازهکارها
| اشتباه | راه درست |
|---|---|
استفاده از any برای ساکت کردن خطا |
از unknown و بررسی نوع استفاده کنید |
تبدیل نوع با as بدون بررسی |
دادهی ورودی را در زمان اجرا اعتبارسنجی کنید |
استفادهی زیاد از عملگر ! |
حالت null و undefined را صریحاً مدیریت کنید |
| نوشتن نوع برای هر متغیر | به استنتاج نوع اعتماد کنید و فقط مرزها را تایپ کنید |
| اعتماد به نوع دادهی دریافتی از شبکه | نوعها در زمان اجرا وجود ندارند؛ پاسخ API را اعتبارسنجی کنید |
نکتهی آخر مهمترین نکته است: TypeScript فقط کدی را که خودتان نوشتهاید بررسی میکند. دادهای که از شبکه، فرم یا فایل میآید، هر شکلی ممکن است داشته باشد.
جمعبندی
در این آموزش تایپاسکریپت یک پروژه را از صفر راه انداختید، tsconfig.json را شناختید، با انواع پایه، interface، Union و Generic کار کردید و یک مدیر وظایف ساختید که هم با tsc کامپایل میشود و هم مستقیم با Node.js اجرا میشود. قدم بعدی این است که یکی از فایلهای پروژهی جاوااسکریپتی خودتان را به TypeScript تبدیل کنید و ببینید کامپایلر چه چیزهایی را پیدا میکند که تا حالا از چشمتان پنهان مانده بود.




دیدگاهها
دیدگاهها پس از بررسی تحریریه منتشر میشوند. توهین، تبلیغ و لینکهای بیربط حذف میشوند؛ نقد فنی و مستند همیشه خوشآمد است.
هنوز کسی چیزی ننوشته. اولین دیدگاه را شما ثبت کنید.