۱۴۰۵ مهر ۱۵, چهارشنبه
دلار آمریکا۲۶۲٬۲۰۵▼ ۲٫۵۲٪یورو۲۹۴٬۸۳۰▼ ۲٫۸۴٪درهم امارات۷۱٬۷۷۶▼ ۲٫۲۹٪سکه امامی۲۶۷٬۳۸۵٬۰۰۰▼ ۱٫۶۹٪طلای ۱۸ عیار (گرم)۲۶٬۲۶۷٬۶۰۰▼ ۱٫۷۹٪انس طلا۴٬۱۱۶ $▼ ۱٫۱۴٪تتر۲۶۲٬۶۷۸▼ ۲٫۴۱٪بیت‌کوین۸۳٬۷۰۹ $▼ ۲٫۳٪اتریوم۲٬۵۸۴ $▼ ۴٫۲۶٪سولانا۱۱۷٫۵۵ $▼ ۳٫۰۱٪اپل۳۳۳٫۶۳ $▲ ۰٫۲۲٪انویدیا۲۳۹٫۲۴ $▲ ۰٫۱۴٪مایکروسافت۵۲۹٫۳۰ $▲ ۰٫۷۸٪آلفابت (گوگل)۳۴۷٫۶۸ $▲ ۰٫۳۵٪تسلا۳۸۰٫۶۸ $▲ ۰٫۵۲٪شاخص نزدک۲۷٬۶۰۰ $▲ ۰٫۴۵٪
نرخ ارز

آموزش تایپ‌اسکریپت (TypeScript) برای برنامه‌نویس جاوااسکریپت

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

کاور گرافیکی DevNA با عنوان «TypeScript»: برای برنامه‌نویس‌های جاوااسکریپت
کاور گرافیکی DevNA با عنوان «TypeScript»: برای برنامه‌نویس‌های جاوااسکریپت
فهرست مطالب
  1. TypeScript چیست و چه مشکلی را حل می‌کند؟
  2. نصب و راه‌اندازی پروژه
  3. انواع پایه؛ اولین قدم آموزش تایپ‌اسکریپت
  4. Union، Narrowing و Generic
  5. پروژه‌ی عملی: مدیر وظایف تایپ‌شده
  6. اجرای مستقیم فایل ts با Node.js
  7. مهاجرت تدریجی یک پروژه‌ی جاوااسکریپتی
  8. اشتباه‌های رایج تازه‌کارها
  9. جمع‌بندی

آموزش تایپ‌اسکریپت (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 از صفر را ببینید.

مهاجرت تدریجی یک پروژه‌ی جاوااسکریپتی

لازم نیست همه‌ی پروژه را یک‌جا بازنویسی کنید:

  1. گزینه‌های "allowJs": true و "checkJs": true را فعال کنید تا TypeScript فایل‌های .js را هم بررسی کند.
  2. برای فایل‌هایی که هنوز تبدیل نشده‌اند، نوع‌ها را با توضیحات JSDoc مثل /** @param {string} name */ اعلام کنید.
  3. فایل‌ها را یکی‌یکی از .js به .ts تغییر نام دهید؛ از فایل‌های کم‌وابستگی مثل توابع کمکی شروع کنید.
  4. اگر 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 تبدیل کنید و ببینید کامپایلر چه چیزهایی را پیدا می‌کند که تا حالا از چشمتان پنهان مانده بود.

$ devna rate --article

—
هنوز رأیی ثبت نشده
  1. ۵۰
  2. ۴۰
  3. ۳۰
  4. ۲۰
  5. ۱۰

این مطلب چقدر به کارتان آمد؟

روی ستاره‌ها بزنید

واکنش شما

اولین نفری باشید که واکنش نشان می‌دهد.

پرسش‌های پرتکرار

آیا برای یادگیری تایپ‌اسکریپت باید اول جاوااسکریپت بلد باشم؟

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

آیا TypeScript سرعت اجرای برنامه را بیشتر می‌کند؟

نه. انواع داده هنگام کامپایل حذف می‌شوند و کد اجرایی همان جاوااسکریپت است. سود TypeScript جای دیگری است: کشف زودهنگام خطا، تکمیل خودکار بهتر و بازآرایی امن‌تر کد.

در TypeScript از type استفاده کنیم یا interface؟

برای توصیف شکل اشیا هر دو کار می‌کنند. interface را می‌توان گسترش داد و ادغام کرد و type برای Union، ترکیب انواع و انواع پیچیده انعطاف بیشتری دارد. مهم‌تر از انتخاب، یکدست بودن در پروژه است.

آیا می‌توان فایل TypeScript را بدون کامپایل با Node.js اجرا کرد؟

در نسخه‌های جدید Node.js بله؛ Node.js انواع را حذف و کد را اجرا می‌کند، اما نوع‌ها را بررسی نمی‌کند. برای بررسی خطاها همچنان باید tsc را اجرا کنید.

منابع

  1. TypeScript for JavaScript Programmers — TypeScript Handbook
  2. Announcing TypeScript 7.0 — Microsoft DevBlogs
  3. Download TypeScript — typescriptlang.org
  4. Modules: TypeScript — Node.js Documentation

خطایی در این مطلب دیدید؟ به تحریریه گزارش دهید؛ اصلاحیه‌ها طبق اصول تحریریه ثبت می‌شوند.

دیدگاه‌ها

۰/۲٬۰۰۰

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

$ comments --count۰

هنوز کسی چیزی ننوشته. اولین دیدگاه را شما ثبت کنید.

مطالب مرتبط