考虑一个类型记录接口,它描述了一个对象具有一个返回相同类型的对象(或者可能是一个可以分配给它自己的类型的对象)的.clone()方法的约束。我想象的一个例子:
// Naive definition
interface Cloneable {
clone: () => Clonable;
}
class A implements Cloneable {
a: number[];
constructor(a: number[]) {
this.a = a;
}
clone(): A {
return new A(this.a.slice()); // returns a deep copy of itself
}
}
interface RequiresCloneable<T extends Cloneable> {
//...
}
const arrayOfCloneables: Clonable[] = [new A([1,2,3])];虽然这是可行的,但是接口无法理解Cloneable应该如何运行:它允许一个类Faker,它的.clone()方法返回另一个类的克隆对象,这个对象是不可分配的,无法键入Faker。为了解决这个问题,我们可以按类型参数化Cloneable接口:
// Approach 1
interface Cloneable<T> {
clone: () => T;
}
// Approach 2
interface Cloneable<T> {
clone: () => Cloneable<T>;
}
// Approach 3. This pattern looks quite strange but this seems to work the best.
interface Cloneable<T extends Cloneable<T>> {
clone: () => T;
}然后,要将类型参数约束为cloneable,我们可以指定T extends Cloneable<T>
// Interface that requires a cloneable
interface RequiresCloneable<T extends Cloneable<T>> {
//...
}这三个参数化接口都允许类A。然而,我们失去了在不知道其实现类型的情况下指定特定值应该是可克隆的能力:
const clonableA: Cloneable<A> = new A([1]);
const clonableOfUnknownType: Cloneable<unknown>; // admits Fakers with approach 1 and 2, doesn't compile with approach 3我试着思考类型系统是如何工作的,以及在类型系统下什么是可能的,什么是不可能的。我的问题是:
Cloneable<T>接口中,哪个是最好的?最具潜在意义的最清晰、最简单、最简洁、最地道或最严格的意思。似乎第三种界面是唯一不允许冒牌货的界面。有没有更好的方法来做他们正在做的事情?interface S<T extends S<T>>的名称,或者在任何类型系统中是否有更一般的名称?发布于 2021-03-05 20:42:55
TypeScript有类型,您可以使用名为this的类型来引用您所称的“与自身相同的类型”。
interface Cloneable {
clone(): this;
}在Cloneable的子类型的值中,this类型将与该子类型相同:
interface Foo extends Cloneable {
bar: string;
}
declare const foo: Foo;
const otherFoo = foo.clone();
otherFoo.bar.toUpperCase();注意,当您尝试实际实现Cloneable时,编译器会拒绝任何试图返回与您已经拥有的this对象不同的内容。在您的class A中,会发生这样的情况:
clone() { // error! Type 'A' is not assignable to type 'this'.
return new A(this.a.slice()); // returns a deep copy of itself
}这实际上是一个很好的错误,因为您不能选择子类型的底部。有人可以来扩展A (或者提供一个A & SomethingElse类型的值),this也会随之缩小范围:
class B extends A {
z: string = "oopsie";
}
declare const b: B;
b.clone().z.toUpperCase();如果您的clone()方法在A中没有预见到可能的子类型,比如B,那么它就不会以一种类型安全的方式工作。总之,一般来说,谈论如何处理这个问题可能是一种离题,但如果您并不真正关心可能的子类型,只想让编译器接受您的实现,那么您将需要一个类型断言,并且在子类型面前要小心一些:
clone() {
return new A(this.a.slice()) as this;
}你说的另一个技巧是:
interface Cloneable<T extends Cloneable<T>> {
clone(): T;
}名为F-有界多态性,这里的"F“只是”函数“的意思。您正在绑定(在TS中我们称之为约束),类型T由另一个类型组成,它是它的一个函数,F<T>。它也被称为“递归边界”。
请注意,多态this是一种F界多态性的隐式类型,其中this可以被看作是某种“阴影”泛型类型参数。与使用显式类型参数T相比,您对T的控制更少,该参数使您能够选择子类型的底部位置:
class A implements Cloneable<A> {
/* ... */
clone() {
return new A(this.a.slice());
}
}
class B extends A {
z: string = "oopsie";
}
declare const b: B;
b.clone().z.toUpperCase() // error, no z在这里,b.clone()生成A类型的值,而不是B类型,因为A implements Clonable<A>而不是Clonable<this>。
但是这里的缺点是,您需要随身携带这个“额外”类型参数,无论您想要引用哪个Cloneable。
const arrayOfCloneables: Cloneable<A>[] = [new A([1, 2, 3])];https://stackoverflow.com/questions/66498870
复制相似问题