提问人:cpcallen 提问时间:9/29/2023 最后编辑:cpcallen 更新时间:9/29/2023 访问量:59
为什么类型 {[x: string]: this} 不等同于 TypeScript 类成员声明中的 Record<string, this>?
Why is the type {[x: string]: this} not equivalent to Record<string, this> in TypeScript class member declarations?
问:
背景
上周,我问了一个关于如何声明一个类成员的问题,该类成员是一个函数,其参数与类是协变的,对于它来说,使用多态这种
类型是一个完美的解决方案。
我的实际代码有一个带有成员的类,该成员是此类函数的字典。在调整解决方案时,遇到了意外的类型错误。
问题
给定以下类声明:
class C {
public dict1: {[x: string]: this} = {}; // Error: A 'this' type is available only in a non-static member of a class or interface.
public dict2: Record<string, this> = {}; // OK
public dict3: {[P in string]: this} = {}; // OK?!
}
为什么 的声明 ,使用索引签名,会引起错误dict1
“this”类型仅在类或接口的非静态成员中可用
但是看似等效的声明(使用实用程序类型)和(使用基于 Record<> 定义的不同索引签名)不会引起相同的错误?dict2
Record<>
dict3
答:
请参阅 microsoft/TypeScript#47868 以获取此问题大部分的规范答案。在 microsoft/TypeScript#4910 中实现的多态 this 类型通常是指在提及该值的范围内
this
值的类型。对于像这样的类型
interface Foo { a: this }
this
指的是 的某个子类型,所以你可以像这样实现它Foo
Foo
class Bar implements Foo { a = this; }
class Baz extends Bar { z = "abc" }
const baz = new Baz();
console.log(baz.a.a.a.z.toUpperCase()); // "ABC"
所以 、 、 等都是 类型 。真棒。baz
baz.a
baz.a.a
Baz
但是对于像这样的类型
interface Qux { a: { b: this } } // error!
// -------------------> ~~~~
// only in an interface property or a class instance member
这
指的是什么?相关范围是什么?人们可能会期望它指的是外部范围,因此它是(或一个子类型),但也许它只是指内部范围,因此它是(或子类型)。这是模棱两可的。Qux
Qux["a"]
TypeScript 通过只允许在接口属性或类实例成员中使用,而不干预范围来避免歧义。这就是为什么上面是一个错误。this
为了指定你指的是哪一个,你必须使用一些间接的,可能是泛型。如果需要外部范围,可以执行以下操作:
interface B<T> { b: T }
interface Qux { a: B<this> }
现在只能引用 的子类型。你可以用一个类来实现它,比如this
Qux
class Quux implements Qux {
a = { b: this }
z = "abc";
}
const q = new Quux();
console.log(q.a.b.a.b.z.toUpperCase()) // "ABC"
所以 、 、 等 都是 类型 。q
q.a.b
q.a.b.a.b
Quux
如果需要内部范围,可以执行以下操作:
interface B { b: this }
interface Qux { a: B }
现在只能引用 的子类型。您可以像这样实现它this
B
class Quux implements Qux {
a = { get b() { return this }, z: "abc" };
}
const q = new Quux();
console.log(q.a.b.b.b.z.toUpperCase()) // "ABC"
在这里,我使用了吸气剂,但如果你愿意,你可以用另一个类来做。反正现在 、 、 等 都是 类型 。q.a
q.a.b
q.a.b.b
Quux["a"]
对于您的示例,
class C {
dict: { [x: string]: this } = {};
}
您有一个索引签名,其行为类似于对象类型中的一系列键。索引签名可以与对象类型中的其他属性(例如 )并列。{[k: string]: string; foo: "bar"}
并且和 .是否指 的实例或仅指 的属性?看起来你打算前者。因此,我们需要某种间接性。你可以这样做,就像{ [x: string]: this }
{ a: this }
this
C
dict
C
type Dict<T> = { [x: string]: T };
class C {
dict: Dict<this> = {}; // okay
}
但是,当然,您不必编写自己的类型。Record
实用程序类型可以通过这种方式投入使用,其中本质上是相同的:Dict
Record<string, T>
class C {
dict: Record<string, this> = {}; // okay
}
最后,如果你定义一个内联的映射类型,比如
class C {
dict: { [K in string]: this } = {}; // okay
}
它也有效,因为显然映射类型不会引入自己的范围。请注意,虽然映射类型的语法看起来与索引签名的语法相似,但它们并不相同。(问题将其称为索引签名,但它不是索引签名。索引签名可以被认为是对象类型的一个部分(例如,其他属性和签名可以与它一起出现在对象类型中),而映射类型是它自己的独立事物(例如,大括号是映射类型的一部分;你不能添加其他属性)。有关更多信息,请参阅另一个问题的答案。
所以你去吧。根据编译器,您只能在范围不明确的地方使用多态。不能在嵌套对象属性或索引签名中执行此操作,但可以使用间接、泛型和明显映射的类型执行此操作。this
评论
this
interface X {a: {b: this}}
this
X
X["a"]
P