I need an IClonable
interface that defines a clone()
member, that returns an instance of the class that implemented it.
If possible, how can I indicate that the return type of clone()
will be the same as the class it is called on?
interface IClonable {
clone(): ???
}
I know I can do this with generics like below, but that seems overly verbose
interface IClonable<T> {
clone(): T
}
When writing out a type or interface in TypeScript for something that will be fed into a recursive function you could cop out and use any or you could properly define the structure. Fortunately, both type and interface allow you to be self referential in terms of defining properties.
TypeScript allows an interface to extend a class. In this case, the interface inherits the properties and methods of the class. Also, the interface can inherit the private and protected members of the class, not just the public members.
TypeScript Interface TypeTypeScript allows you to specifically type an object using an interface that can be reused by multiple objects. To create an interface, use the interface keyword followed by the interface name and the typed object.
You can extend from as many interfaces as necessary by separating the interfaces with a comma. You are not required to add any new members to the final interface and can use the extends keyword to simply combine interfaces.
The previous answer had a flaw that only shows up when the inheritance chain grows beyond our simple example. Consider this example:
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
Playground link
There is a problem here that won't be caught at compile time, and only during runtime will we see it. Therefore, I propose yet another alternative:
interface ICloneable {
clone(): ThisType<this>;
}
class A implements ICloneable {
constructor(readonly a: number){}
clone() {
return new A(this.a);
}
}
Usage:
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();
By using a contextualized this
type, the return type for clone
is bound to the context within which it was declared, and not the context within which it was called.
Now if we remove the // @ts-expect-error
comment, typescript will catch this error at runtime, because the clone method we have in B
does not return type B
.
Use (polymorphic) this
as the return type:
interface ICloneable {
clone(): this;
}
In classes, a special type called
this
refers dynamically to the type of the current class.
Usage:
class A implements ICloneable {
constructor(readonly a: number){}
clone() {
return new A(this.a) as this;
}
}
You are right, using generics is the way to do it, even if it's verbose..
You can also:
interface IClonable {
clone(): any;
}
or
interface IClonable {
clone(): any;
clone<T>(): T;
}
or
interface IClonable {
clone(): IClonable;
}
But using generics is probably the best way to go.
@Silvermind comments below made me check the suggested code of clone<T>(): T
and I was wrong as it does not compile.
First Here's a code to show what I meant:
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>();
Basically to perform the cast in the clone
method instead of casting the returned value.
But <T> new Point(this.x, this.y)
produces:
Neither type 'Point` nor type 'T' is assignable to each other
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With