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

เจเนอริก - คลาส 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
  • ประเภทการคืนค่าถูกต้อง
  • ค่าที่ส่งกลับถูกต้อง

มันมีผลข้างเคียงแปลก ๆ ที่ต้องสร้างวัตถุโบนัสใหม่แล้วทิ้งไป

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