فارسی

با بررسی Type Guards و Type Assertions در TypeScript، ایمنی نوع را افزایش دهید، از خطاهای زمان اجرا جلوگیری کنید و کدی قوی‌تر و قابل نگهداری بنویسید. با مثال‌های عملی و بهترین شیوه‌ها بیاموزید.

تسلط بر ایمنی نوع: راهنمای جامع Type Guards و Type Assertions

در حوزه توسعه نرم‌افزار، به‌ویژه هنگام کار با زبان‌های دارای نوع‌دهی پویا مانند جاوااسکریپت، حفظ ایمنی نوع می‌تواند یک چالش بزرگ باشد. تایپ‌اسکریپت، که یک بالامجموعه از جاوااسکریپت است، با معرفی نوع‌دهی ایستا به این نگرانی پاسخ می‌دهد. با این حال، حتی با وجود سیستم نوع در تایپ‌اسکریپت، شرایطی پیش می‌آید که کامپایلر برای استنتاج نوع صحیح یک متغیر به کمک نیاز دارد. اینجاست که type guards و type assertions وارد عمل می‌شوند. این راهنمای جامع به بررسی عمیق این ویژگی‌های قدرتمند می‌پردازد و با ارائه مثال‌های عملی و بهترین شیوه‌ها، به شما در افزایش قابلیت اطمینان و نگهداری کدتان کمک می‌کند.

Type Guards چیستند؟

Type guards عباراتی در تایپ‌اسکریپت هستند که نوع یک متغیر را در یک محدوده خاص محدودتر (narrow) می‌کنند. آن‌ها به کامپایلر امکان می‌دهند تا نوع یک متغیر را دقیق‌تر از آنچه در ابتدا استنباط کرده بود، درک کند. این ویژگی به‌ویژه هنگام کار با انواع union یا زمانی که نوع یک متغیر به شرایط زمان اجرا بستگی دارد، مفید است. با استفاده از type guards، می‌توانید از خطاهای زمان اجرا جلوگیری کرده و کدی قوی‌تر بنویسید.

تکنیک‌های رایج Type Guard

تایپ‌اسکریپت چندین مکانیزم داخلی برای ایجاد type guards فراهم می‌کند:

استفاده از typeof

عملگر typeof یک راه ساده برای بررسی نوع اولیه یک متغیر است. این عملگر یک رشته که نشان‌دهنده نوع است را برمی‌گرداند.

function printValue(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase()); // تایپ‌اسکریپت در اینجا می‌داند که 'value' یک رشته است
  } else {
    console.log(value.toFixed(2)); // تایپ‌اسکریپت در اینجا می‌داند که 'value' یک عدد است
  }
}

printValue("hello"); // خروجی: HELLO
printValue(3.14159); // خروجی: 3.14

استفاده از instanceof

عملگر instanceof بررسی می‌کند که آیا یک شیء نمونه‌ای از یک کلاس خاص است یا خیر. این ویژگی به‌ویژه هنگام کار با وراثت مفید است.

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof!");
  }
}

function makeSound(animal: Animal) {
  if (animal instanceof Dog) {
    animal.bark(); // تایپ‌اسکریپت در اینجا می‌داند که 'animal' یک Dog است
  } else {
    console.log("Generic animal sound");
  }
}

const myDog = new Dog("Buddy");
const myAnimal = new Animal("Generic Animal");

makeSound(myDog); // خروجی: Woof!
makeSound(myAnimal); // خروجی: Generic animal sound

استفاده از in

عملگر in بررسی می‌کند که آیا یک شیء دارای یک خاصیت مشخص است یا خیر. این ویژگی هنگام کار با اشیائی که ممکن است بسته به نوعشان خصوصیات متفاوتی داشته باشند، مفید است.

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

function move(animal: Bird | Fish) {
  if ("fly" in animal) {
    animal.fly(); // تایپ‌اسکریپت در اینجا می‌داند که 'animal' یک Bird است
  } else {
    animal.swim(); // تایپ‌اسکریپت در اینجا می‌داند که 'animal' یک Fish است
  }
}

const myBird: Bird = { fly: () => console.log("Flying"), layEggs: () => console.log("Laying eggs") };
const myFish: Fish = { swim: () => console.log("Swimming"), layEggs: () => console.log("Laying eggs") };

move(myBird); // خروجی: Flying
move(myFish); // خروجی: Swimming

توابع Type Guard سفارشی

برای سناریوهای پیچیده‌تر، می‌توانید توابع type guard خود را تعریف کنید. این توابع یک type predicate برمی‌گردانند، که یک عبارت بولی است که تایپ‌اسکریپت برای محدود کردن نوع یک متغیر از آن استفاده می‌کند. یک type predicate به شکل variable is Type است.

interface Square {
  kind: "square";
  size: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Circle;

function isSquare(shape: Shape): shape is Square {
  return shape.kind === "square";
}

function getArea(shape: Shape) {
  if (isSquare(shape)) {
    return shape.size * shape.size; // تایپ‌اسکریپت در اینجا می‌داند که 'shape' یک Square است
  } else {
    return Math.PI * shape.radius * shape.radius; // تایپ‌اسکریپت در اینجا می‌داند که 'shape' یک Circle است
  }
}

const mySquare: Square = { kind: "square", size: 5 };
const myCircle: Circle = { kind: "circle", radius: 3 };

console.log(getArea(mySquare)); // خروجی: 25
console.log(getArea(myCircle)); // خروجی: 28.274333882308138

Type Assertions چیستند؟

Type assertions روشی برای این است که به کامپایلر تایپ‌اسکریپت بگویید شما بیشتر از آنچه او در حال حاضر درک می‌کند، در مورد نوع یک متغیر اطلاعات دارید. این‌ها راهی برای نادیده گرفتن استنتاج نوع تایپ‌اسکریپت و مشخص کردن صریح نوع یک مقدار هستند. با این حال، مهم است که از type assertions با احتیاط استفاده کنید، زیرا می‌توانند بررسی نوع تایپ‌اسکریپت را دور بزنند و در صورت استفاده نادرست، به طور بالقوه منجر به خطاهای زمان اجرا شوند.

Type assertions دو شکل دارند:

کلمه کلیدی as به طور کلی ترجیح داده می‌شود زیرا با JSX سازگارتر است.

چه زمانی از Type Assertions استفاده کنیم

Type assertions معمولاً در سناریوهای زیر استفاده می‌شوند:

مثال‌هایی از Type Assertions

Type Assertion صریح

در این مثال، ما تصریح می‌کنیم که فراخوانی document.getElementById یک HTMLCanvasElement را برمی‌گرداند. بدون این assertion، تایپ‌اسکریپت نوع عمومی‌تری مانند HTMLElement | null را استنباط می‌کرد.

const canvas = document.getElementById("myCanvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d"); // تایپ‌اسکریپت در اینجا می‌داند که 'canvas' یک HTMLCanvasElement است

if (ctx) {
  ctx.fillStyle = "#FF0000";
  ctx.fillRect(0, 0, 150, 75);
}

کار با انواع Unknown

هنگام کار با داده‌های یک منبع خارجی، مانند یک API، ممکن است داده‌هایی با نوع unknown دریافت کنید. می‌توانید از type assertion استفاده کنید تا به تایپ‌اسکریپت بگویید چگونه با داده‌ها رفتار کند.

interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  const data = await response.json();
  return data as User; // تصریح می‌کنیم که داده‌ها یک User هستند
}

fetchUser(1)
  .then(user => {
    console.log(user.name); // تایپ‌اسکریپت در اینجا می‌داند که 'user' یک User است
  })
  .catch(error => {
    console.error("خطا در دریافت کاربر:", error);
  });

هشدارهایی هنگام استفاده از Type Assertions

Type assertions باید به ندرت و با احتیاط استفاده شوند. استفاده بیش از حد از type assertions می‌تواند خطاهای نوع اساسی را پنهان کرده و منجر به مشکلات زمان اجرا شود. در اینجا چند نکته کلیدی آورده شده است:

محدودسازی نوع (Type Narrowing)

Type guards ذاتاً با مفهوم type narrowing مرتبط هستند. Type narrowing فرآیند اصلاح نوع یک متغیر به نوعی خاص‌تر بر اساس شرایط یا بررسی‌های زمان اجرا است. Type guards ابزارهایی هستند که ما برای دستیابی به type narrowing استفاده می‌کنیم.

تایپ‌اسکریپت از تحلیل جریان کنترل (control flow analysis) برای درک اینکه چگونه نوع یک متغیر در شاخه‌های مختلف کد تغییر می‌کند، استفاده می‌کند. هنگامی که از یک type guard استفاده می‌شود، تایپ‌اسکریپت درک داخلی خود از نوع متغیر را به‌روز می‌کند و به شما امکان می‌دهد تا با خیال راحت از متدها و خصوصیات مختص آن نوع استفاده کنید.

مثالی از Type Narrowing

function processValue(value: string | number | null) {
  if (value === null) {
    console.log("Value is null");
  } else if (typeof value === "string") {
    console.log(value.toUpperCase()); // تایپ‌اسکریپت در اینجا می‌داند که 'value' یک رشته است
  } else {
    console.log(value.toFixed(2)); // تایپ‌اسکریپت در اینجا می‌داند که 'value' یک عدد است
  }
}

processValue("test"); // خروجی: TEST
processValue(123.456); // خروجی: 123.46
processValue(null); // خروجی: Value is null

بهترین شیوه‌ها (Best Practices)

برای استفاده مؤثر از type guards و type assertions در پروژه‌های تایپ‌اسکریپت خود، بهترین شیوه‌های زیر را در نظر بگیرید:

ملاحظات بین‌المللی

هنگام توسعه برنامه‌ها برای مخاطبان جهانی، به نحوه تأثیر type guards و type assertions بر تلاش‌های محلی‌سازی و بین‌المللی‌سازی (i18n) توجه داشته باشید. به طور خاص، موارد زیر را در نظر بگیرید:

نتیجه‌گیری

Type guards و type assertions ابزارهای ضروری برای افزایش ایمنی نوع و نوشتن کدهای قوی‌تر در تایپ‌اسکریپت هستند. با درک نحوه استفاده مؤثر از این ویژگی‌ها، می‌توانید از خطاهای زمان اجرا جلوگیری کنید، قابلیت نگهداری کد را بهبود بخشید و برنامه‌های قابل اطمینان‌تری ایجاد کنید. به یاد داشته باشید که تا حد امکان type guards را بر type assertions ترجیح دهید، type assertions خود را مستند کنید و داده‌های خارجی را برای اطمینان از صحت اطلاعات نوع خود اعتبارسنجی کنید. به کارگیری این اصول به شما امکان می‌دهد نرم‌افزاری پایدارتر و قابل پیش‌بینی‌تر، مناسب برای استقرار در سطح جهانی، ایجاد کنید.