วิธีอ้างอิงประเภทของตัวเองในอินเทอร์เฟซ 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' ไม่สามารถกำหนดค่าให้กันและกันได้
