คู่มือที่ครอบคลุมเกี่ยวกับคีย์เวิร์ด 'infer' ของ TypeScript อธิบายวิธีการใช้งานกับชนิดข้อมูลแบบมีเงื่อนไขสำหรับการดึงและจัดการชนิดข้อมูลที่มีประสิทธิภาพ รวมถึงกรณีการใช้งานขั้นสูง
การเรียนรู้ TypeScript Infer อย่างเชี่ยวชาญ: การดึงชนิดข้อมูลแบบมีเงื่อนไขสำหรับการจัดการชนิดข้อมูลขั้นสูง
ระบบชนิดข้อมูลของ TypeScript มีประสิทธิภาพอย่างเหลือเชื่อ ช่วยให้นักพัฒนาสามารถสร้างแอปพลิเคชันที่แข็งแกร่งและบำรุงรักษาได้ง่าย หนึ่งในคุณสมบัติที่สำคัญที่ช่วยให้มีประสิทธิภาพนี้คือคีย์เวิร์ด infer
ที่ใช้ร่วมกับชนิดข้อมูลแบบมีเงื่อนไข การรวมกันนี้มีกลไกสำหรับการดึงชนิดข้อมูลเฉพาะจากโครงสร้างชนิดข้อมูลที่ซับซ้อน โพสต์บล็อกนี้จะเจาะลึกคีย์เวิร์ด infer
อธิบายฟังก์ชันการทำงานและแสดงกรณีการใช้งานขั้นสูง เราจะสำรวจตัวอย่างที่เป็นประโยชน์ซึ่งสามารถนำไปใช้กับสถานการณ์การพัฒนาซอฟต์แวร์ที่หลากหลาย ตั้งแต่การโต้ตอบกับ API ไปจนถึงการจัดการโครงสร้างข้อมูลที่ซับซ้อน
ชนิดข้อมูลแบบมีเงื่อนไขคืออะไร
ก่อนที่เราจะดำดิ่งสู่ infer
มาทบทวนชนิดข้อมูลแบบมีเงื่อนไขกันอย่างรวดเร็ว ชนิดข้อมูลแบบมีเงื่อนไขใน TypeScript ช่วยให้คุณกำหนดชนิดข้อมูลตามเงื่อนไข คล้ายกับตัวดำเนินการ ternary ใน JavaScript ไวยากรณ์พื้นฐานคือ:
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
ใช้ภายใน clause extends
ของชนิดข้อมูลแบบมีเงื่อนไขเพื่อประกาศตัวแปรชนิดข้อมูลที่สามารถอนุมานได้จากชนิดข้อมูลที่กำลังตรวจสอบ โดยพื้นฐานแล้ว จะช่วยให้คุณ "จับภาพ" ส่วนหนึ่งของชนิดข้อมูลเพื่อใช้ในภายหลัง
ไวยากรณ์พื้นฐาน:
type MyType<T> = T extends (infer U) ? U : never;
ในตัวอย่างนี้ ถ้า T
สามารถกำหนดให้กับชนิดข้อมูลบางชนิดได้ TypeScript จะพยายามอนุมานชนิดข้อมูลของ U
หากการอนุมานสำเร็จ ชนิดข้อมูลจะเป็น U
มิฉะนั้น จะเป็น never
ตัวอย่างง่ายๆ ของ infer
1. การอนุมานชนิดข้อมูลการคืนค่าของฟังก์ชัน
กรณีการใช้งานทั่วไปคือการอนุมานชนิดข้อมูลการคืนค่าของฟังก์ชัน:
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
2. การอนุมานชนิดข้อมูลองค์ประกอบของอาร์เรย์
อีกสถานการณ์ที่มีประโยชน์คือการดึงชนิดข้อมูลองค์ประกอบจากอาร์เรย์:
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
1. การอนุมานพารามิเตอร์ของคอนสตรัคเตอร์
คุณสามารถใช้ 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
2. การดึงคุณสมบัติจากชนิดข้อมูลออบเจ็กต์
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; }
//An interface representing geographic coordinates.
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
และชนิดข้อมูลแบบมีเงื่อนไขจะกรองคีย์ที่ไม่ตรงกับชนิดข้อมูลที่ระบุ
3. การทำงานกับ Promises
คุณสามารถอนุมานชนิดข้อมูลที่แก้ไขแล้วของ 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
ของ Promise และส่งคืน หาก T
ไม่ใช่ promise จะส่งคืน T นี่คือชนิดข้อมูลยูทิลิตี้ในตัวใน TypeScript เวอร์ชันใหม่กว่า
4. การดึงชนิดข้อมูลของอาร์เรย์ของ Promises
การรวม Awaited
และการอนุมานชนิดข้อมูลอาร์เรย์ช่วยให้คุณอนุมานชนิดข้อมูลที่แก้ไขโดยอาร์เรย์ของ Promises ได้ สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อจัดการกับ 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]
ตัวอย่างนี้กำหนดฟังก์ชัน asynchronous สองฟังก์ชันก่อน getUSDRate
และ getEURRate
ซึ่งจำลองการดึงอัตราแลกเปลี่ยน จากนั้นชนิดข้อมูลยูทิลิตี้ PromiseArrayReturnType
จะดึงชนิดข้อมูลที่แก้ไขแล้วจากแต่ละ Promise
ในอาร์เรย์ ส่งผลให้ชนิดข้อมูล tuple โดยที่แต่ละองค์ประกอบเป็นชนิดข้อมูล awaited ของ Promise ที่สอดคล้องกัน
ตัวอย่างที่เป็นประโยชน์ในโดเมนต่างๆ
1. แอปพลิเคชันอีคอมเมิร์ซ
พิจารณาแอปพลิเคชันอีคอมเมิร์ซที่คุณดึงรายละเอียดผลิตภัณฑ์จาก API คุณสามารถใช้ 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> {
// Simulate API call
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
ที่ดึงรายละเอียดผลิตภัณฑ์จาก API เราใช้ Awaited
และ ReturnType
เพื่อดึงชนิดข้อมูล Product
จากชนิดข้อมูลการคืนค่าของฟังก์ชัน fetchProduct
ทำให้เราสามารถตรวจสอบชนิดข้อมูลของฟังก์ชัน displayProductDetails
ได้
2. Internationalization (i18n)
สมมติว่าคุณมีฟังก์ชันการแปลที่ส่งคืนสตริงที่แตกต่างกันตาม locale คุณสามารถใช้ 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
มีข้อมูลชนิดข้อมูลที่ถูกต้องสำหรับการเข้าถึงสตริงที่แปลแล้ว
3. การจัดการการตอบสนองของ API
เมื่อทำงานกับ API โครงสร้างการตอบสนองอาจซับซ้อน infer
สามารถช่วยดึงชนิดข้อมูลเฉพาะจากการตอบสนองของ API ที่ซ้อนกัน:
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>> {
// Simulate API call
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
เราใช้ infer
และการจัดทำดัชนีชนิดข้อมูลเพื่อดึง UserProfileType
จากการตอบสนองของ API เพื่อให้มั่นใจว่าฟังก์ชัน displayUserProfile
ได้รับชนิดข้อมูลที่ถูกต้อง
แนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้ infer
- ทำให้เรียบง่าย: ใช้
infer
เฉพาะเมื่อจำเป็น การใช้มากเกินไปอาจทำให้โค้ดของคุณอ่านและทำความเข้าใจได้ยากขึ้น - จัดทำเอกสารชนิดข้อมูลของคุณ: เพิ่มความคิดเห็นเพื่ออธิบายว่าชนิดข้อมูลแบบมีเงื่อนไขและคำสั่ง
infer
ของคุณกำลังทำอะไร - ทดสอบชนิดข้อมูลของคุณ: ใช้การตรวจสอบชนิดข้อมูลของ TypeScript เพื่อให้แน่ใจว่าชนิดข้อมูลของคุณทำงานตามที่คาดไว้
- พิจารณาประสิทธิภาพ: ชนิดข้อมูลแบบมีเงื่อนไขที่ซับซ้อนบางครั้งอาจส่งผลต่อเวลาในการคอมไพล์ โปรดคำนึงถึงความซับซ้อนของชนิดข้อมูลของคุณ
- ใช้ชนิดข้อมูลยูทิลิตี้: TypeScript มีชนิดข้อมูลยูทิลิตี้ในตัวหลายชนิด (เช่น
ReturnType
,Awaited
) ที่สามารถทำให้โค้ดของคุณง่ายขึ้นและลดความจำเป็นในการใช้คำสั่งinfer
ที่กำหนดเอง
ข้อผิดพลาดทั่วไป
- การอนุมานที่ไม่ถูกต้อง: บางครั้ง TypeScript อาจอนุมานชนิดข้อมูลที่ไม่ใช่สิ่งที่คุณคาดหวัง ตรวจสอบคำจำกัดความและเงื่อนไขของชนิดข้อมูลของคุณอีกครั้ง
- การอ้างอิงแบบวงกลม: ระมัดระวังเมื่อกำหนดชนิดข้อมูลแบบเรียกซ้ำโดยใช้
infer
เนื่องจากอาจนำไปสู่การอ้างอิงแบบวงกลมและข้อผิดพลาดในการคอมไพล์ - ชนิดข้อมูลที่ซับซ้อนเกินไป: หลีกเลี่ยงการสร้างชนิดข้อมูลแบบมีเงื่อนไขที่ซับซ้อนเกินไปซึ่งยากต่อการทำความเข้าใจและบำรุงรักษา แบ่งออกเป็นชนิดข้อมูลที่เล็กลงและจัดการได้ง่ายกว่า
ทางเลือกอื่นแทน infer
แม้ว่า infer
จะเป็นเครื่องมือที่มีประสิทธิภาพ แต่มีสถานการณ์ที่แนวทางอื่นอาจเหมาะสมกว่า:
- การยืนยันชนิดข้อมูล: ในบางกรณี คุณสามารถใช้การยืนยันชนิดข้อมูลเพื่อระบุชนิดข้อมูลของค่าอย่างชัดเจนแทนที่จะอนุมาน อย่างไรก็ตาม โปรดระมัดระวังเกี่ยวกับการยืนยันชนิดข้อมูล เนื่องจากอาจข้ามการตรวจสอบชนิดข้อมูลได้
- ตัวป้องกันชนิดข้อมูล: ตัวป้องกันชนิดข้อมูลสามารถใช้เพื่อจำกัดชนิดข้อมูลของค่าตามการตรวจสอบรันไทม์ สิ่งนี้มีประโยชน์เมื่อคุณต้องการจัดการกับชนิดข้อมูลที่แตกต่างกันตามเงื่อนไขรันไทม์
- ชนิดข้อมูลยูทิลิตี้: TypeScript มีชุดชนิดข้อมูลยูทิลิตี้มากมายที่สามารถจัดการงานจัดการชนิดข้อมูลทั่วไปจำนวนมากได้โดยไม่จำเป็นต้องใช้คำสั่ง
infer
ที่กำหนดเอง
สรุป
คีย์เวิร์ด infer
ใน TypeScript เมื่อรวมกับชนิดข้อมูลแบบมีเงื่อนไข จะปลดล็อกความสามารถในการจัดการชนิดข้อมูลขั้นสูง ช่วยให้คุณสามารถดึงชนิดข้อมูลเฉพาะจากโครงสร้างชนิดข้อมูลที่ซับซ้อน ทำให้คุณสามารถเขียนโค้ดที่แข็งแกร่ง บำรุงรักษาได้ง่าย และปลอดภัยต่อชนิดข้อมูลมากขึ้น ตั้งแต่การอนุมานชนิดข้อมูลการคืนค่าของฟังก์ชันไปจนถึงการดึงคุณสมบัติจากชนิดข้อมูลออบเจ็กต์ ความเป็นไปได้นั้นมีมากมาย การทำความเข้าใจหลักการและแนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในคู่มือนี้ คุณสามารถใช้ประโยชน์จาก infer
ได้อย่างเต็มศักยภาพและยกระดับทักษะ TypeScript ของคุณ อย่าลืมจัดทำเอกสารชนิดข้อมูลของคุณ ทดสอบอย่างละเอียดถี่ถ้วน และพิจารณาแนวทางอื่นเมื่อเหมาะสม การเรียนรู้ infer
อย่างเชี่ยวชาญช่วยให้คุณเขียนโค้ด TypeScript ที่แสดงออกและมีประสิทธิภาพอย่างแท้จริง ซึ่งนำไปสู่ซอฟต์แวร์ที่ดีขึ้นในที่สุด