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

วิธีอ้างอิงประเภทของตัวเองในอินเทอร์เฟซ Typescript (สำหรับอินเทอร์เฟซ IClonable)


ฉันต้องการอินเทอร์เฟซ IClonable ที่กำหนดสมาชิก clone() ซึ่งคืนค่าตัวอย่างของคลาสที่ได้ทำการใช้งานมัน

ถ้าเป็นไปได้ ฉันจะแสดงให้เห็นได้อย่างไรว่า ประเภทค่าที่ส่งกลับของ clone() จะเหมือนกับคลาสที่ถูกเรียกใช้งาน

interface IClonable {
    clone(): ???
}

ฉันรู้ว่าฉันสามารถทำสิ่งนี้ด้วยเจเนอริกแบบด้านล่างได้ แต่ดูเหมือนจะเยิ่นเย้อเกินไป

interface IClonable<T> {
    clone(): T
}

คำตอบ #1

อัปเดต 11 ตุลาคม 2022

class B extends A {
  foo() {
    console.log("Foobar");
  }
}

const b = new B(3).clone() // type: B ???
b.foo(); // RuntimeError: b.foo is not a function 

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

มีปัญหาที่นี่ซึ่งจะไม่ถูกตรวจพบในขณะคอมไพล์ และจะเห็นได้เฉพาะเวลารันไทม์เท่านั้น ดังนั้น ฉันจึงเสนอทางเลือกอีกหนึ่งทาง:

วิธีแก้ปัญหา

interface ICloneable {
    clone(): ThisType<this>;
}

class A implements ICloneable {
  constructor(readonly a: number){}

  clone() {
    return new A(this.a);
  }
}

การใช้งาน :

const a = new A(42).clone(); // type: A

class B extends A {
  foo() {
    console.log("Foobar");
  }
}

const b = new B(3).clone() // type: A

// @ts-expect-error
b.foo();

โดยการใช้ประเภท this แบบมีบริบท ประเภทค่าที่ส่งกลับสำหรับ clone จะถูกผูกกับบริบทที่มันถูกประกาศไว้ และไม่ใช่บริบทที่มันถูกเรียกใช้งาน

ตอนนี้ถ้าเราลบคอมเมนต์ // @ts-expect-error ออก TypeScript จะจับข้อผิดพลาดนี้ในขณะรันไทม์ เพราะเมธอด clone ที่เรามีใน B ไม่ได้ส่งค่ากลับเป็นชนิด B


คำตอบก่อนหน้า:

ใช้ this (แบบ polymorphic) เป็นชนิดค่าที่ส่งกลับ:

interface ICloneable {
    clone(): this;
}

ในคลาส ประเภทพิเศษที่เรียกว่า this จะอ้างอิงแบบไดนามิกไปยังประเภทของคลาสปัจจุบัน

การใช้งาน :

class A implements ICloneable {
  constructor(readonly a: number){}

  clone() {
    return new A(this.a) as this;
  }
}

คำตอบ #2

คุณพูดถูก การใช้เจนเนอริกเป็นวิธีที่จะทำ ถึงแม้ว่ามันจะเยิ่นเย้อ

คุณยังสามารถ:

interface IClonable {
    clone(): any;
}

หรือ

interface IClonable {
    clone(): any;
    clone<T>(): T;
}

หรือ

interface IClonable {
    clone(): IClonable;
}

แต่การใช้เจนเนอริกน่าจะเป็นวิธีที่ดีที่สุดที่จะใช้


แก้ไข

@Silvermind ความคิดเห็นด้านล่างทำให้ฉันต้องตรวจสอบโค้ดที่แนะนำของ clone<T>(): T และฉันผิดพลาดเพราะมันคอมไพล์ไม่ได้
ก่อนอื่น นี่คือโค้ดเพื่อแสดงสิ่งที่ฉันหมายถึง:

class Point implements IClonable {
    private x: number;
    private y: number;
    
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    
    public clone<T>(): T {
        return <T> new Point(this.x, this.y);
    }
}

var p1 = new Point(1, 2);
var p3 = p1.clone<Point>();

โดยพื้นฐานแล้วเพื่อทำการแคสต์ในเมธอด clone แทนการแคสต์ค่าที่ส่งกลับมา
แต่ <T> new Point(this.x, this.y) ให้ผลลัพธ์ดังนี้:

ทั้งชนิด 'Point' และชนิด 'T' ไม่สามารถกำหนดค่าให้กันและกันได้

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