如何将 属性 类型设置为 TypeScript 中类型名称的文本表示?
How to set a property type to textable representation of Type name in TypeScript?
说,我有这些 class 和界面:
class Foo {}
interface Bar {}
我想创建一个 type
具有指定类型的 属性 名称:
type DynamicPropertyName<T> = ... <-- ???
那我想这样用:
type WithPropFoo = DynamicPropertyName<Foo>; // desire: {Foo: any}
type WithPropBar = DynamicPropertyName<Bar>; // desire: {Bar: any}
而不是 any
,我将指定我的类型,这很简单。
第三条评论后更新
至少,也许可以得到这个:
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
这样的接口名称原则上是不可观察的。很难找到这方面的权威来源,但如果您查阅 this comment on Microsoft/TypeScript#3060 and maybe this comment on Microsoft/TypeScript#3628,您会看到一些相关信息和推理。
TypeScript 的类型系统是 structural and not nominal。这意味着如果两个类型 A
和 B
具有相同的 结构 ,则它们是相同的 type。它们是否具有不同的名称或声明并不重要。例如:
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
的类型被推断为一个anonymous 类型。然而,当您将 c
传递给 acceptA()
时,编译器没有问题,它也不关心您是否将类型 B
的任意值传递给 acceptA()
。就编译器而言,A
、B
和 typeof C
是相同的类型。它们可能显示不同,但它们不是不同的类型。
因为它们是相同的类型,所以原则上应该不可能编写任何类型函数 type F<T> = ...
使得 F<A>
和 F<B>
和 F<typeof c>
结果在任何不同的类型。如果您希望 DynamicPropertyName<A>
生成包含 literal type "A"
的类型,但 DynamicPropertyName<B>
生成包含 "B"
而不是 "A"
的类型,那么怎么办您要查找的内容会违反结构类型,因此是不可能的。
嗯,呃... 有时 编译器确实会生成违反结构类型的类型,但这些都是边缘情况,您不能依赖它们。当某些东西应该是不可观察的而你设法观察到它时,你实际上是在欺骗编译器暴露一些内部实现细节,这些细节可能会改变。我特别想到 or 。有时您可以从编译器中提取隐藏信息,但您得到的是未定义的行为,而不是问题的解决方案。
现在 class 名称有点不同,因为 class 构造函数有时 do have a name
property at runtime。但是你不能指望 属性 在运行时是任何特定的东西。
有 microsoft/TypeScript#43325 要求 class
es 的更强类型的 name
属性。如果这是适当的,你也许可以为 class Foo
.
执行此操作
但即使您可以 100% 保证 Foo.name
在运行时将是 "Foo"
,TypeScript 也有理由不在类型级别公开它。如果 class Foo
有一个类型为 "Foo"
的静态 name
属性,那么 class Baz extends Foo {}
大概会有一个类型为静态 name
属性 "Baz"
,对吧?但是 typeof Baz extends typeof Foo
将是错误的,如果 class 实例类型具有强类型 constructor
属性,那么 Baz extends Foo
将是错误的 。如果写 class Baz extends Foo {}
会导致类型 Baz
不扩展 Foo
,那将是令人不愉快的。全部用于强类型名称 属性。所以这不太可能发生。
好的,就这样。编译器不关心 Foo
被命名为 Foo
或 Bar
被命名为 Bar
。并且为了更好地使用 TypeScript,您真的也不应该在意。您似乎想要这个的事实可能表明您有 an XY problem。如果可以让编译器为类型 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"
,它与接口的声明名称无关.
也许这对您的基础用例不起作用,但关键是您需要通过其他方式实现这些目标。祝你好运!
说,我有这些 class 和界面:
class Foo {}
interface Bar {}
我想创建一个 type
具有指定类型的 属性 名称:
type DynamicPropertyName<T> = ... <-- ???
那我想这样用:
type WithPropFoo = DynamicPropertyName<Foo>; // desire: {Foo: any}
type WithPropBar = DynamicPropertyName<Bar>; // desire: {Bar: any}
而不是 any
,我将指定我的类型,这很简单。
第三条评论后更新
至少,也许可以得到这个:
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
这样的接口名称原则上是不可观察的。很难找到这方面的权威来源,但如果您查阅 this comment on Microsoft/TypeScript#3060 and maybe this comment on Microsoft/TypeScript#3628,您会看到一些相关信息和推理。
TypeScript 的类型系统是 structural and not nominal。这意味着如果两个类型 A
和 B
具有相同的 结构 ,则它们是相同的 type。它们是否具有不同的名称或声明并不重要。例如:
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
的类型被推断为一个anonymous 类型。然而,当您将 c
传递给 acceptA()
时,编译器没有问题,它也不关心您是否将类型 B
的任意值传递给 acceptA()
。就编译器而言,A
、B
和 typeof C
是相同的类型。它们可能显示不同,但它们不是不同的类型。
因为它们是相同的类型,所以原则上应该不可能编写任何类型函数 type F<T> = ...
使得 F<A>
和 F<B>
和 F<typeof c>
结果在任何不同的类型。如果您希望 DynamicPropertyName<A>
生成包含 literal type "A"
的类型,但 DynamicPropertyName<B>
生成包含 "B"
而不是 "A"
的类型,那么怎么办您要查找的内容会违反结构类型,因此是不可能的。
嗯,呃... 有时 编译器确实会生成违反结构类型的类型,但这些都是边缘情况,您不能依赖它们。当某些东西应该是不可观察的而你设法观察到它时,你实际上是在欺骗编译器暴露一些内部实现细节,这些细节可能会改变。我特别想到
现在 class 名称有点不同,因为 class 构造函数有时 do have a name
property at runtime。但是你不能指望 属性 在运行时是任何特定的东西。
有 microsoft/TypeScript#43325 要求 class
es 的更强类型的 name
属性。如果这是适当的,你也许可以为 class Foo
.
但即使您可以 100% 保证 Foo.name
在运行时将是 "Foo"
,TypeScript 也有理由不在类型级别公开它。如果 class Foo
有一个类型为 "Foo"
的静态 name
属性,那么 class Baz extends Foo {}
大概会有一个类型为静态 name
属性 "Baz"
,对吧?但是 typeof Baz extends typeof Foo
将是错误的,如果 class 实例类型具有强类型 constructor
属性,那么 Baz extends Foo
将是错误的 。如果写 class Baz extends Foo {}
会导致类型 Baz
不扩展 Foo
,那将是令人不愉快的。全部用于强类型名称 属性。所以这不太可能发生。
好的,就这样。编译器不关心 Foo
被命名为 Foo
或 Bar
被命名为 Bar
。并且为了更好地使用 TypeScript,您真的也不应该在意。您似乎想要这个的事实可能表明您有 an XY problem。如果可以让编译器为类型 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"
,它与接口的声明名称无关.
也许这对您的基础用例不起作用,但关键是您需要通过其他方式实现这些目标。祝你好运!