با بررسی 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
: نوع اولیه یک متغیر را بررسی میکند (مثلاً "string"، "number"، "boolean"، "undefined"، "object"، "function"، "symbol"، "bigint"). - عملگر
instanceof
: بررسی میکند که آیا یک شیء نمونهای از یک کلاس خاص است یا خیر. - عملگر
in
: بررسی میکند که آیا یک شیء دارای یک خاصیت مشخص است یا خیر. - توابع Type Guard سفارشی: توابعی که یک type predicate برمیگردانند، که نوع خاصی از عبارت بولی است که تایپاسکریپت برای محدود کردن انواع از آن استفاده میکند.
استفاده از 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 دو شکل دارند:
- سینتکس Angle bracket:
<Type>value
- کلمه کلیدی
as
:value as Type
کلمه کلیدی 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 میتواند خطاهای نوع اساسی را پنهان کرده و منجر به مشکلات زمان اجرا شود. در اینجا چند نکته کلیدی آورده شده است:
- از Assertions اجباری خودداری کنید: از type assertions برای مجبور کردن یک مقدار به نوعی که به وضوح آن نوع نیست، استفاده نکنید. این کار میتواند بررسی نوع تایپاسکریپت را دور زده و منجر به رفتار غیرمنتظره شود.
- Type Guards را ترجیح دهید: در صورت امکان، به جای type assertions از type guards استفاده کنید. Type guards روشی امنتر و قابل اطمینانتر برای محدود کردن انواع فراهم میکنند.
- اعتبارسنجی دادهها: اگر نوع دادههای یک منبع خارجی را تصریح میکنید، اعتبارسنجی دادهها را در برابر یک schema در نظر بگیرید تا اطمینان حاصل شود که با نوع مورد انتظار مطابقت دارد.
محدودسازی نوع (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 ترجیح دهید: Type guards روشی امنتر و قابل اطمینانتر برای محدود کردن انواع فراهم میکنند. از type assertions فقط در مواقع ضروری و با احتیاط استفاده کنید.
- از Custom Type Guards برای سناریوهای پیچیده استفاده کنید: هنگام کار با روابط نوع پیچیده یا ساختارهای داده سفارشی، توابع type guard خود را برای بهبود وضوح و قابلیت نگهداری کد تعریف کنید.
- Type Assertions را مستند کنید: اگر از type assertions استفاده میکنید، کامنتهایی برای توضیح دلیل استفاده از آنها و اینکه چرا معتقدید assertion امن است، اضافه کنید.
- دادههای خارجی را اعتبارسنجی کنید: هنگام کار با دادههای منابع خارجی، دادهها را در برابر یک schema اعتبارسنجی کنید تا اطمینان حاصل شود که با نوع مورد انتظار مطابقت دارد. کتابخانههایی مانند
zod
یاyup
میتوانند برای این کار مفید باشند. - تعاریف نوع را دقیق نگه دارید: اطمینان حاصل کنید که تعاریف نوع شما به طور دقیق ساختار دادههای شما را منعکس میکنند. تعاریف نوع نادرست میتواند منجر به استنتاجهای نوع غلط و خطاهای زمان اجرا شود.
- حالت Strict را فعال کنید: از حالت strict تایپاسکریپت (
strict: true
درtsconfig.json
) برای فعال کردن بررسی نوع سختگیرانهتر و تشخیص زودهنگام خطاهای احتمالی استفاده کنید.
ملاحظات بینالمللی
هنگام توسعه برنامهها برای مخاطبان جهانی، به نحوه تأثیر type guards و type assertions بر تلاشهای محلیسازی و بینالمللیسازی (i18n) توجه داشته باشید. به طور خاص، موارد زیر را در نظر بگیرید:
- قالببندی دادهها: قالبهای اعداد و تاریخ در مناطق مختلف بهطور قابل توجهی متفاوت هستند. هنگام انجام بررسیهای نوع یا assertions روی مقادیر عددی یا تاریخ، اطمینان حاصل کنید که از توابع قالببندی و تجزیه آگاه از منطقه (locale-aware) استفاده میکنید. به عنوان مثال، از کتابخانههایی مانند
Intl.NumberFormat
وIntl.DateTimeFormat
برای قالببندی و تجزیه اعداد و تاریخها مطابق با منطقه کاربر استفاده کنید. فرض نادرست یک قالب خاص (مثلاً فرمت تاریخ آمریکایی MM/DD/YYYY) میتواند در مناطق دیگر منجر به خطا شود. - مدیریت ارز: نمادها و قالببندی ارز نیز در سطح جهانی متفاوت است. هنگام کار با مقادیر پولی، از کتابخانههایی استفاده کنید که از قالببندی و تبدیل ارز پشتیبانی میکنند و از کدگذاری ثابت نمادهای ارز خودداری کنید. اطمینان حاصل کنید که type guards شما به درستی انواع مختلف ارز را مدیریت کرده و از ترکیب تصادفی ارزها جلوگیری میکنند.
- کدگذاری کاراکترها: از مسائل مربوط به کدگذاری کاراکترها آگاه باشید، بهویژه هنگام کار با رشتهها. اطمینان حاصل کنید که کد شما کاراکترهای یونیکد را به درستی مدیریت میکند و از فرضیات در مورد مجموعههای کاراکتر خودداری میکند. استفاده از کتابخانههایی که توابع دستکاری رشته آگاه از یونیکد را ارائه میدهند، در نظر بگیرید.
- زبانهای راست به چپ (RTL): اگر برنامه شما از زبانهای راست به چپ مانند عربی یا عبری پشتیبانی میکند، اطمینان حاصل کنید که type guards و assertions شما جهتگیری متن را به درستی مدیریت میکنند. به نحوه تأثیر متن RTL بر مقایسه و اعتبارسنجی رشتهها توجه کنید.
نتیجهگیری
Type guards و type assertions ابزارهای ضروری برای افزایش ایمنی نوع و نوشتن کدهای قویتر در تایپاسکریپت هستند. با درک نحوه استفاده مؤثر از این ویژگیها، میتوانید از خطاهای زمان اجرا جلوگیری کنید، قابلیت نگهداری کد را بهبود بخشید و برنامههای قابل اطمینانتری ایجاد کنید. به یاد داشته باشید که تا حد امکان type guards را بر type assertions ترجیح دهید، type assertions خود را مستند کنید و دادههای خارجی را برای اطمینان از صحت اطلاعات نوع خود اعتبارسنجی کنید. به کارگیری این اصول به شما امکان میدهد نرمافزاری پایدارتر و قابل پیشبینیتر، مناسب برای استقرار در سطح جهانی، ایجاد کنید.