เจเนอริก - คลาส TypeScript เป็นพารามิเตอร์ที่สืบทอดจากคลาสอื่น
ฉันค่อนข้างใหม่กับ Typescript และ generics; ฉันคงพลาดบางสิ่งไป หวังว่าจะเป็นเรื่องเล็กน้อย
ฉันกำลังพยายามส่งคลาส (ทั่วไป) เป็นอาร์กิวเมนต์สำหรับฟังก์ชัน แต่คลาสนี้สืบทอดมาจากคลาสเฉพาะอีกคลาสหนึ่ง
ตัวอย่างที่ทำให้เรียบง่ายเกินไปจะเป็นดังนี้: สมมติว่าฉันมี
class A {
static generate3() { return [new A(),new A(),new A()]; }
}
class B extends A {}
class C extends A {}
ฉันต้องการวิธีการที่ฉันสามารถเรียกใช้กับคลาสใด ๆ ที่สืบทอดมาจาก A เป็นพารามิเตอร์และคืนค่าผลลัพธ์ของเมธอดสแตติกนั้น บางอย่างเช่น
f(B) // returns type B[]
ฉันคิดว่าฉันทำได้
function f(type: typeof B){
return type.generate3();
}
แต่นี่ต้องการให้ฉันนิยามคลาสล่วงหน้า ฉันก็ไม่สามารถใช้ typeof B|typeof C ได้เพราะในชีวิตจริงมีคลาสมากเกินไป ทำให้ไม่สามารถใช้วิธีนี้ได้ ฉันได้ลองแล้ว
function f2<T>(type: typeof T extends A){
return type.generate3();
}
โดยที่ T ควรจะเป็นคลาส แต่ก็เกิดข้อผิดพลาดดังนี้: 'T' อ้างถึงเพียงประเภท แต่ถูกใช้เป็นค่าที่นี่
ฉันคิดว่านี่ใช้ได้
function f3(type: typeof A){
return type.generate3();
}
แต่ประเภทการคืนค่าของ f3(B) ยังคงเป็น A[] แทนที่จะเป็น B[] ที่ต้องการ ฉันลองผสมทั้งสองแบบเช่น:
function f4<T extends A>(type: typeof A) : T[]{
return type.generate3() as T[]; // cast it
}
แต่ชนิดผลลัพธ์ของ f4(B) ยังคงเป็น A[] ผมไม่เข้าใจมัน มีใครสามารถหาว่าผมทำอะไรผิดได้ไหม?
คำตอบ #1
ในตัวอย่างของคุณ A, B และ C ทุกตัวมีเมธอดแบบสแตติก generate3() { return [new A(),new A(),new A()]; } TypeScript กำลังบอกคุณอย่างถูกต้องว่า ไม่ว่าคุณจะเรียก A.generate3(), B.generate3() หรือ C.generate3() ทั้งสามตัวจะทำการรัน JavaScript return [new A(),new A(),new A()] และคืนค่า A ให้คุณสามตัว
ถ้าคุณต้องการให้ generate3 คืนค่าอะไรที่ไม่ใช่ A ดังนั้น generate3 ต้องไม่อ้างอิงโดยตรงถึง A
class A {
name = 'instance of A';
static generate3() { return [new A(),new A(),new A()]; }
}
class B extends A {
name = 'instance of B';
}
class C extends A {
name = 'instance of C';
}
console.log(`A: ${A.generate3().map(x => x.name)}`);
console.log(`B: ${B.generate3().map(x => x.name)}`);
console.log(`C: ${C.generate3().map(x => x.name)}`);ทั้งสามอันนี้บันทึก "instance of A" สามครั้ง
ในการเขียนฟังก์ชัน generate3 ที่รับคลาส Type extends A และส่งคืนอินสแตนซ์สามตัวของ Type คุณสามารถทำได้ดังนี้:
class A {
name = 'instance of A';
}
class B extends A {
name = 'instance of B';
}
class C extends A {
name = 'instance of C';
}
interface Constructor<Type extends A> {
new (): Type;
}
function generate3<Type extends A>(constructor: Constructor<Type>): [Type, Type, Type] {
return [new constructor(), new constructor(), new constructor()];
}
console.log(`A: ${generate3(A).map(x => x.name)}`);
console.log(`B: ${generate3(B).map(x => x.name)}`);
console.log(`C: ${generate3(C).map(x => x.name)}`);
(รันใน TypeScript playground)
นี่พิมพ์ว่า:
"A: ตัวอย่างของ A, ตัวอย่างของ A, ตัวอย่างของ A"
"B: ตัวอย่างของ B, ตัวอย่างของ B, ตัวอย่างของ B"
"C: ตัวอย่างของ C, ตัวอย่างของ C, ตัวอย่างของ C"
ประเภทเป็นไปตามที่คาดไว้:
const c: C = generate3(C)[1] // OK
const b: B = generate3(C)[1] // 'C' is not assignable to type 'B'
นั่นไม่ได้ทำให้คุณมีเมทอด generate3 แบบสแตติกบนคลาส อย่างไรก็ตาม คุณขอ B.generate3() ตัวเลือกนี้คือ generate3(B)
อีกการวนซ้ำหนึ่งครั้ง ซึ่งฉันคิดว่าน่าสนใจในเชิงทฤษฎี แต่คงแปลกเกินไปที่จะใช้ในการผลิต (เว้นแต่ว่าคุณมีเหตุผลที่ดีจริง ๆ)
class A {
name = 'instance of A';
generate3(): [typeof this, typeof this, typeof this] {
const ThisClass: Constructor<typeof this> = this.constructor as any;
return [new ThisClass(), new ThisClass(), new ThisClass()];
}
}
โค้ด generate3 สามารถใช้งานได้ดังนี้: new B().generate3() และมีลักษณะดังนี้:
- มันสร้างอินสแตนซ์ของ
Bไม่ใช่A - มันมีชนิดค่าที่ส่งกลับเป็น
[B, B, B]
ฉันไม่รู้ว่าการใช้คุณสมบัติ .constructor และการสร้างใหม่มันมีผลกระทบอะไรบ้าง TypeScript ไม่ยอมรับจนกว่าฉันจะใช้ as any ซึ่งก็ยิ่งทำให้ฉันรู้สึกว่าวิธีนี้ไม่ใช่วิธีที่ดี
เราสามารถไปไกลกว่านี้ได้ เข้าไปในพื้นที่ของ "เพียงเพราะว่าคุณทำได้ ไม่ได้หมายความว่าคุณควรทำ" เพื่อทำให้สิ่งนี้คงที่:
class A {
private generate3(): [typeof this, typeof this, typeof this] {
const ThisClass: Constructor<typeof this> = this.constructor as any;
return [new ThisClass(), new ThisClass(), new ThisClass()];
}
static generate3() {
return new this().generate3();
}
}
ตอนนี้สิ่งนี้มีคุณลักษณะที่ต้องการทั้งหมด:
- เมธอดแบบสแตติกทำงานสำหรับ
A.generate3()และเช่นเดียวกันสำหรับซับคลาสของA - ประเภทการคืนค่าถูกต้อง
- ค่าที่ส่งกลับถูกต้อง
มันมีผลข้างเคียงแปลก ๆ ที่ต้องสร้างวัตถุโบนัสใหม่แล้วทิ้งไป
