در بازبینی یک پروژه‌ی TypeScript که تیم دیگری نوشته بود، با موردی عجیب روبرو شدم: کل کدبیس با strict mode کامپایل می‌شد، هیچ خطای کامپایلی وجود نداشت، اما کاربران همچنان باگ گزارش می‌کردند. بعد از دو روز بررسی، فهمیدم که تیم از any به‌عنوان یک راه فرار استفاده کرده بود. در هر نقطه‌ای که تایپ واقعی پیچیده می‌شد، any به‌عنوان چتر نجات ظاهر می‌شد و کدبیس، از نظر ظاهری TS بود اما از نظر معنایی، یک JS با تزئینات تایپ بود. آن تجربه، نگاه من به تایپ‌ها در TypeScript را برای همیشه تغییر داد: تایپ‌ها فقط یک لایه‌ی رویی نیستند؛ یک زبان طراحی هستند که اگر درست به‌کار نروند، به‌جای محافظت، یک توهم امنیت می‌سازند.

تایپ‌ها در TS قلب این زبان هستند. TypeScript یک سیستم تایپ بسیار غنی دارد که از تایپ‌های ساده‌ی اولیه شروع می‌شود و تا Level-Level Programming ادامه پیدا می‌کند. اگر در مسیر آموزش تایپ اسکریپت از صفر هستید و مقالات تفاوت تایپ اسکریپت و جاوااسکریپت و اینترفیس در تایپ اسکریپت را خوانده‌اید، این نوشته یک لایه عمیق‌تر از همان مباحث را باز می‌کند.

چرا تایپ‌ها در TS از آنچه فکر می‌کنید پیچیده‌ترند؟

در نگاه اول، تایپ‌های TS ساده به‌نظر می‌رسند: string، number، boolean. اما سیستم تایپ TypeScript بسیار عمیق‌تر از این سطح ابتدایی است. سه دلیل که تایپ‌ها را در پروژه‌های واقعی جدی می‌گیرم:

  • تایپ‌ها، مستنداتِ زنده هستند: وقتی یک تابع با امضای دقیق نوشته می‌شود، خودش را مستند می‌کند. اگر تایپ‌ها مبهم یا نادرست باشند، این لایه‌ی مستندسازی هم از بین می‌رود. مطالعه‌ی این لایه در تفاوت تایپ اسکریپت و جاوااسکریپت آمده است.
  • تایپ‌ها، خطاها را زودتر می‌گیرند: هدف اصلی تایپ‌ها، جلوگیری از خطاهای زمان اجرا است. اگر تایپ‌ها را درست به‌کار نبرید (مثلاً با any همه‌جا)، این هدف محقق نمی‌شود.
  • تایپ‌ها، تصمیم‌های معماری را شکل می‌دهند: یک تایپ که خوب طراحی شده باشد، مسیر رشد کد را روشن می‌کند. یک تایپ که بد طراحی شده باشد، در پروژه‌های بزرگ به یک بدهی فنی تبدیل می‌شود.

در پروژه‌ای که یک تیم استارتاپی سه ماه روی TS کار کرده بود، متوجه شدم که در فایل تایپ‌های مشترک (types.ts)، بیش از ۸۰ interface با نام‌های مشابه مثل User، UserData، UserInfo، UserPayload وجود داشت که اکثراً تفاوت معناداری نداشتند. این تیم TS را می‌شناخت اما تایپ‌ها را طراحی نکرده بود. تفاوت بین «TS بلد بودن» و «تایپ‌ها را طراحی کردن»، همان تفاوت بین یک سایت معمولی و یک سایت حرفه‌ای است.

تایپ‌ها در TypeScript، مثل نقشه‌ی معماری یک ساختمان هستند؛ بدون آن‌ها، بنا کردن طبقه‌ی بعدی، کارِ حدس‌زدن است نه مهندسی.

تایپ‌های اولیه و مفاهیم پایه

شروع کار با TS، معمولاً با همان سه تایپ اولیه‌ی جاوااسکریپت است که در TS هم وجود دارند:

let name: string = "Ali";
let age: number = 30;
let isAdmin: boolean = true;
let tags: string[] = ["wordpress", "seo"];
let scores: number[] = [85, 92, 78];
let matrix: number[][] = [[1, 2], [3, 4]];

اما در پروژه‌های واقعی، تایپ‌های پایه به‌ندرت به‌تنهایی استفاده می‌شوند. سه نکته‌ی مهم که در پروژه‌ها به‌کارم آمده:

۱. Type Inference را جدی بگیرید

در بسیاری از موارد، نیازی به نوشتن صریح تایپ نیست. TS خودش استنتاج می‌کند:

let name = "Ali";        // TS خودش می‌فهمد string است
let age = 30;            // TS خودش می‌فهمد number است
let isReady = true;      // TS خودش می‌فهمد boolean است

قاعده‌ی من: اگر مقدار اولیه واضح است، تایپ را صریح ننویسید. اگر مقدار اولیه پیچیده است (مثل تابع یا شیء)، تایپ را صریح بنویسید. این تصمیم کوچک، حجم کد را در پروژه‌های بزرگ به‌طور محسوس کاهش می‌دهد.

۲. تفاوت let و const در استنتاج تایپ

این یکی از ظریف‌ترین نکات TS است:

let role = "admin";      // role: string
const role = "admin";    // role: "admin" (literal type!)

چرا؟ چون const تضمین می‌کند مقدار تغییر نمی‌کند، TS می‌تواند تایپ را دقیق‌تر استنتاج کند. این تفاوت در پروژه‌های واقعی تفاوت محسوسی در دقت تایپ‌چکینگ می‌سازد.

۳. تایپ‌های اولیه به‌عنوان مقادیر پیش‌فرض

در پروژه‌ها، معمولاً تایپ‌های پیچیده‌تر مثل User، Product و Order را با interface یا type تعریف می‌کنیم و از تایپ‌های اولیه درون آن‌ها استفاده می‌کنیم. برای مشاهده‌ی این الگو، اینترفیس در تایپ اسکریپت را ببینید.

any، unknown و never: سه تایپ که همه اشتباه می‌گیرند

این سه تایپ، بیشترین سوءاستفاده و بیشترین سردرگمی را در پروژه‌های TS می‌سازند. تفاوت بنیادی آن‌ها را در یک جدول جمع می‌کنم:

تایپمعنیکاربرد صحیحکاربرد غلط
anyهر چیزی، بدون بررسی تایپتقریباً هیچ‌وقتراه فرار از تایپ‌چکینگ
unknownهر چیزی، اما باید قبل از استفاده بررسی شودپاسخ‌های API، ورودی‌های نامشخصجایگزین امن any
neverمقداری که هرگز اتفاق نمی‌افتدتوابعی که هرگز برنمی‌گردند یا همیشه خطا می‌دهندبه‌ندرت به‌تنهایی

any: راه فراری که پروژه را به JS برمی‌گرداند

function process(data: any) {
  return data.something.deep;  // بدون هیچ خطای کامپایلی!
}

در این تابع، TS هیچ محافظتی نمی‌کند. اگر data در واقع یک رشته باشد، در زمان اجرا خطای پنهانی رخ می‌دهد که نه در زمان کامپایل و نه در تست‌های ساده مشخص نمی‌شود. در پروژه‌ای که بیش از ۲۰۰ مورد any داشت، بعد از پاکسازی و جایگزینی، هفت باگ واقعی کشف شد که در runtime رخ می‌دادند اما در تست‌های معمولی گرفته نمی‌شدند.

unknown: جایگزین امن

function process(data: unknown) {
  if (typeof data === "string") {
    return data.toUpperCase();  // امن است
  }
  // TS اجازه نمی‌دهد بدون بررسی، از data استفاده کنید
}

unknown شما را مجبور به بررسی تایپ می‌کند. این یک لایه‌ی اضافی از محافظت است که در پروژه‌های جدی باید به‌عنوان جایگزین any استفاده شود.

never: ابزار Type-Level

function throwError(message: string): never {
  throw new Error(message);
}

never برای توابعی است که هرگز برنمی‌گردند. کاربرد اصلی‌اش در Exhaustive Checking است — یعنی اطمینان از این‌که تمام حالت‌های یک union بررسی شده‌اند. مطالعه‌ی این الگو در Literal Types در همین مقاله می‌آید.

Literal Types: از رشته تا عدد و Boolean

Literal Types یکی از پرقدرت‌ترین امکانات TS هستند که در پروژه‌های واقعی کم استفاده می‌شوند. به‌جای تعریف یک تایپ کلی، می‌توانید مقادیر مشخصی را تعیین کنید:

type Status = "pending" | "approved" | "rejected";
type Direction = "up" | "down" | "left" | "right";
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
type Answer = true | false;

مزیت این رویکرد: اگر جایی در کد مقدار اشتباهی بنویسید، TS خطا می‌دهد:

let orderStatus: Status = "pending";   // OK
let orderStatus: Status = "unknown";   // خطای کامپایل!

در پروژه‌ای که یک سیستم پرداخت داشتیم، این الگو یک برد قابل توجه بود. قبل از TS، مقادیر status به‌عنوان string ساده نگهداری می‌شدند و برنامه‌نویس‌ها گاهی به‌اشتباه "approved" را "aprove" یا "Approved" تایپ می‌کردند. بعد از استفاده از Literal Types، این خطاها در زمان کامپایل گرفته شدند و تعداد باگ‌های مربوط به مقادیر رشتی به‌طور محسوس کاهش پیدا کرد.

Literal Types، تفاوت بین یک زبان با تایپ پویا و یک زبان با تایپ‌چکینگ معنادار است؛ در اولی هر رشته‌ای مجاز است، در دومی فقط مقادیر مورد انتظار.

Union و Intersection: ترکیب تایپ‌ها

Union و Intersection دو ابزار پایه برای ترکیب تایپ‌ها هستند. تفاوت‌شان ظریف اما حیاتی است:

Union (یا): |

type ID = string | number;

function findUser(id: ID) {
  // id می‌تواند string یا number باشد
}

Union می‌گوید «یا این یا آن». یک مقدار می‌تواند هر یک از انواع تعریف‌شده باشد.

Intersection (و): &

type Person = { name: string };
type Employee = { company: string };
type EmployedPerson = Person & Employee;

const worker: EmployedPerson = {
  name: "Ali",
  company: "Acme"
};

Intersection می‌گوید «هر دو». یک مقدار باید تمام ویژگی‌های هر دو تایپ را داشته باشد.

کاربرد واقعی: Union با Discriminator

یکی از پرکاربردترین الگوهای TS، Union با یک فیلد مشترک (discriminator) است:

type Result =
  | { status: "success"; data: string }
  | { status: "error"; message: string }
  | { status: "loading" };

function render(result: Result) {
  switch (result.status) {
    case "success":
      return result.data;      // TS می‌داند data وجود دارد
    case "error":
      return result.message;   // TS می‌داند message وجود دارد
    case "loading":
      return "Loading...";     // TS می‌داند فقط status وجود دارد
  }
}

این الگو، به‌عنوان Discriminated Union شناخته می‌شود و در مدیریت state برنامه‌های React و Vue جایگاه محکمی دارد. مطالعه‌ی موازی این لایه در تایپ اسکریپت با React آمده است.

Tuple در برابر Array: تفاوت بنیادی

در JS، آرایه‌ها می‌توانند هر چیزی را شامل شوند. در TS، Array و Tuple دو تایپ متفاوت هستند:

// Array: تعداد نامشخص، تایپ یکسان
let numbers: number[] = [1, 2, 3, 4, 5];

// Tuple: تعداد مشخص، تایپ‌های متفاوت در هر موقعیت
let point: [number, number] = [10, 20];
let user: [string, number, boolean] = ["Ali", 30, true];

Tuple زمانی استفاده می‌شود که تعداد و ترتیب مقادیر مشخص است. کاربردهای واقعی:

  • مختصات (Coordinates): [number, number] برای x و y.
  • مقادیر بازگشتی چندگانه: [User, string] برای user و error message.
  • Pairs کلید-مقدار: [string, number][] برای entries.
  • عناصر با معنا در موقعیت: مثل [status, data, error] در یک API response.

نکته‌ی مهم: Tuple در runtime، به‌عنوان آرایه معمولی رفتار می‌کند. تایپ Tuple فقط در زمان کامپایل معنا دارد. مطالعه‌ی بیشتر در آموزش تایپ اسکریپت از صفر.

Enum و جایگاهش در TS مدرن

Enum یکی از تایپ‌هایی است که در TS وجود دارد اما در JS ندارد. دو نوع Enum داریم:

// Numeric Enum
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

// String Enum
enum Status {
  Pending = "PENDING",
  Approved = "APPROVED",
  Rejected = "REJECTED"
}

اما در سال‌های اخیر، جامعه‌ی TS به سمت جایگزین‌های Enum رفته است. سه دلیل:

  1. Enum در runtime واقعی است: Enum کامپایل می‌شود به یک شیء JS که در runtime وجود دارد. این حجم اضافه تولید می‌کند.
  2. Tree-shaking ضعیف: bundlerها نمی‌توانند Enum را به‌طور کامل حذف کنند اگر استفاده نشده باشد.
  3. جایگزین‌های بهتر: Literal Types با Union، همان کار را بدون حجم اضافه انجام می‌دهند:
// جایگزین مدرن Enum
type Status = "PENDING" | "APPROVED" | "REJECTED";
const Status = {
  Pending: "PENDING" as const,
  Approved: "APPROVED" as const,
  Rejected: "REJECTED" as const
};

توصیه‌ی من در پروژه‌های جدید: اگر به Enum نیاز دارید، از Union Type با Literal Values استفاده کنید. اگر مجبورید Enum داشته باشید (مثلاً برای سازگاری با کتابخانه)، String Enum انتخاب بهتری از Numeric Enum است چون در debug خوانا‌تر است. مطالعه‌ی موازی این لایه در آموزش تایپ اسکریپت از صفر آمده است.

Type Assertion و Type Guard: تفاوت حیاتی

Type Assertion و Type Guard دو ابزار متفاوت برای کار با تایپ‌ها هستند که اکثر توسعه‌دهنده‌ها تفاوت‌شان را نمی‌دانند:

Type Assertion (ادعای تایپ)

const value: unknown = "hello";
const length = (value as string).length;  // ادعا: value یک string است

این یک ادعاست، نه بررسی. اگر value در واقع رشته نباشد، در زمان اجرا خطا رخ می‌دهد اما TS آن را نمی‌گیرد.

Type Guard (نگهبان تایپ)

function isString(value: unknown): value is string {
  return typeof value === "string";
}

const value: unknown = "hello";
if (isString(value)) {
  const length = value.length;  // امن است، TS می‌داند value یک string است
}

Type Guard یک بررسی واقعی در runtime است. این الگو، محافظت واقعی می‌سازد نه یک ادعای صرف.

الگوهای رایج Type Guard

  • typeof value === "string" برای تایپ‌های اولیه.
  • Array.isArray(value) برای تشخیص آرایه.
  • "property" in obj برای تشخیص خاصیت.
  • value instanceof SomeClass برای تشخیص کلاس.
  • توابع سفارشی با return type value is Type برای تایپ‌های پیچیده.

در پروژه‌ای که یک API با پاسخ‌های متنوع داشتیم، Type Guard این امکان را می‌داد که پس از هر درخواست، پاسخ را در runtime بررسی کنیم و فقط اگر مطابق انتظار بود، به‌عنوان تایپ مشخص به منطق برنامه بفرستیم. این ترکیب Type Assertion با Type Guard، خطاهای پاسخ API را در همان نقطه‌ی دریافت، به‌جای چند لایه بعد، می‌گیرد.

Utility Types: Pick، Omit، Partial و دوستان

TS مجموعه‌ای از Utility Types آماده دارد که کار با تایپ‌ها را بسیار ساده‌تر می‌کند. جدول زیر مرجع سریع پرکاربردترین آن‌ها است:

Utilityکاربردمثال
Partial<T>همه‌ی خاصیت‌ها اختیاریPartial<User>
Required<T>همه‌ی خاصیت‌ها اجباریRequired<User>
Readonly<T>همه‌ی خاصیت‌ها فقط‌خواندنیReadonly<User>
Pick<T, K>فقط خاصیت‌های مشخصPick<User, "id" | "name">
Omit<T, K>حذف خاصیت‌های مشخصOmit<User, "password">
Record<K, V>شیء با کلیدها و مقادیر مشخصRecord<string, number>
Exclude<T, U>حذف تایپ‌های مشخص از UnionExclude<Status, "pending">
Extract<T, U>نگه‌داشتن تایپ‌های مشخص از UnionExtract<Status, "approved" | "rejected">
NonNullable<T>حذف null و undefinedNonNullable<string | null>
ReturnType<F>تایپ بازگشتی یک تابعReturnType<typeof fetchUser>
Parameters<F>تایپ پارامترهای یک تابعParameters<typeof fetchUser>

یک مثال کاربردی از Omit که در پروژه‌ای به‌کار بردم:

interface User {
  id: number;
  name: string;
  email: string;
  password: string;  // نباید به کلاینت فرستاده شود
}

type PublicUser = Omit<User, "password">;
// PublicUser فقط id، name و email دارد

این الگو یکی از تمیزترین راه‌های جداسازی داده‌ی حساس از داده‌ی عمومی در یک پروژه‌ی TS است. مطالعه‌ی کاربرد این الگو در پروژه‌های واقعی در آموزش تایپ اسکریپت از صفر.

الگوهای واقعی از پروژه‌ها

سه الگویی که در پروژه‌های خودم بیشترین استفاده را داشته‌اند:

  1. Discriminated Unions برای State Management: در یک اپلیکیشن React که حالت‌های مختلف لود، خطا و موفقیت داشت، استفاده از Union با Discriminator باعث شد تمام حالت‌های ممکن در یک تایپ مشخص خلاصه شوند. کامپایلر به‌طور خودکار تشخیص می‌داد که کدام حالت امکان‌پذیر است.
  2. Utility Types برای API Layer: در پروژه‌ای که صدها endpoint داشت، تایپ پایه‌ی پاسخ API را تعریف کردیم و با Pick و Omit، نسخه‌های مختلف برای هر endpoint ساختیم. این کار حجم کد تایپ‌ها را حدود ۶۰٪ کاهش داد.
  3. Type Guards برای پردازش ورودی: در یک API backend که با داده‌های پیچیده از سمت کلاینت کار می‌کرد، Type Guards در همه‌ی ورودی‌ها اعمال می‌شد تا قبل از رسیدن به منطق دامنه، اطمینان حاصل شود که داده‌ها معتبر هستند. این الگو، خطاهای ورودی را از چند لایه بعد به همان نقطه‌ی ورود می‌آورد.

برای مطالعه‌ی الگوهای عملی بیشتر، اینترفیس در تایپ اسکریپت، Generics در تایپ اسکریپت و تایپ اسکریپت با React را ببینید.

اشتباهاتی که در پروژه‌ها دیدم

  • استفاده‌ی بی‌رویه از any: شایع‌ترین اشتباه. هر مورد any، یک نقطه‌ی خاموش تایپ‌چکینگ است. راه‌حل: جایگزینی با unknown یا تایپ دقیق.
  • Type Assertion به‌جای Type Guard: as SomeType فقط یک ادعاست و در runtime هیچ محافظتی نمی‌کند. Type Guard با value is Type رفتار واقعی دارد.
  • مقایسه‌ی تایپ‌ها با === در کامپایلر: بعضی توسعه‌دهنده‌ها از typeof value === "string" استفاده می‌کنند اما Type Guard را از آن نمی‌سازند. TS این را فقط به‌عنوان یک شرط ساده می‌بیند و تایپ را باریک نمی‌کند.
  • نادیده گرفتن strictNullChecks: اگر این تنظیم خاموش باشد، null و undefined بدون هشدار عبور می‌کنند و یکی از مؤثرترین لایه‌های محافظت TS غیرفعال می‌شود.
  • استفاده از Numeric Enum: Numeric Enum در debug غیرخوانا است و در runtime حجم اضافه تولید می‌کند. String Enum یا Union با Literal Values انتخاب بهتری هستند.
  • تعریف تایپ‌های تکراری: در پروژه‌ای که بازبینی کردم، چهار interface با نام‌های User، UserData، UserInfo، UserPayload وجود داشت که همه یک ساختار داشتند. مدیریت این تکرار، در بلندمدت به کابوس تبدیل می‌شود.
  • نوشتن Type برای همه‌چیز: در بعضی پروژه‌ها، توسعه‌دهنده‌ها حتی برای متغیرهای محلی ساده هم تایپ صریح می‌نویسند. TS یک سیستم استنتاج قوی دارد؛ از آن استفاده کنید.

بخشی از این اشتباهات در خطاهای رایج تایپ اسکریپت و اشتباهات رایج توسعه هم آمده است.

لایه‌ای پایین‌تر از سینتکس تایپ

اینجا وارد لایه‌ای می‌شوم که در پروژه‌های معمولی به آن نگاه نمی‌شود اما برای مهندسان پلتفرم و توسعه‌دهنده‌های ارشد اهمیت دارد. آنچه سیستم تایپ TS با کد شما می‌کند، در پنج مفهوم خلاصه می‌شود:

  1. Structural Typing و پیامدهای آن: TS از سیستم تایپ ساختاری (structural) استفاده می‌کند. یعنی تایپ‌ها بر اساس ساختارشان مقایسه می‌شوند، نه نامشان. یک interface به نام User و یک type به نام Person با ساختار یکسان، از نظر TS معادل هستند. این ویژگی، هم انعطاف بالایی می‌دهد و هم می‌تواند در مواردی باعث شود کامپایلر خطایی نگیرد که از نظر معنایی غلط است. در پروژه‌های بزرگ با کتابخانه‌های مختلف، فهم این تفاوت، از خطاهای ظریف جلوگیری می‌کند. مطالعه‌ی موازی این لایه در تفاوت تایپ اسکریپت و جاوااسکریپت آمده است.
  2. Type Erasure و هزینه‌ی زمان اجرا: تمام اطلاعات تایپ در زمان کامپایل حذف می‌شود. یعنی خروجی نهایی JS، حجم تایپ‌ها را ندارد و هیچ هزینه‌ی کارایی در مرورگر ایجاد نمی‌شود. اما نکته‌ی ظریف اینجاست: اگر بیش‌ازحد از as (Type Assertion) استفاده کنید، این ویژگی به‌دلیل حذف بازرسی‌ها، به یک بدهی امنیتی تبدیل می‌شود. مطالعه‌ی موازی این لایه با کارایی در بهینه سازی جاوااسکریپت آمده است.
  3. Declaration Merging و Interaction با کتابخانه‌ها: در TS، interface‌ها می‌توانند در چند نقطه ادغام شوند. این ویژگی در کتابخانه‌های عمومی برای گسترش تایپ‌ها استفاده می‌شود اما در پروژه‌های داخلی می‌تواند منبع سردرگمی باشد اگر بدون دقت اعمال شود. قاعده‌ی من: interface را برای تایپ‌هایی استفاده کنید که از بیرون گسترش می‌یابند، و type را برای تایپ‌های داخلی. مطالعه‌ی این لایه در اینترفیس در تایپ اسکریپت آمده است.
  4. Type Narrowing و Control Flow Analysis: TS کامپایلر از یک تحلیل جریان کنترل (Control Flow Analysis) برای باریک کردن تایپ‌ها استفاده می‌کند. یعنی وقتی می‌نویسید if (typeof x === "string")، در داخل آن بلوک، TS می‌داند x از تایپ string است. این تحلیل در Type Guards، Discriminated Unions و حتی پس از if (x !== null) اعمال می‌شود. مطالعه‌ی موازی این لایه در مفاهیم پیشرفته جاوااسکریپت آمده است.
  5. Type-Level Programming و کتابخانه‌های عمومی: در کتابخانه‌های عمومی مثل React، Prisma و tRPC، از امکانات پیشرفته‌ی سیستم تایپ TS مثل Conditional Types، Mapped Types و Template Literal Types استفاده‌ی سنگین می‌شود. اگر شما هم کتابخانه‌ی عمومی می‌نویسید، تسلط بر این لایه تفاوت بین یک کتابخانه‌ی معمولی و یک کتابخانه‌ی حرفه‌ای است. در پروژه‌های داخلی، این لایه معمولاً لازم نیست. مطالعه‌ی موازی این لایه در آموزش تایپ اسکریپت از صفر و تایپ اسکریپت با Node.js آمده است.

یک تجربه‌ی واقعی از پروژه‌ای که با مسئله‌ی Type Narrowing روبرو شدیم: در یک تابع که یک مقدار از نوع string | null می‌گرفت، تیم از as string برای خاموش کردن خطا استفاده می‌کرد. اما در runtime، این مقدار می‌توانست واقعاً null باشد و باعث می‌شد منطق برنامه با یک باگ پنهان مواجه شود. راه‌حل درست: بازنویسی با یک شرط if (value !== null) که TS را وادار به Narrowing خودکار می‌کرد. این یک اصلاح ساده‌ی TS، خطای runtime را از ریشه حذف کرد.

اگر روی پروژه‌های وردپرسی هستید و می‌خواهید این لایه‌ها را در development pipeline خود اعمال کنید، پیشنهاد می‌کنم ابتدا به توسعه وردپرس از صفر نگاهی بیندازید. برای مطالعه‌ی موازی با استانداردها و معماری، استانداردهای HTML و CSS و CSS مدرن از Flexbox تا Grid دید وسیع‌تری می‌دهند. برای درک این لایه در چارچوب کارایی، بهینه سازی جاوااسکریپت و بهینه‌سازی سرعت سایت منابع کلیدی هستند. اگر روی موضوع تست و کیفیت کد متمرکز هستید، گیت در وردپرس و ابزارهای CI/CD را هم ببینید.

تایپ‌ها در TypeScript مثل لنزهای دوربین هستند: با لنز درست، همه‌چیز واضح و دقیق دیده می‌شود؛ با لنز اشتباه، تصویر مبهم و پر از نویز است. تفاوت بین یک پروژه‌ی TS خوب و یک پروژه‌ی TS معمولی، دقیقاً در انتخاب همین لنز است.

خط آخر این مسیر

تایپ‌ها در TypeScript را می‌توان در یک جمله خلاصه کرد: «ابزارهایی برای مدل‌سازی دامنه، نه فقط برچسب‌گذاری متغیرها.» سه درس که از این مسیر با خودم بردم:

  1. از any فرار کنید، به unknown پناه ببرید. هر مورد any یک نقطه‌ی خاموش تایپ‌چکینگ است. اگر مجبور به استفاده از آن شدید، حداقل با کامنت دلیلش را بنویسید و در بازبینی بعدی به سراغش برگردید.
  2. Literal Types و Discriminated Unions را جدی بگیرید. این دو ابزار، ساده‌ترین و پرقدرت‌ترین امکانات TS هستند. در پروژه‌های واقعی، همین دو الگو، بیشترین کاهش باگ را می‌سازند.
  3. Type Guard، نه Type Assertion. as SomeType یک ادعا است، نه بررسی. Type Guard با value is Type یک لایه‌ی محافظت واقعی در runtime ایجاد می‌کند. تفاوت بین این دو، تفاوت بین یک پروژه‌ی TS ظاهری و یک پروژه‌ی TS واقعی است.

مسیر یادگیری فرانت‌اند با این نوشته تمام نمی‌شود. اگر می‌خواهید مرحله‌ی بعدی را بردارید، آموزش تایپ اسکریپت از صفر، اینترفیس در تایپ اسکریپت و Generics در تایپ اسکریپت سه قدم منطقی بعدی هستند. اگر روی فریم‌ورک‌ها متمرکز هستید، تایپ اسکریپت با React، تایپ اسکریپت با Node.js و ماژول ها در تایپ اسکریپت دید وسیع‌تری می‌دهند. اگر هم به سمت خطاها و دیباگ می‌روید، خطاهای رایج تایپ اسکریپت و تنظیمات tsconfig منابع کلیدی هستند.

در تجربه‌ی خودم، جالب‌ترین الگوی تایپ را در پروژه‌هایی دیده‌ام که تیم توسعه، قبل از نوشتن کد، تایپ‌ها را روی کاغذ طراحی می‌کرد. اگر شما هم چنین تجربه‌ای دارید — یا برعکس، با تایپی روبرو شده‌اید که بعداً گریبان پروژه را گرفته — آن را برای ما تعریف کنید. اگر هم الگویی از Discriminated Union یا Type Guard در پروژه‌ای پیدا کرده‌اید که مشکل بزرگی را حل کرده، همان داستان برای کسی که امروز در حال طراحی تایپ‌های یک پروژه‌ی جدید است، از هر مستند فنی ارزشمندتر است.