گارد نوع تعریف شده توسط کاربر در تایپ اسکریپت — از صفر تا صد

۱۱۴ بازدید
آخرین به‌روزرسانی: ۰۳ مهر ۱۴۰۲
زمان مطالعه: ۵ دقیقه
گارد نوع تعریف شده توسط کاربر در تایپ اسکریپت — از صفر تا صد

در این مقاله با روش استفاده از کارکردهای قدرتمند تایپ اسکریپت برای بهبود مدیریت نوع در اپلیکیشن‌ها آشنا می‌شویم. «گاردهای نوع» (Type Guards) یکی از ویژگی‌های کلیدی کدِ دارای امنیت نوع محسوب می‌شوند. با استفاده از گاردهای نوع می‌توانیم نوع یک شیء را درون یک بلوک شرطی محدود کنیم. در این مقاله با گارد نوع تعریف شده توسط کاربر در تایپ اسکریپت آشنا خواهیم شد.

مستندات تایپ اسکریپت گارد نوع را چنین تعریف کرده‌اند:

نوعی از عبارت که یک بررسی زمان اجرا انجام می‌دهد و نوع را در یک دامنه تضمین می‌کند.

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

تایپ اسکریپت دارای گاردهای نوع درونی مانند typeof ،instanceof ،in و گاردهای نوع لفظی است. این موارد بسیار مفید هستند، اما دارای دامنه محدودی در توسعه وب مدرن هستند.

استفاده از instanceof

از instanceof می‌توان برای بررسی این که آیا یک متغیر وهله‌ای از یک کلاس مفروض است یا نه استفاده کرد. سمت راست عملگر instanceof باید یک تابع سازنده باشد:

1class Pet { }
2
3class Dog extends Pet {
4    bark() {
5        console.log('woof');
6    }
7}
8
9class Cat extends Pet {
10    purr() { 
11        console.log('meow');
12    }
13}
14
15function example(foo: any) {
16    if (foo instanceof Dog) {
17        foo.bark(); // OK, foo is type Dog in this block
18//      foo.purr(); // Error! Property 'purr' does not exist on type 'Dog'.
19} 
20    
21    if (foo instanceof Cat) {
22        foo.purr(); // OK, foo is type Cat in this block
23//      foo.bark(); // Error! Property 'bark' does not exist on type 'Cat'.
24    }
25}
26
27example(new Dog());
28example(new Cat());

کد فوق خروجی زیر را در کنسول پرینت می‌کند:

woof
meow

کامپایلر تایپ اسکریپت بلوک‌های if-else را به روش مشابهی مدیریت می‌کند. برای نمونه نوع متغیر را در صورتی که وهله‌ای از Dog نباشد به Cat محدود می‌کند:

1function example(foo: Dog | Cat) {
2    if (foo instanceof Dog) {
3        foo.bark(); 
4    } else {
5        foo.purr(); // must be Cat
6    }
7}

استفاده از typeof

از typeof در مواردی استفاده می‌کنیم که لازم باشد بین انواع number ،string ،boolean ،bigint ،object ،function ،symbol و undefined تمایز قائل شویم. زمانی که از ثابت‌های رشته‌ای دیگری استفاده کنید، عملگر typeof خطایی نخواهد داشت، اما مطابق انتظار هم عمل نمی‌کند و در نتیجه باگ‌هایی بروز می‌یابند که ردگیری آن‌ها دشوار است. این نوع از عبارت‌ها به صورت گاردهای نوع شناسایی نمی‌شوند. typeof برخلاف instanceof با یک متغیر از نوع any نیز کار می‌کند. در مثال زیر foo بدون هیچ اشکالی می‌تواند از نوع number | string باشد:

1function example(foo: any) {
2    if (typeof foo === 'number') {
3        console.log(foo + 100); // foo is type number
4    }
5    if (typeof foo === 'string') {
6        console.log('not a number: '+ foo); // foo is type string
7    }
8}
9example(23);
10example('foo');

کد فوق خروجی زیر را تولید می‌کند:

123
not a number: foo

بخش پیچیده این است که typeof تنها بررسی نوع سطحی اجرا می‌کند. یعنی می‌تواند یک متغیر که شیء ژنریک است را تعیین کند، اما نمی‌تواند شکل شیء را مشخص سازد. به عنوان مثال کد زیر کار نمی‌کند:

if (typeof foo === 'Dog')

استفاده از in

عملگر in یک بررسی امنیتی برای وجود یک مشخصه روی یک شیء از نوع union اجرا می‌کند. در چنین حالتی شاخه if به انواعی محدود می‌شود که مشخصه مورد نظر را به صورت optional یا required دارند.

به مثال زیر توجه کنید:

1function example(foo: Dog | Cat) {
2  if ('purr' in foo) {
3    foo.purr();  // foo is narrowed Cat
4  }
5  else {
6    foo.bark(); // foo is narrowed to Dog
7  }
8}

استفاده از گاردهای نوع لفظی (Literal)

علاوه بر گاردهای قبلی می‌توانید از ===، ==، ==! و =‎! برای تمییز بین مقادیر لفظی و از این رو از گاردهای نوع ساده مانند مثال زیر استفاده کنید:

1type ConfirmationState = 'yes' | 'no' | 'N/A';
2
3function confirmationHandler(state: ConfirmationState) {
4  if (state == 'yes') {
5    console.log('User selected yes');
6  } else if (state == 'no') {
7    console.log('User selected no');
8  } else {
9    console.log('User has not made a selection yet');
10  }
11}

این کد حتی در مثال‌های پیچیده نیز مانند مواردی که از انواع لفظی در یک union استفاده می‌کنید، کار می‌کند. بدین ترتیب می‌توانید مقدار یک نام مشخصه مشترک را بررسی کنید تا union را مشخص سازید. به مثال زیر توجه کنید:

1type Dog = {
2  kind: 'dog', // Literal type 
3  age: number
4}
5
6type Cat = {
7  kind: 'cat', // Literal type 
8  lives: number
9}
10
11function example(foo: Dog | Cat) {
12    if (foo.kind === 'dog') {
13        console.log(foo.age);   // OK
14        console.log(foo.lives); // Error! Property 'lives' does not exist on type 'Dog'.
15    }
16    else {  // MUST BE Cat!
17        console.log(foo.age);   // Error! Property 'age' does not exist on type 'Cat'.
18        console.log(foo.lives); // OK
19    }
20}

کامپایلر تایپ اسکریپت به قدر کافی هوشمند است تا هر دو مورد null و undefined را با بررسی‌های null == و null != کنار بگذارد. به مثال زیر توجه کنید:

1function example(foo?: Dog | null) {
2  if (foo == null) return;
3  foo.bark();
4}

استفاده از گاردهای نوع تعریف شده توسط کاربر

در پروژه‌های واقعی ممکن است لازم باشد گاردهای نوع تعریف شده خودمان را نیز با منطق سفارشی اعلان کنیم تا به کامپایلر تایپ اسکریپت کمک کنیم نوع را تشخیص دهد. به این منظور باید با استفاده از هر نوع منطقی که دوست دارید، تابعی اعلان کنید که به عنوان گارد نوع عمل کند. این تابع یعنی «تابع گارد نوع تعریف شده کاربر» تابعی است که یک «پیش‌بین نوع» (type predicate) به شکل event is MouseEvent به جای یک نوع بازگشتی بازمی‌گرداند:

1function handle(event: any): event is MouseEvent {
2    // body that returns boolean
3}

اگر تابع مقدار true بازگشت دهد، تایپ اسکریپت نوع MouseEvent را در هر بلوک مورد حفاظت از سوی یک فراخوانی به تابع محدود می‌سازد. به بیان دیگر نوع دقیق‌تر خواهد بود. event is MouseEvent کامپایلر را مطمئن می‌سازد که event ارسالی به گارد نوع واقعاً یک MouseEvent است. به مثال زیر توجه کنید:

1function isDog(test: any): test is Dog {
2  return test instanceof Dog;
3}
4
5function example(foo: any) {
6    if (isDog(foo)) {
7        // foo is type as a Dog in this block
8        foo.bark();
9    } else {
10        // foo is type any in this block
11        console.log("don't know what this is! [" + foo + "]");
12    }
13}
14
15example(new Dog());
16example(new Cat());

از پیش‌بین نوع تابع گارد (test is Dog در موقعیت نوع بازگشتی تابع) در زمان کامپایل برای محدودسازی انواع استفاده می‌شود، در حالی که تابع در زمان اجرا استفاده می‌شود. پیش‌بین نوع و تابع باید با هم توافق داشته باشد تا کد کار کند. تابع‌های گارد نوع الزاماً از typeof یا instanceof استفاده نمی‌کنند و می‌توانند از منطق پیچیده‌تری بهره بگیرند. برای نمونه کد زیر مشخصه شیء را برای تعیین این که یک Dog است یا نه بررسی می‌کند:

1function isDog(test: any): test is Dog {
2  return test.bark !== undefined;
3}

جمع‌بندی

گاردهای نوع به عنوان یکی از ویژگی‌های کمتر شناخته‌شده تایپ اسکریپت بری محدودسازی انواع و کسب رضایت کامپایلر بسیار مفید هستند. آن‌ها به حفظ امنیت نوع کمک می‌کنند و موجب می‌شوند که کد نوشته‌شده ساده‌تر درک شده و نگهداری آسان‌تری داشته باشد. گاردهای نوع داخلی مانند typeof و instanceof وجود دارند، اما کاربرد آن‌ها محدود است. گاردهای نوع تعریف شده از سوی کاربر می‌توانند با پیش‌بین نوع سفارشی و یک مشخصه یکتا از نوع مطلوب تعریف شوند. برای ایجاد یک گارد نوع سفارشی باید مراحل زیر را طی کنید:

  1. تابعی بنویسید که به جای نوع بازگشتی یک پیش‌بین نوع مانند event is MouseEvent داشته باشد.
  2. منطقی را پیاده‌سازی کنید که نوع متغیر ارسالی را به صورت پارامتری که از typeof ،instanceof یا هر نوع عملگر دیگر تابع که مقدار بولی به دست می‌دهد، تعیین کند.

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

اگر این مطلب برای شما، مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
levelup.gitconnected
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *