ค้นหาเว็บไซต์

อินเทอร์เฟซ - 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

เพื่อความชัดเจน เป้าหมายคือ:

  1. หลีกเลี่ยงการเขียนอินเทอร์เฟซใหม่และตั้งค่าคุณสมบัติทั้งหมดเป็นแบบเลือกได้ด้วยตนเอง
  2. หลีกเลี่ยงการกำหนดคุณลักษณะที่ไม่คาดคิด (เช่น การสะกดคำผิด)
  3. หลีกเลี่ยงตรรกะแบบเชิงคำสั่ง เช่น คำสั่ง 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 ได้ - และคุณไม่สามารถเปลี่ยนประเภทในลักษณะที่ไม่เข้ากันได้ สิ่งนี้ยังทำงานแบบเรียกซ้ำกับอ็อบเจ็กต์ที่ซ้อนกันด้วย

บทความที่เกี่ยวข้อง