อินเทอร์เฟซ - Typescript รองรับ "ชนิดย่อย" หรือไม่?
สมมติว่าฉันมีอินเทอร์เฟซ:
interface IUser {
email: string;
id: number;
phone: string;
};
จากนั้นฉันมีฟังก์ชันที่คาดหวัง ชุดย่อย (หรือการตรงกันทั้งหมด) ของชนิดนั้น บางทีอาจจะส่งวัตถุทั้งหมด บางทีอาจจะส่งแค่ {email: "[email "} ฉันต้องการให้ตัวตรวจสอบชนิดอนุญาตทั้งสองแบบ
ตัวอย่าง :
function updateUser(user: IUser) {
// Update a "subset" of user attributes:
$http.put("/users/update", user);
}
Typescript รองรับพฤติกรรมแบบนี้แล้วหรือยัง? ผมคิดว่ามันน่าจะมีประโยชน์มาก โดยเฉพาะกับรูปแบบการทำงานอย่าง Redux
เพื่อความชัดเจน เป้าหมายคือ:
- หลีกเลี่ยงการเขียนอินเทอร์เฟซใหม่และตั้งค่าคุณสมบัติทั้งหมดเป็นแบบเลือกได้ด้วยตนเอง
- หลีกเลี่ยงการกำหนดคุณลักษณะที่ไม่คาดคิด (เช่น การสะกดคำผิด)
- หลีกเลี่ยงตรรกะแบบเชิงคำสั่ง เช่น คำสั่ง
ifซึ่งจะทำให้สูญเสียประโยชน์ของการตรวจสอบชนิดข้อมูลในเวลาคอมไพล์
อัปเดต: Typescript ได้ประกาศการสนับสนุน mapped types ซึ่งควรจะแก้ปัญหานี้ได้เมื่อมีการเผยแพร่
คำตอบ #1
ควรสังเกตว่า Partial<T> ตามที่แนะนำในคำตอบที่ได้รับการยอมรับ จะทำให้ฟิลด์ ทั้งหมด เป็นทางเลือก ซึ่งไม่จำเป็นต้องเป็นสิ่งที่คุณต้องการเสมอไป
ถ้าคุณต้องการทำให้บางฟิลด์เป็นฟิลด์บังคับ (เช่น id และ email) คุณจำเป็นต้องรวมมันกับ Pick:
type UserWithOptionalPhone = Pick<IUser, 'id' | 'email'> & Partial<IUser>
คำอธิบายบางอย่าง:
สิ่งที่ Pick ทำคือมันช่วยให้คุณสามารถระบุส่วนย่อยของอินเทอร์เฟซได้อย่างกระชับ (โดยไม่ต้องสร้างอินเทอร์เฟซใหม่ทั้งหมดและทำซ้ำประเภทฟิลด์เหมือนที่คำตอบอื่นแนะนำ) จากนั้นก็ให้คุณใช้เฉพาะฟิลด์เหล่านั้น และ เฉพาะฟิลด์เหล่านั้น
function hello1(user: Pick<IUser, 'id' | 'email'>) {
}
hello1({email: '@', id: 1}); //OK
hello1({email: '@'}); //Not OK, id missing
hello1({email: '@', id: 1, phone: '123'}); //Not OK, phone not allowed
ตอนนี้ นี่ไม่ใช่สิ่งที่เราต้องการอย่างแท้จริง เพราะเราต้องการ อนุญาต แต่ไม่ใช่ บังคับ ให้ใช้โทรศัพท์ ในการทำเช่นนั้น เราจึง "รวม" เวอร์ชันบางส่วนกับเวอร์ชัน "ที่เลือก" ของประเภทของเราโดยสร้างประเภทการตัดกัน ซึ่งจะมี id และ email เป็นฟิลด์ที่จำเป็น และทุกอย่างอื่นเป็นทางเลือก - เหมือนกับที่เราต้องการเป๊ะ ๆ
function hello2(user: Pick<IUser, 'id' | 'email'> & Partial<IUser>) {
}
hello2({email: '@', id: 1}); //OK
hello2({email: '@', id: 1, phone: '123'}); //OK
hello2({email: '@'}); //Not OK, id missing
คำตอบ #2
Typescript ตอนนี้รองรับประเภทบางส่วนแล้ว
วิธีที่ถูกต้องในการสร้างชนิดบางส่วนคือ:
type PartialUser = Partial<IUser>;
คำตอบ #3
สิ่งที่คุณต้องการคืออันนี้
type Subset<T extends U, U> = U;
นี่ทำให้แน่ใจว่า U เป็นเซตย่อยของ T และส่งกลับ U เป็นประเภทใหม่ ตัวอย่างเช่น:
interface Foo {
name: string;
age: number;
}
type Bar = Subset<Foo, {
name: string;
}>;
คุณไม่สามารถเพิ่มคุณสมบัติใหม่ให้กับ Bar ที่ไม่ได้เป็นส่วนหนึ่งของ Foo ได้ - และคุณไม่สามารถเปลี่ยนประเภทในลักษณะที่ไม่เข้ากันได้ สิ่งนี้ยังทำงานแบบเรียกซ้ำกับอ็อบเจ็กต์ที่ซ้อนกันด้วย
