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

ประเภทการคืนค่าแบบไวด์การ์ดใน Typescript ที่ส่งคืนวัตถุที่ใช้อินเทอร์เฟซ


ฉันมีอินเตอร์เฟซดังต่อไปนี้ที่ฉันใช้ในคลาสพื้นฐาน abstract:

interface MyInterface {
   field1: string
}

abstract class BaseClass {
   // some fields here...
   abstract serialize(): Array<MyInterface>
}

ตอนนี้ฉันมีหลายซับคลาสที่สืบทอดจาก BaseClass ซึ่งแต่ละซับคลาสมีฟิลด์เพิ่มเติมสองสามตัวที่แตกต่างกันไปตามแต่ละซับคลาส เช่น

class Subclass1 extends BaseClass {
   // some additional fields that are not in BaseClass
   serialize(): Array<MyInterface> {
      return [{field1: "test", field2: "test2"}];
   }
}

ตอนนี้ฉันกำลังมองหาประเภทค่าที่ถูกต้องของฟังก์ชัน serialize ที่ควรมีความหมายดังนี้:

"ส่งคืนวัตถุใด ๆ ที่ใช้งาน MyInterface ได้ กล่าวคือ วัตถุนั้นมีอย่างน้อยทุกฟิลด์จาก MyInterface แต่ก็สามารถมีมากกว่านั้นได้"

ในการตั้งค่าข้างต้น Typescript ให้ข้อผิดพลาดกับผมเพราะชนิดค่าที่ส่งกลับไม่ตรงตาม MyInterface ซึ่งก็สมเหตุสมผล field2 ไม่มีอยู่ในอินเทอร์เฟซ

ฉันคิดว่าฉันกำลังมองหา TypeScript ที่เทียบเท่ากับ Java unbounded wildcard บางอย่างเช่น

List<? extends MyInterface>

หมายถึงสิ่งใดก็ตามที่ใช้งาน MyInterface

คำตอบ #1

คุณยังไม่ได้ส่งกลับอาร์เรย์ในตัวอย่างของคุณเลย ซึ่งนั่นคือปัญหาหลักของคุณ

อย่างไรก็ตาม หากคุณ พยายามโดยตรง ที่จะส่งกลับอาเรย์:

return [{field1: "test", field2: "test2"}];

คุณจะตกเป็นเหยื่อของการตรวจสอบทรัพย์สินเกินขอบเขต (ดูเพิ่มเติม)

ออบเจ็กต์ลิตเทอรัลได้รับการปฏิบัติพิเศษและถูกตรวจสอบคุณสมบัติเกินเมื่อกำหนดค่าให้กับตัวแปรอื่น หรือส่งเป็นอาร์กิวเมนต์ หากออบเจ็กต์ลิตเทอรัลมีคุณสมบัติใด ๆ ที่ "ประเภทเป้าหมาย" ไม่มี คุณจะได้รับข้อผิดพลาด

จริง ๆ แล้วการส่งคืนสิ่งที่มีพื้นที่ผิวกว้างขึ้นซึ่งสอดคล้องกับประเภทการส่งคืนเป็นสิ่งที่ยอมรับได้อย่างสมบูรณ์ แต่ไม่ใช่ถ้าคุณสร้างมันขึ้นมาในทันที TypeScript มองว่า ถ้าอ้างอิงเพียงหนึ่งเดียวที่มีต่อวัตถุนั้นมีคุณสมบัติเกินมา แสดงว่ามีอยู่โดยความผิดพลาด เพราะคุณสมบัติเหล่านั้นจะไม่สามารถเข้าถึงได้ทันที

ดังนั้น... แม้ว่าฉันจะไม่แนะนำให้ส่งกลับอ็อบเจ็กต์ลิเทอรัลโดยตรงที่มีคุณสมบัติที่ไม่สามารถดึงค่าได้ เว้นแต่ว่าคุณจะใช้การยืนยันชนิดข้อมูล คุณสามารถหลีกเลี่ยงได้โดยการกำหนดตัวแปรกลางแล้วส่งค่าตัวนั้นกลับแทน

// unuseful way of sidestepping
// excess property checks. bad idea.
const returnValue = [{field1: "test", field2: "test2"}];
return returnValue;

ในความเป็นไปได้สูง ในโลกความเป็นจริง สิ่งที่คุณพยายามส่งคืนมักจะถูกเก็บอยู่ในตัวแปรหรือฟิลด์แล้ว และปัญหานี้เกิดจากโค้ดทดสอบ/ตัวอย่างที่เรียบง่าย

การผลิตวัตถุที่มีรูปร่างผิดพลาดโดยเฉพาะเพื่อส่งคืนบางสิ่งที่สอดคล้องกับอินเทอร์เฟซที่มีพื้นที่ผิวแคบกว่านั้นมีกลิ่นของความผิดพลาด ดังนั้น TypeScript จึงพยายามป้องกันมันอย่างแข็งขัน

ลิงก์สนามเด็กเล่น

คำตอบ #2

ฟังดูเหมือนว่าคุณเพียงแค่ต้องผสม MyInterface กับฟิลด์เพิ่มเติมที่คุณต้องการส่งคืน คลาสย่อยสามารถปรับปรุงชนิดการส่งค่ากลับของวิธีการของตนในลักษณะนี้ได้ ตราบใดที่ชนิดการส่งค่ากลับยังคงตรงตามคลาสฐาน

class Subclass1 extends BaseClass {
   serialize(): Array<MyInterface & { field2: string }> {
      return [{field1: "test", field2: "test2"}]
   }
}

const foo = new Subclass1().serialize()
// (MyInterface & { field2: string })[]

ดูสนามเด็กเล่น


หรือถ้าคุณต้องการให้ BaseClass บังคับใช้นี่ คุณสามารถทำให้มันเป็นแบบ generic และรวมประเภทของ properties เพิ่มเติมไว้ที่นั่น วิธีนี้จะทำให้มันสะอาดขึ้นหน่อย และทำให้ชัดเจนที่ระดับบนสุดของคลาสว่าข้อมูลอื่นที่คลาสนี้อาจจัดการมีอะไรบ้าง

ตอนนี้คุณสามารถลบการระบุประเภทค่าที่ส่งกลับออกไปทั้งหมดได้ เนื่องจากมันถูกบังคับโดย BaseClass อย่างสมบูรณ์

interface MyInterface { field1: string }

abstract class BaseClass<Extra = Record<string, never>> {
   abstract serialize(): Array<MyInterface & Extra>
}

class Subclass1 extends BaseClass<{ field2: string }> {
   serialize() {
      return [{field1: "test", field2: "test2"}]
   }
}

const foo = new Subclass1().serialize()
// { field1: string, field2: string }[]

หมายเหตุ: Record<string, never> ดูแปลก แต่โดยพื้นฐานแล้วหมายถึงอ็อบเจ็กต์ว่าง ซึ่งเป็นค่ามาตรฐานที่สมเหตุสมผลสำหรับพารามิเตอร์ประเภทเจเนอริกนี้

ดูสนามเด็กเล่น