برنامه نویسی

تسلط بر Generics در Typescript – DEV Community

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

Generics در Typescript چیست؟

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

این مثال از این تابع را در زیر در نظر بگیرید:

function echo(value) {
  return value;
}

//Call the function
echo("Hello World")

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این echo تابع یک مقدار را دریافت می کند و آن مقدار را بدون هیچ تغییری برمی گرداند. در حال حاضر، TypeScript به پارامتر تابع ما اخم می کند و شکایت می کند که پارامتر ‘value’ به طور ضمنی دارای یک نوع ‘any’ است. به این معنی که از آنجایی که هیچ نوع صریحاً مشخص نشده است، TypeScript یک نوع از را استنباط کرده است any به پارامتر تابع این به ما کمک نمی‌کند داده‌های پارامتر تابع خود را بررسی کنیم، پس چگونه بررسی کنیم که وقتی تابع ما فراخوانی می‌شود نوع مناسب ارسال شده است و اینکه تابع ما نیز داده‌های صحیح را برمی‌گرداند؟

یکی از راه هایی که می توانیم این کار را انجام دهیم، تنظیم صریح نوع پارامتر است value گذشت و برگشت

function echo(value:string):string {
  return value;
}
echo("Hello World")

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این مثال کاملاً کار می کند زیرا ما می دانیم نوع داده ای که به عنوان پارامتر ارسال می شود و داده هایی که تابع ما برمی گرداند، اما یک مشکل کوچک وجود دارد. اگر بخواهیم آن را بسازیم چه می شود echo تابع قابل استفاده مجدد است و آن را در جای دیگری با یک نوع داده متفاوت فراخوانی کنید؟ تایپ اسکریپت شکایت می کند که نوع داده اشتباهی را به آن ارسال می کند echo تابع.

function echo(value:string): string {
  return value;
}
//Argument of type 'number' is not assignable to parameter of type 'string'
echo(3.14159)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پس چگونه این را برطرف کنیم؟ چگونه یک تابع قابل استفاده مجدد ایجاد کنیم که انواعی را که به آن ارسال می کنیم را می پذیرد و برمی گرداند؟ ژنریک ها!

اصول عمومی

شروع با اصول اولیه، با استفاده از echo به عنوان مثال، اجازه می دهد تا آن را ادویه کنید و عملکرد را با ژنریک به روز کنید تا قابل استفاده مجدد باشد.

function echo<T> (value: T): T {
  return value;
}

 //calls the function with no errors from typescript
echo("Hello World")
echo(3.14159);
echo(false);
echo([1, 2, 3, 4])
echo({id: 1, fullName: "John Doe"})

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مثال، تابع echo یک تابع عمومی است که یک آرگومان می گیرد value از نوع T و مقدار دقیق نوع را برمی گرداند T بدون هیچ تغییری پارامتر نوع عمومی <T> ما به تابعی منتقل شدیم که به آن اجازه می دهد با انواع مختلف کار کند، بنابراین به ما امکان می دهد از همان تابع با داده های مختلف دوباره استفاده کنیم.

به طور کلی، شما می توانید یک نوع عمومی با هر الفبای دلخواه بنویسید، <X>, <Y>، ولی T است برای type، و این فقط یک سنت نامگذاری ژنریک است، و هیچ چیزی مانع از استفاده شما از نام ها یا الفبای دیگر نمی شود.

ژنریک در اعلان های نوع

ژنریک ها فقط با آن کار نمی کنند functions; همچنین می‌توانیم از ژنریک‌ها برای قوی‌تر کردن، استفاده مجدد و انعطاف‌پذیرتر کردن انواع در هنگام اعلام استفاده کنیم. به عنوان مثال، فرض کنید در حال ساخت یک برنامه تجارت الکترونیکی هستیم و یک رابط مخزن داریم که عملیات مشترک CRUD را برای کار با موجودیت های مختلف در سیستم خود تعریف می کند. به جای ایجاد رابط های جداگانه برای هر موجودیت (به عنوان مثال، UserRepository، ProductRepositoryو غیره)، می‌توانیم از ژنریک برای ایجاد یک تک، عمومی استفاده کنیم Repository رابطی که می تواند انواع مختلف موجودیت را مدیریت کند.

interface Repository<T> {
  getById(id: string): T | undefined;
  getAll(): T[];
  create(item: T): void;
  update(item: T): void;
  delete(id: string): void;
}
class UserRepository implements Repository<User> {
  // Implementation specific to User entity
}

class ProductRepository implements Repository<Product> {
  // Implementation specific to Products operation
}

// Usage
const userRepository: Repository<User> = new UserRepository();
const user = userRepository.getById('123');
userRepository.create(newUser);

const productRepository: Repository<Product> = new ProductRepository();
const products = productRepository.getAll();
productRepository.update(updatedProduct);

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مثال، Repository رابط با یک پارامتر نوع عمومی تعریف شده است T نشان دهنده نوع موجودیت روش های رایجی مانند getById، getAll، create، update، و delete که با هر موجودیتی قابل استفاده است.

با اجرای Repository رابط با انواع موجودیت خاص مانند User و Product، می توانیم مخازن تخصصی ایجاد کنیم که عملیات خاص آن موجودیت ها را مدیریت می کند. ماهیت عمومی اینترفیس امکان استفاده مجدد از کد و انعطاف پذیری هنگام کار با انواع مختلف موجودیت ها را فراهم می کند.

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

ژنریک در توابع با محدودیت نوع

همانطور که از اولین مثال ژنریک خود در این مقاله دیدیم، می‌توانیم از ژنریک‌ها با توابع استفاده کنیم و آن‌ها را با برخی از ویژگی‌های دیگر TypeScript ترکیب کنیم تا نتایج هیجان‌انگیزتری تولید کنیم. فرض کنید می خواهیم تابعی فراخوانی شود printLength که فقط موارد با طول را می پذیرد و طول هر اقلامی را که روی آن ارسال می کنیم چاپ می کند. با استفاده از extend کلمه کلیدی در Typescript، ما می توانیم تابعی ایجاد کنیم که داده های ارسال شده به تابع را محدود کند.

interface Lengthy {
  length: number;
}

function printLength<T extends Lengthy>(item: T): void {
  console.log(`Length: ${item.length}`);
}

const stringItem = "Hello, World!";
printLength(stringItem); // Output: Length: 13

const arrayItem = [1, 2, 3, 4, 5];
printLength(arrayItem); // Output: Length: 5

const numberItem = 42; // Error: Type 'number' does not have a property 'length'
printLength(numberItem);

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مثال، The printLength تابع یک پارامتر نوع عمومی می گیرد T که گسترش می دهد Lengthy رابط با استفاده از extends کلمه کلیدی. این یعنی نوع T باید داشته باشد length خاصیت نوع number.

در داخل printLength تابع، ما می توانیم با خیال راحت به length دارایی از item استدلال بدون ایجاد خطاهای نوع به دلیل نوع عمومی T تضمین شده است که یک length ویژگی.

با استفاده از printLength عملکرد با آیتم ها (stringItem و arrayItem ) که راضی می کند Lengthy الزامات، ما هیچ خطایی از TypeScript دریافت نکردیم. با این حال، وقتی سعی می کنیم یک عدد را پاس کنیم (numberItem) که الف ندارد length ویژگی، TypeScript یک خطا ایجاد می کند.

بیایید مثال دیگری را در نظر بگیریم که ژنریک ها را با برخی از ویژگی های داخلی دیگر TypeScript ترکیب می کند. تابع زیر آرایه ای از اشیاء را می پذیرد، یک شی را با استفاده از id ویژگی کلیدی شیء، و مقدار را برمی گرداند key مقداری که در هنگام فراخوانی تابع به عنوان پارامتر تعیین کردیم.

interface Person {
  id: number;
  name: string;
  age: number;
}

function getObjectValue<T extends { id: number }, K extends keyof T>
  (arr: T[], key: K, id: number): T[K] | undefined {
  const foundObject = arr.find((obj) => obj.id === id);
  return foundObject ? foundObject[key] : undefined;
}

const people: Person[] = [
  { id: 1, name: "John", age: 25 },
  { id: 2, name: "Jane", age: 30 },
  { id: 3, name: "Bob", age: 40 },
];

const nameValue = getObjectValue(people, "name", 2);
console.log(nameValue); // Output: Jane

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اوف به نظر می رسد یک عملکرد پیچیده با انواع مختلفی که در همه جا پرواز می کنند. بیایید آن را ذره ذره تجزیه کنیم.

  1. خط اول تابع شامل دو نوع عمومی است <T, K> جایی که T یک شی است و دارای محدودیتی است که باید دارای ویژگی شی باشد {id: number } و K یک ویژگی کلیدی شی است T (با استفاده از keyof عملگر) و دارای یک محدودیت است که فقط باید دارای ویژگی هایی از آن باشد T شی (با استفاده از extend کلمه کلیدی).

  2. خط دوم شامل پارامترهایی است که تابع دریافت می کند و آنچه تابع برمی گرداند.

    • arr: آرایه ای از شی ماست T.
    • key: یک ویژگی کلیدی شی است T.
    • id: یک عدد منحصر به فرد برای هر شی است T در آرایه
    • سپس تابع getObjectValue مقدار ویژگی کلید شی پیدا شده، T را برمی گرداند[K]، یا اگر مقدار پیدا نشد تعریف نشده باشد.
  3. خط سوم با استفاده از آرایه را بررسی می کند id به عنوان یک معیار، یافتن شیء با همان id.

  4. خط چهارم بررسی می کند که آیا شی foundObject ما فقط وجود واقعی را جستجو کردیم و اگر وجود داشت، با استفاده از کلید آن مقداری را از آن استخراج می کنیم foundObject[key] و آن را برگردانید یا برگردانید undefined اگر پیدا نشد

خودشه. ما فقط شکستیم getObjectValue برای درک بهتر تعاریف نوع عمومی که دارد، به قطعات کوچک تبدیل شود.

ژنریک ها می توانند مقادیر پیش فرض داشته باشند.

هنگام کار با توابع نیز می توانیم از ژنریک با مقادیر پیش فرض استفاده کنیم. در قبلی ما getObjectValue تابع، تصور کنید wed می خواهیم یک پارامتر پیش فرض برای خود اضافه کنیم id پارامتر به عنوان مقدار پیش‌فرض تابع ما را بر اساس فیلتر می‌کند. ما می توانیم تابع را به صورت زیر بازنویسی کنیم:

function getObjectValue<T extends { id: number }, K extends keyof T>
  (arr: T[], key:K, id:number = 2): T[K] | undefined {
  const foundObject = arr.find((obj) => obj.id === id);
  return foundObject ? foundObject[key] : undefined;
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با به روز رسانی id: number بودن id: number = 2, مقدار پیش فرض را بر روی آن قرار می دهیم 2 هر زمان که تابع ما اجرا شود.

نتیجه

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

Generics یکی از ویژگی های TypeScript است که ممکن است در ابتدا بسیار زیاد به نظر برسد، اما زمانی که آن را بهتر بشناسید، متوجه خواهید شد که چگونه کد شما را دقیق تر تایپ می کند.

امیدوارم این مقاله به شما در درک بهتر اصول عمومی کمک کند.

با تشکر برای خواندن. به سلامتی.

منابع

من دوست دارم با شما در ارتباط باشم توییتر | لینکدین | GitHub

نوشته های مشابه

دیدگاهتان را بنویسید

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

همچنین ببینید
بستن
دکمه بازگشت به بالا