বাংলা

টাইপস্ক্রিপ্টের 'infer' কীওয়ার্ডের একটি বিস্তারিত গাইড। এতে কন্ডিশনাল টাইপের সাথে এর ব্যবহার, শক্তিশালী টাইপ এক্সট্র্যাকশন ও ম্যানিপুলেশন এবং অ্যাডভান্সড ব্যবহার দেখানো হয়েছে।

টাইপস্ক্রিপ্ট Infer আয়ত্ত করা: অ্যাডভান্সড টাইপ ম্যানিপুলেশনের জন্য কন্ডিশনাল টাইপ এক্সট্র্যাকশন

টাইপস্ক্রিপ্টের টাইপ সিস্টেম অত্যন্ত শক্তিশালী, যা ডেভেলপারদের নির্ভরযোগ্য এবং রক্ষণাবেক্ষণযোগ্য অ্যাপ্লিকেশন তৈরি করতে সাহায্য করে। এই শক্তির অন্যতম মূল ফিচার হলো infer কীওয়ার্ড যা কন্ডিশনাল টাইপের সাথে ব্যবহৃত হয়। এই সংমিশ্রণটি জটিল টাইপ স্ট্রাকচার থেকে নির্দিষ্ট টাইপ বের করার একটি পদ্ধতি প্রদান করে। এই ব্লগ পোস্টে infer কীওয়ার্ডের গভীরে প্রবেশ করে এর কার্যকারিতা ব্যাখ্যা করা হয়েছে এবং এর অ্যাডভান্সড ব্যবহার তুলে ধরা হয়েছে। আমরা এপিআই ইন্টারঅ্যাকশন থেকে শুরু করে জটিল ডেটা স্ট্রাকচার ম্যানিপুলেশন পর্যন্ত বিভিন্ন সফটওয়্যার ডেভেলপমেন্ট পরিস্থিতিতে প্রযোজ্য বাস্তব উদাহরণগুলো অন্বেষণ করব।

কন্ডিশনাল টাইপস কী?

infer কীওয়ার্ড নিয়ে আলোচনার আগে, আসুন সংক্ষেপে কন্ডিশনাল টাইপস পর্যালোচনা করি। টাইপস্ক্রিপ্টে কন্ডিশনাল টাইপস আপনাকে জাভাস্ক্রিপ্টের টারনারি অপারেটরের মতো একটি শর্তের উপর ভিত্তি করে টাইপ নির্ধারণ করতে দেয়। এর বেসিক সিনট্যাক্স হলো:

T extends U ? X : Y

এর মানে হলো: "যদি টাইপ T টাইপ U-এর সাথে অ্যাসাইন করা যায়, তাহলে টাইপটি হবে X; অন্যথায়, টাইপটি হবে Y।"

উদাহরণ:

type IsString<T> = T extends string ? true : false;

type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false

infer কীওয়ার্ডের পরিচিতি

infer কীওয়ার্ডটি একটি কন্ডিশনাল টাইপের extends ক্লজের মধ্যে ব্যবহার করা হয় একটি টাইপ ভ্যারিয়েবল ঘোষণা করার জন্য যা চেক করা টাইপ থেকে অনুমান (infer) করা যায়। মূলত, এটি আপনাকে পরবর্তী ব্যবহারের জন্য একটি টাইপের অংশ "ক্যাপচার" করতে দেয়।

বেসিক সিনট্যাক্স:

type MyType<T> = T extends (infer U) ? U : never;

এই উদাহরণে, যদি T কোনো টাইপের সাথে অ্যাসাইনযোগ্য হয়, টাইপস্ক্রিপ্ট U-এর টাইপ অনুমান করার চেষ্টা করবে। যদি অনুমান সফল হয়, টাইপটি হবে U; অন্যথায়, এটি হবে never

infer-এর সহজ উদাহরণ

১. ফাংশনের রিটার্ন টাইপ অনুমান করা

একটি সাধারণ ব্যবহার হলো একটি ফাংশনের রিটার্ন টাইপ অনুমান করা:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

function add(a: number, b: number): number {
  return a + b;
}

type AddReturnType = ReturnType<typeof add>; // type AddReturnType = number

function greet(name: string): string {
  return `Hello, ${name}!`;
}

type GreetReturnType = ReturnType<typeof greet>; // type GreetReturnType = string

এই উদাহরণে, ReturnType<T> ইনপুট হিসাবে একটি ফাংশন টাইপ T নেয়। এটি পরীক্ষা করে যে T এমন একটি ফাংশনের সাথে অ্যাসাইনযোগ্য কিনা যা যেকোনো আর্গুমেন্ট গ্রহণ করে এবং একটি মান রিটার্ন করে। যদি তা হয়, তবে এটি রিটার্ন টাইপটিকে R হিসাবে অনুমান করে এবং তা রিটার্ন করে। অন্যথায়, এটি any রিটার্ন করে।

২. অ্যারের এলিমেন্ট টাইপ অনুমান করা

আরেকটি দরকারি পরিস্থিতি হলো একটি অ্যারে থেকে এলিমেন্ট টাইপ এক্সট্র্যাক্ট করা:

type ArrayElementType<T> = T extends (infer U)[] ? U : never;

type NumberArrayType = ArrayElementType<number[]>; // type NumberArrayType = number
type StringArrayType = ArrayElementType<string[]>; // type StringArrayType = string
type MixedArrayType = ArrayElementType<(string | number)[]>; // type MixedArrayType = string | number
type NotAnArrayType = ArrayElementType<number>; // type NotAnArrayType = never

এখানে, ArrayElementType<T> পরীক্ষা করে যে T একটি অ্যারে টাইপ কিনা। যদি তাই হয়, তবে এটি এলিমেন্ট টাইপটিকে U হিসাবে অনুমান করে এবং তা রিটার্ন করে। যদি না হয়, তবে এটি never রিটার্ন করে।

infer-এর অ্যাডভান্সড ব্যবহার

১. কনস্ট্রাক্টরের প্যারামিটার অনুমান করা

আপনি একটি কনস্ট্রাক্টর ফাংশনের প্যারামিটার টাইপগুলো এক্সট্র্যাক্ট করতে infer ব্যবহার করতে পারেন:

type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

class Person {
  constructor(public name: string, public age: number) {}
}

type PersonConstructorParams = ConstructorParameters<typeof Person>; // type PersonConstructorParams = [string, number]

class Point {
    constructor(public x: number, public y: number) {}
}

type PointConstructorParams = ConstructorParameters<typeof Point>; // type PointConstructorParams = [number, number]

এই ক্ষেত্রে, ConstructorParameters<T> একটি কনস্ট্রাক্টর ফাংশন টাইপ T নেয়। এটি কনস্ট্রাক্টর প্যারামিটারগুলোর টাইপকে P হিসাবে অনুমান করে এবং அவற்றை একটি টাপল (tuple) হিসাবে রিটার্ন করে।

২. অবজেক্ট টাইপ থেকে প্রোপার্টি এক্সট্র্যাক্ট করা

ম্যাপড টাইপ এবং কন্ডিশনাল টাইপ ব্যবহার করে অবজেক্ট টাইপ থেকে নির্দিষ্ট প্রোপার্টি এক্সট্র্যাক্ট করার জন্যও infer ব্যবহার করা যেতে পারে:

type PickByType<T, K extends keyof T, U> = {
  [P in K as T[P] extends U ? P : never]: T[P];
};

interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  isActive: boolean;
}

type StringProperties = PickByType<User, keyof User, string>; // type StringProperties = { name: string; email: string; }

type NumberProperties = PickByType<User, keyof User, number>; // type NumberProperties = { id: number; age: number; }

//ভৌগোলিক স্থানাঙ্ক উপস্থাপনকারী একটি ইন্টারফেস।
interface GeoCoordinates {
    latitude: number;
    longitude: number;
    altitude: number;
    country: string;
    city: string;
    timezone: string;
}

type NumberCoordinateProperties = PickByType<GeoCoordinates, keyof GeoCoordinates, number>; // type NumberCoordinateProperties = { latitude: number; longitude: number; altitude: number; }

এখানে, PickByType<T, K, U> একটি নতুন টাইপ তৈরি করে যা T-এর শুধুমাত্র সেই প্রোপার্টিগুলো অন্তর্ভুক্ত করে (যেগুলোর কী K-তে আছে) যাদের ভ্যালু U টাইপের সাথে অ্যাসাইনযোগ্য। ম্যাপড টাইপটি T-এর কী-গুলোর উপর ইটারেট করে, এবং কন্ডিশনাল টাইপটি নির্দিষ্ট টাইপের সাথে মেলে না এমন কী-গুলোকে ফিল্টার করে বাদ দেয়।

৩. প্রমিস (Promise) নিয়ে কাজ করা

আপনি একটি Promise-এর রিজলভড টাইপ অনুমান করতে পারেন:

type Awaited<T> = T extends Promise<infer U> ? U : T;

async function fetchData(): Promise<string> {
  return 'Data from API';
}

type FetchDataType = Awaited<ReturnType<typeof fetchData>>; // type FetchDataType = string

async function fetchNumbers(): Promise<number[]> {
    return [1, 2, 3];
}

type FetchedNumbersType = Awaited<ReturnType<typeof fetchNumbers>>; //type FetchedNumbersType = number[]

Awaited<T> টাইপটি একটি টাইপ T নেয়, যা একটি প্রমিস (Promise) হবে বলে আশা করা হয়। টাইপটি তখন প্রমিসের রিজলভড টাইপ U অনুমান করে এবং তা রিটার্ন করে। যদি T একটি প্রমিস না হয়, তবে এটি T রিটার্ন করে। এটি টাইপস্ক্রিপ্টের নতুন সংস্করণগুলিতে একটি বিল্ট-ইন ইউটিলিটি টাইপ।

৪. প্রমিসের অ্যারে থেকে টাইপ এক্সট্র্যাক্ট করা

Awaited এবং অ্যারে টাইপ ইনফারেন্স একত্রিত করে আপনি প্রমিসের একটি অ্যারে দ্বারা রিজলভ করা টাইপ অনুমান করতে পারেন। এটি বিশেষত Promise.all নিয়ে কাজ করার সময় কার্যকর।

type PromiseArrayReturnType<T extends Promise<any>[]> = {
    [K in keyof T]: Awaited<T[K]>;
};


async function getUSDRate(): Promise<number> {
  return 0.0069;
}

async function getEURRate(): Promise<number> {
  return 0.0064;
}

const rates = [getUSDRate(), getEURRate()];

type RatesType = PromiseArrayReturnType<typeof rates>;
// type RatesType = [number, number]

এই উদাহরণে প্রথমে দুটি অ্যাসিঙ্ক্রোনাস ফাংশন, getUSDRate এবং getEURRate সংজ্ঞায়িত করা হয়েছে, যা এক্সচেঞ্জ রেট আনার অনুকরণ করে। এরপর PromiseArrayReturnType ইউটিলিটি টাইপটি অ্যারের প্রতিটি Promise থেকে রিজলভড টাইপ এক্সট্র্যাক্ট করে, যার ফলে একটি টাপল টাইপ তৈরি হয় যেখানে প্রতিটি এলিমেন্ট সংশ্লিষ্ট প্রমিসের অ্যাওয়েটেড (awaited) টাইপ।

বিভিন্ন ডোমেনে বাস্তব উদাহরণ

১. ই-কমার্স অ্যাপ্লিকেশন

একটি ই-কমার্স অ্যাপ্লিকেশনের কথা ভাবুন যেখানে আপনি একটি এপিআই থেকে পণ্যের বিবরণ আনেন। আপনি পণ্যের ডেটার টাইপ এক্সট্র্যাক্ট করতে infer ব্যবহার করতে পারেন:

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
  imageUrl: string;
  category: string;
  rating: number;
  countryOfOrigin: string;
}

async function fetchProduct(productId: number): Promise<Product> {
  // API কলের অনুকরণ
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        id: productId,
        name: 'Example Product',
        price: 29.99,
        description: 'A sample product',
        imageUrl: 'https://example.com/image.jpg',
        category: 'Electronics',
        rating: 4.5,
        countryOfOrigin: 'Canada'
      });
    }, 500);
  });
}


type ProductType = Awaited<ReturnType<typeof fetchProduct>>; // type ProductType = Product

function displayProductDetails(product: ProductType) {
  console.log(`Product Name: ${product.name}`);
  console.log(`Price: ${product.price} ${product.countryOfOrigin === 'Canada' ? 'CAD' : (product.countryOfOrigin === 'USA' ? 'USD' : 'EUR')}`);
}

fetchProduct(123).then(displayProductDetails);

এই উদাহরণে, আমরা একটি Product ইন্টারফেস এবং একটি fetchProduct ফাংশন সংজ্ঞায়িত করি যা একটি এপিআই থেকে পণ্যের বিবরণ আনে। আমরা fetchProduct ফাংশনের রিটার্ন টাইপ থেকে Product টাইপটি এক্সট্র্যাক্ট করার জন্য Awaited এবং ReturnType ব্যবহার করি, যা আমাদেরকে displayProductDetails ফাংশনটিকে টাইপ-চেক করতে দেয়।

২. আন্তর্জাতিকীকরণ (i18n)

ধরুন আপনার একটি অনুবাদ ফাংশন আছে যা লোকালের উপর ভিত্তি করে বিভিন্ন স্ট্রিং রিটার্ন করে। টাইপ সুরক্ষার জন্য আপনি এই ফাংশনের রিটার্ন টাইপ এক্সট্র্যাক্ট করতে infer ব্যবহার করতে পারেন:

interface Translations {
  greeting: string;
  farewell: string;
  welcomeMessage: (name: string) => string;
}

const enTranslations: Translations = {
  greeting: 'Hello',
  farewell: 'Goodbye',
  welcomeMessage: (name: string) => `Welcome, ${name}!`, 
};

const frTranslations: Translations = {
  greeting: 'Bonjour',
  farewell: 'Au revoir',
  welcomeMessage: (name: string) => `Bienvenue, ${name}!`, 
};

function getTranslation(locale: 'en' | 'fr'): Translations {
  return locale === 'en' ? enTranslations : frTranslations;
}

type TranslationType = ReturnType<typeof getTranslation>;

function greetUser(locale: 'en' | 'fr', name: string) {
  const translations = getTranslation(locale);
  console.log(translations.welcomeMessage(name));
}

greetUser('fr', 'Jean'); // Output: Bienvenue, Jean!

এখানে, TranslationType-কে Translations ইন্টারফেস হিসাবে অনুমান করা হয়, যা নিশ্চিত করে যে greetUser ফাংশনের কাছে অনূদিত স্ট্রিংগুলো অ্যাক্সেস করার জন্য সঠিক টাইপ তথ্য রয়েছে।

৩. এপিআই রেসপন্স হ্যান্ডলিং

এপিআই নিয়ে কাজ করার সময়, রেসপন্স স্ট্রাকচার জটিল হতে পারে। infer নেস্টেড এপিআই রেসপন্স থেকে নির্দিষ্ট ডেটা টাইপ এক্সট্র্যাক্ট করতে সাহায্য করতে পারে:

interface ApiResponse<T> {
  status: number;
  data: T;
  message?: string;
}

interface UserData {
  id: number;
  username: string;
  email: string;
  profile: {
    firstName: string;
    lastName: string;
    country: string;
    language: string;
  }
}

async function fetchUser(userId: number): Promise<ApiResponse<UserData>> {
  // API কলের অনুকরণ
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        status: 200,
        data: {
          id: userId,
          username: 'johndoe',
          email: 'john.doe@example.com',
          profile: {
            firstName: 'John',
            lastName: 'Doe',
            country: 'USA',
            language: 'en'
          }
        }
      });
    }, 500);
  });
}


type UserApiResponse = Awaited<ReturnType<typeof fetchUser>>;

type UserProfileType = UserApiResponse['data']['profile'];

function displayUserProfile(profile: UserProfileType) {
  console.log(`Name: ${profile.firstName} ${profile.lastName}`);
  console.log(`Country: ${profile.country}`);
}

fetchUser(123).then((response) => {
  if (response.status === 200) {
    displayUserProfile(response.data.profile);
  }
});

এই উদাহরণে, আমরা একটি ApiResponse ইন্টারফেস এবং একটি UserData ইন্টারফেস সংজ্ঞায়িত করি। আমরা এপিআই রেসপন্স থেকে UserProfileType এক্সট্র্যাক্ট করতে infer এবং টাইপ ইনডেক্সিং ব্যবহার করি, যা নিশ্চিত করে যে displayUserProfile ফাংশনটি সঠিক টাইপ পায়।

infer ব্যবহারের সেরা অনুশীলন

সাধারণ ভুলত্রুটি

infer-এর বিকল্প

যদিও infer একটি শক্তিশালী টুল, এমন কিছু পরিস্থিতি রয়েছে যেখানে বিকল্প পদ্ধতিগুলো আরও উপযুক্ত হতে পারে:

উপসংহার

টাইপস্ক্রিপ্টে infer কীওয়ার্ড, যখন কন্ডিশনাল টাইপের সাথে মিলিত হয়, তখন অ্যাডভান্সড টাইপ ম্যানিপুলেশন ক্ষমতা উন্মোচন করে। এটি আপনাকে জটিল টাইপ স্ট্রাকচার থেকে নির্দিষ্ট টাইপ এক্সট্র্যাক্ট করতে দেয়, যা আপনাকে আরও শক্তিশালী, রক্ষণাবেক্ষণযোগ্য এবং টাইপ-সেফ কোড লিখতে সক্ষম করে। ফাংশনের রিটার্ন টাইপ অনুমান করা থেকে শুরু করে অবজেক্ট টাইপ থেকে প্রোপার্টি এক্সট্র্যাক্ট করা পর্যন্ত, এর সম্ভাবনা বিশাল। এই গাইডে বর্ণিত নীতি এবং সেরা অনুশীলনগুলো বোঝার মাধ্যমে, আপনি infer-কে তার পূর্ণ সম্ভাবনায় ব্যবহার করতে এবং আপনার টাইপস্ক্রিপ্ট দক্ষতা বাড়াতে পারেন। আপনার টাইপগুলো ডকুমেন্ট করতে, সেগুলো পুঙ্খানুপুঙ্খভাবে পরীক্ষা করতে এবং প্রয়োজনে বিকল্প পদ্ধতি বিবেচনা করতে ভুলবেন না। infer আয়ত্ত করা আপনাকে সত্যিই অভিব্যক্তিপূর্ণ এবং শক্তিশালী টাইপস্ক্রিপ্ট কোড লিখতে ক্ষমতা দেয়, যা শেষ পর্যন্ত উন্নত সফটওয়্যারের দিকে পরিচালিত করে।