比如说,我有这些class &接口:
class Foo {}
interface Bar {}
我想创建这样一个type
,它具有指定类型的属性名称:
type DynamicPropertyName<T> = ... <-- ???
那么我想这样使用它:
type WithPropFoo = DynamicPropertyName<Foo>; // desire: {Foo: any}
type WithPropBar = DynamicPropertyName<Bar>; // desire: {Bar: any}
我将指定我的类型,而不是any
,这很容易。
第三条评论后的UPD
至少,也许有可能得到这个:
type WithPropFoo = DynamicPropertyName<Foo>; // desire: {type: 'Foo'}
type WithPropBar = DynamicPropertyName<Bar>; // desire: {type: 'Bar'}
const foo: WithPropFoo = {type: 'Foo'}; // ok
const foo: WithPropFoo = {type: 'Hello'}; // error
const foo: WithPropBar = {type: 'Bar'}; // ok
const foo: WithPropBar = {type: 'Hello'}; // error
抱歉,这在TypeScript中是不可能的。
接口名称,如Bar
,原则上是不可观察的。这很难找到一个权威的来源,但是如果你参考一下Microsoft/TypeScript#3060和Microsoft/TypeScript#3628的注释,你会看到一些相关的信息和推理。
A
和B
具有相同的结构,则它们是相同的类型。它们是否有不同的名称或声明并不重要。例如:interface A {x: string}
interface B {x: string}
const c = {x: "hello"};
// const c: { x: string; }
function acceptA(a: A) {}
acceptA(c); // okay
function acceptB(b: B) {
acceptA(b); // okay
}
这里,A
和B
有不同的声明位置和名称,c
的类型被推断为匿名类型。然而,当您将c
传递给acceptA()
时,编译器没有问题,也不会关心您是否将B
类型的任意值传递给acceptA()
。对于编译器来说,A
、B
和typeof C
是相同的类型。它们的显示方式可能不同,但它们不是不同的类型。
因为它们是相同的类型,那么原则上应该不可能编写任何类型函数type F<T> = ...
,使F<A>
和F<B>
和F<typeof c>
产生任何不同的类型。如果您希望DynamicPropertyName<A>
生成包含文字类型"A"
的类型,但DynamicPropertyName<B>
生成包含"B"
而不是"A"
的类型,那么您正在寻找的将违反结构类型,因此是不可能的。
嗯,嗯…有时编译器会生成违反结构类型的类型,但这些都是边缘情况,您不能依赖它们。当某些东西应该是不可观察的,而您设法观察到它时,您实际上是在欺骗编译器暴露一些内部实现细节,这些细节可能会发生变化。我正在考虑将联合转换为元组,或者尝试推断未使用的类型参数的值。有时候你可以从编译器那里得到隐藏的信息,但是你得到的是未定义的行为,而不是问题的解决方案。
现在类名有点不同,因为类构造函数有时在运行时确实有name
属性。但是在运行时,你不能指望这个属性是任何特定的东西。
有microsoft/TypeScript#43325要求class
es的更多强类型name
属性。如果这是在适当的地方,你可以这样做class Foo
。
但是,即使你能100%保证Foo.name
在运行时是"Foo"
, TypeScript中也有理由不在类型级别公开这一点。如果class Foo
有一个类型为"Foo"
的静态name
属性,那么class Baz extends Foo {}
应该有一个类型为"Baz"
的静态name
属性,对吗?但是,typeof Baz extends typeof Foo
将为假,如果类实例类型具有强类型constructor
属性,则Baz extends Foo
将为假。如果写入class Baz extends Foo {}
会导致类型Baz
没有扩展Foo
,这将是令人不快的。所有这些都用于强类型名称属性。所以这是不可能发生的。
好的,所以。编译器并不关心Foo
是否被命名为Foo
或Bar
是否被命名为Bar
。为了更好地使用TypeScript,你也不应该在意这些。您似乎想要这样做,这可能表明您有XY问题。如果您可以让编译器为Foo
类型输出"Foo"
,为Bar
类型输出"Bar"
,那么您认为可能会解决一些底层用例。由于这是不可能的,您可能需要后退一步,再次检查您的用例。
如果您希望字符串文字类型"Foo"
和"Bar"
出现在某个地方,您可能希望将它们放在代码中以:
class Foo {
fooProp = 123
readonly myName = "Foo"
}
interface Bar {
barProp: string;
myName: "Bar";
}
这里您显式地将强类型名称添加为myName
属性。这样做之后,您可以访问这些名称:
type DynamicPropertyName<T extends { myName: string }> =
{ [K in T["myName"]]: any };
type WithPropFoo = DynamicPropertyName<Foo>; // {Foo: any}
type WithPropBar = DynamicPropertyName<Bar>; // {Bar: any}
您可能觉得这是多余的,但请记住,结构类型意味着它不是多余的。类型名称Bar
是无关的,interface Qux {barProp: string myName: "Bar"}
与Bar
是相同的,所以如果您关心名称"Bar"
,它与声明的接口名称无关。
也许这对您的底层用例不起作用,但关键是您将需要以其他方式实现这些目标。好运!
Playground链接到代码