typescript - TS2322: 'T' อาจถูกกำหนดให้เป็นชนิดใดก็ได้ซึ่งอาจไม่เกี่ยวข้องกับ 'SuperClass'
ฉันมีคลาสหลักซึ่งมีซับคลาสจำนวนมาก
class SuperClass{
constructor(){}
}
class SubClass1 extends SuperClass{
constructor(){super()}
SubClass1Prop(){}
}
class SubClass2 extends SuperClass{
constructor(){super()}
SubClass2Prop(){}
}
ฉันมีรายการที่ประกอบด้วยหลายซับคลาสต่าง ๆ และฉันได้ประกาศประเภทของมันเป็น SuperClass[] เพราะพวกมันมีบรรพบุรุษร่วมกัน
let list:SuperClass[] = [];
list.push(new SubClass1());
list.push(new SubClass2());
ฉันมีฟังก์ชันที่รับ SubClass เป็นพารามิเตอร์ จากนั้นกรองรายการเพื่อคืนค่าอินสแตนซ์ของ SubClass นั้น
การประกาศชนิดจากฟังก์ชันนี้ถูกนำมาจากคำตอบใน SO นี้ (ชนิดของฟังก์ชันที่รับคลาสเป็นอาร์กิวเมนต์ และคืนค่าวัตถุที่เป็นอินสแตนซ์ของคลาสนั้น) โดยมีความแตกต่างเล็กน้อยตรงที่ฉันกำลังคืนค่าเป็นรายการแทนที่จะเป็นวัตถุเพียงตัวเดียว
ข้อผิดพลาดอยู่ตรงนี้ ฉันควบคุมประเภทที่สามารถส่งเข้าไปในฟังก์ชัน filterList ได้ และสามารถรับประกันได้ว่ามันจะเป็นลูกของ SuperClass เสมอ อย่างไรก็ตาม TypeScript ไม่มีวิธีใดที่จะรู้เรื่องนี้ ฉันจะประกาศอย่างไรว่า className ใดๆ ที่ฉันส่งไปยังฟังก์ชันนี้ต้องเป็นลูกของ SuperClass เสมอ และไม่สามารถเป็น "ประเภทใดก็ได้"?
function filterList<T>(className: { new():T }):T[] {
//TS2322: Type 'SuperClass[]' is not assignable to type 'T[]'.
// Type 'SuperClass' is not assignable to type 'T'.
// 'T' could be instantiated with an arbitrary type which could be unrelated to 'SuperClass'
return list.filter(obj => obj instanceof className);
}
ตัวอย่างสถานการณ์การใช้งานสำหรับฟังก์ชันข้างต้น (ลบการตรวจสอบความปลอดภัยจากค่า null ออกเพื่อให้เรียบง่ายสำหรับการสาธิต)
ฉันต้องการให้ filteredList ถูกระบุอย่างถูกต้องว่าเป็นชนิด SubClass1[] และไม่ใช่ชนิด SuperClass[] หรือ any[] โดยไม่ต้องทำการแปลงชนิดอย่างชัดเจนโดยใช้คีย์เวิร์ด "as" ปัจจุบันความต้องการทั้งหมดนี้ถูกตอบสนองแล้ว แต่เกิดข้อผิดพลาด TS ในฟังก์ชันด้านบน วิธีแก้ไขใด ๆ ต้องไม่ทำให้การตรวจสอบชนิดที่นี่ผิดพลาด
let filteredList = filterList(SubClass1);
filteredList[0].SubClass1Prop();
คำตอบ #1
แม้ว่ามันจะง่ายกว่ามาก คุณสามารถใช้ InstanceType ร่วมกับพารามิเตอร์เจเนอริกและตัวตรวจสอบชนิดเพื่อให้ได้ผลลัพธ์แบบเดียวกัน:
function filterList<T extends { new (...args: any): any }>(className: T): InstanceType<T>[] {
const isT = (potentialT: any): potentialT is InstanceType<T> => {
return potentialT instanceof className;
}
return list.filter<InstanceType<T>>(isT);
}
สนามเด็กเล่น
คำตอบ #2
list เป็นชนิด SuperClass[] ดังนั้นการกรองมันจะได้กลับมาเป็น SuperClass[] อีกชุดหนึ่ง ดังนั้นฟังก์ชันจึงคืนค่าชนิด SuperClass[] อย่างไรก็ตาม ตามชนิดของฟังก์ชัน มันจริง ๆ คืนค่าชนิด T[] ถ้า T เป็นไปตาม SuperClass เช่นในตัวอย่างที่คุณเรียกใช้งาน ก็ถือว่าดี แต่ถ้า T ไม่เป็นไปตาม SuperClass ลิสต์ที่ถูกกรองซึ่งคุณคืนค่าจะไม่สามารถกำหนดค่าให้กับชนิดค่าที่แท้จริงของฟังก์ชันได้ เพื่อแก้ไขสิ่งนี้ คุณเพียงแค่ต้องมั่นใจว่า T เป็นไปตาม SuperClass ซึ่งสามารถทำได้ง่ายด้วยพารามิเตอร์ชนิด T extends SuperClass
คุณควรจำกัดพารามิเตอร์ชนิดของคุณเสมอ (ดูเอกสารประกอบ) เว้นแต่ว่าชนิดของมันจะไม่สำคัญจริง ๆ แม้ว่าจะไม่ทำให้เกิดข้อผิดพลาดก็ตาม การเรียก filterList() กับคลาสที่ไม่ตรงตาม SuperClass เป็นเรื่องที่ไม่สมเหตุสมผล ดังนั้นคุณควรบอกคอมไพเลอร์ให้หยุดคุณถ้าคุณพยายาม ในกรณีนี้ มันหยุดคุณโดยที่คุณยังไม่ได้พยายามเลย เพราะมันระบุว่ามีความไม่ปลอดภัยโดยเนื้อแท้
คุณจะสังเกตได้ว่าการทำการเปลี่ยนแปลงนั้น อย่างไรก็ตาม จะทำให้คุณพบข้อผิดพลาดอีกข้อหนึ่ง:
'SuperClass' สามารถกำหนดให้กับข้อจำกัดของชนิด 'T' ได้ แต่ 'T' อาจถูกสร้างขึ้นด้วยชนิดย่อยอื่นของข้อจำกัด 'SuperClass'
ปัญหาที่นี่เริ่มต้นเหมือนเดิมก่อนหน้านี้: ฟังก์ชันส่งค่ากลับชนิด SuperClass[] แต่ถูกระบุประเภทว่าส่งค่ากลับเป็น T[] ถ้า T คือ SuperClass ก็จะไม่มีปัญหา แต่ถ้าไม่ใช่ และ T เป็นซับคลาสของ SuperClass ที่มีสมาชิกเพิ่มเติม SuperClass[] จะไม่สามารถกำหนดค่าให้ T[] ได้
มันอาจดูขัดแย้งกับสัญชาตญาณที่รายชื่อที่กรองออกมาจริงๆ มีประเภทเป็น SuperClass[] เนื่องจากคุณกำลังกรองทุกอย่างออกจากอาร์เรย์ที่ไม่ใช่ประเภท T อย่างชัดเจน ซึ่งบ่งบอกว่าอาร์เรย์ที่กรองออกมาควรมีประเภทเป็น T[] แต่จริงๆ แล้วมันไม่ได้ทำงานแบบนั้น ตัวตรวจสอบประเภท obj instanceof className สร้างความหมายเชิงสัญลักษณ์ว่า obj เป็นประเภท T และตั้งแต่จุดนั้นเป็นต้นมาใน callback ตัวคอมไพเลอร์ก็จะรู้เรื่องนั้น อย่างไรก็ตาม ความหมายนั้นไม่ได้ถูกส่งให้กับผู้ที่เรียกใช้ callback นั้น การเรียก Array.prototype.filter() ของคุณไม่รู้ว่าประเภทใดที่ตอบสนองต่อ callback ของมัน เพราะ callback ไม่ได้บอกมัน สิ่งที่มันรู้คืออาร์เรย์ที่มันกำลังกรองมีประเภทเป็น SuperClass[] ดังนั้นสิ่งที่ดีที่สุดที่มันสามารถบอกได้คืออาร์เรย์ที่ได้จากการกรองก็มีประเภทเป็น SuperClass[] เช่นกัน
วิธีแก้ปัญหานี้ง่ายมาก: คุณเพียงแค่ต้องระบุ type predicate (ดูเอกสาร) บน callback ของคุณ จากสิ่งนี้ filter() จะทราบว่าประเภทใดที่ตอบสนอง callback ของมัน ซึ่งช่วยให้สามารถบอกได้อย่างถูกต้องว่าประเภทของอาร์เรย์ที่ได้คืออะไร
const foo: SuperClass[] = list.filter(obj => obj instanceof className)
const bar: T[] = list.filter((obj): obj is T => obj instanceof className)
ดังนั้น เมื่อใช้การแก้ไขทั้งสองนี้ ฟังก์ชัน filterList() ของคุณตอนนี้จะดูเหมือนดังนี้:
function filterList<T extends SuperClass>(className: { new():T }):T[] {
return list.filter((obj): obj is T => obj instanceof className);
}
ข้างๆ
ฉันรู้สึกสงสัยเกี่ยวกับสิ่งที่คุณพยายามทำที่นี่ โดยในเชิงนามธรรม ฉันไม่แน่ใจว่านี่เป็นความคิดที่ดี คุณควรรู้ว่าการสืบทอดคลาสเองไม่ได้มีความหมายเชิงนิพจน์ในแง่ของประเภท หากกำหนด class A {} แล้ว new A จะสามารถกำหนดค่าให้กับประเภท SuperClass ได้ มันไม่ใช่เรื่องของ A ที่เกี่ยวข้องกับ SuperClass จริง ๆ แต่เป็นเรื่องของ A ที่ตอบสนองต่อ SuperClass ฉันไม่คิดว่ามีวิธีใดในฟังก์ชัน filterList ของคุณที่จะบังคับให้ T เป็นซับคลาสของ SuperClass ได้จริง
