টাইপস্ক্রিপ্টের '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
স্টেটমেন্টগুলো কী করছে তা ব্যাখ্যা করার জন্য কমেন্ট যোগ করুন। - আপনার টাইপগুলো পরীক্ষা করুন: আপনার টাইপগুলো প্রত্যাশিতভাবে কাজ করছে কিনা তা নিশ্চিত করতে টাইপস্ক্রিপ্টের টাইপ চেকিং ব্যবহার করুন।
- পারফরম্যান্স বিবেচনা করুন: জটিল কন্ডিশনাল টাইপগুলো কখনও কখনও কম্পাইলেশন সময়কে প্রভাবিত করতে পারে। আপনার টাইপের জটিলতা সম্পর্কে সচেতন থাকুন।
- ইউটিলিটি টাইপ ব্যবহার করুন: টাইপস্ক্রিপ্ট বেশ কিছু বিল্ট-ইন ইউটিলিটি টাইপ (যেমন,
ReturnType
,Awaited
) প্রদান করে যা আপনার কোডকে সহজ করতে পারে এবং কাস্টমinfer
স্টেটমেন্টের প্রয়োজনীয়তা কমাতে পারে।
সাধারণ ভুলত্রুটি
- ভুল ইনফারেন্স: কখনও কখনও, টাইপস্ক্রিপ্ট এমন একটি টাইপ অনুমান করতে পারে যা আপনি আশা করেন না। আপনার টাইপ ডেফিনিশন এবং শর্তগুলো পুনরায় পরীক্ষা করুন।
- সার্কুলার ডিপেন্ডেন্সি:
infer
ব্যবহার করে রিকার্সিভ টাইপ সংজ্ঞায়িত করার সময় সতর্ক থাকুন, কারণ এটি সার্কুলার ডিপেন্ডেন্সি এবং কম্পাইলেশন ত্রুটির কারণ হতে পারে। - অতিরিক্ত জটিল টাইপ: এমন জটিল কন্ডিশনাল টাইপ তৈরি করা থেকে বিরত থাকুন যা বোঝা এবং রক্ষণাবেক্ষণ করা কঠিন। সেগুলোকে ছোট, আরও পরিচালনাযোগ্য টাইপে বিভক্ত করুন।
infer
-এর বিকল্প
যদিও infer
একটি শক্তিশালী টুল, এমন কিছু পরিস্থিতি রয়েছে যেখানে বিকল্প পদ্ধতিগুলো আরও উপযুক্ত হতে পারে:
- টাইপ অ্যাসারশন: কিছু ক্ষেত্রে, আপনি একটি ভ্যালুর টাইপ অনুমান করার পরিবর্তে স্পষ্টভাবে নির্দিষ্ট করতে টাইপ অ্যাসারশন ব্যবহার করতে পারেন। তবে, টাইপ অ্যাসারশনের সাথে সতর্ক থাকুন, কারণ এগুলো টাইপ চেকিংকে বাইপাস করতে পারে।
- টাইপ গার্ডস: রানটাইম চেকের উপর ভিত্তি করে একটি ভ্যালুর টাইপকে সংকীর্ণ করতে টাইপ গার্ড ব্যবহার করা যেতে পারে। যখন আপনাকে রানটাইম শর্তের উপর ভিত্তি করে বিভিন্ন টাইপ পরিচালনা করতে হয় তখন এটি কার্যকর।
- ইউটিলিটি টাইপস: টাইপস্ক্রিপ্ট একটি সমৃদ্ধ ইউটিলিটি টাইপের সেট সরবরাহ করে যা কাস্টম
infer
স্টেটমেন্টের প্রয়োজন ছাড়াই অনেক সাধারণ টাইপ ম্যানিপুলেশন কাজ পরিচালনা করতে পারে।
উপসংহার
টাইপস্ক্রিপ্টে infer
কীওয়ার্ড, যখন কন্ডিশনাল টাইপের সাথে মিলিত হয়, তখন অ্যাডভান্সড টাইপ ম্যানিপুলেশন ক্ষমতা উন্মোচন করে। এটি আপনাকে জটিল টাইপ স্ট্রাকচার থেকে নির্দিষ্ট টাইপ এক্সট্র্যাক্ট করতে দেয়, যা আপনাকে আরও শক্তিশালী, রক্ষণাবেক্ষণযোগ্য এবং টাইপ-সেফ কোড লিখতে সক্ষম করে। ফাংশনের রিটার্ন টাইপ অনুমান করা থেকে শুরু করে অবজেক্ট টাইপ থেকে প্রোপার্টি এক্সট্র্যাক্ট করা পর্যন্ত, এর সম্ভাবনা বিশাল। এই গাইডে বর্ণিত নীতি এবং সেরা অনুশীলনগুলো বোঝার মাধ্যমে, আপনি infer
-কে তার পূর্ণ সম্ভাবনায় ব্যবহার করতে এবং আপনার টাইপস্ক্রিপ্ট দক্ষতা বাড়াতে পারেন। আপনার টাইপগুলো ডকুমেন্ট করতে, সেগুলো পুঙ্খানুপুঙ্খভাবে পরীক্ষা করতে এবং প্রয়োজনে বিকল্প পদ্ধতি বিবেচনা করতে ভুলবেন না। infer
আয়ত্ত করা আপনাকে সত্যিই অভিব্যক্তিপূর্ণ এবং শক্তিশালী টাইপস্ক্রিপ্ট কোড লিখতে ক্ষমতা দেয়, যা শেষ পর্যন্ত উন্নত সফটওয়্যারের দিকে পরিচালিত করে।