ประเภทการคืนค่าแบบไวด์การ์ดใน 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> ดูแปลก แต่โดยพื้นฐานแล้วหมายถึงอ็อบเจ็กต์ว่าง ซึ่งเป็นค่ามาตรฐานที่สมเหตุสมผลสำหรับพารามิเตอร์ประเภทเจเนอริกนี้
ดูสนามเด็กเล่น
