【问题标题】:How to set a property type to textable representation of Type name in TypeScript?如何在 TypeScript 中将属性类型设置为类型名称的文本表示?
【发布时间】:2021-08-24 06:08:45
【问题描述】:

说,我有这些类和接口:

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

【问题讨论】:

  • 我发现这个答案stackoverflow.com/a/64933956 很有帮助。 @jcalz 也许您可以添加任何内容? =)
  • 虽然@-提及不直接参与问题的人并没有做任何事情,但我在这里看到了这一点,这有点令人难过。无论如何,不​​,没有将类型的 name 转换为相应的字符串文字的编程方式。可以在运行时获得"Foo",但不能在编译时获得(参见this question),但"Bar" 完全无法访问。如果需要,您需要自己将这些字符串放在某个位置。
  • 类型名称本质上是不可观察的。 TypeScript 有一个结构化的而非名义上的类型系统。如果您编写interface A {x: 0}interface B {x: 0},那么类型A 和类型B 是相同的类型,尽管有不同的声明。那么原则上,您不应该能够执行类型操作F&lt;A&gt;F&lt;B&gt; 并有两个不同的输出。如果这是有道理的,并且您对“不,抱歉”没有意见,我可以写一个答案。如果您真的想做其他事情,并且这就是您的想法,则可能是 XY 问题,您应该详细说明预期的用例。
  • @jcalz 请查看 UPD。不管这是否可能,如果你愿意,你可以写一个答案

标签: typescript typescript-generics template-literals


【解决方案1】:

抱歉,这在 TypeScript 中是不可能的。


Bar 这样的接口名称原则上是不可观察的。很难找到这方面的权威来源,但如果你咨询this comment on Microsoft/TypeScript#3060 或者this comment on Microsoft/TypeScript#3628,你会看到一些相关的信息和推理。

TypeScript 的类型系统是 structural 而不是 nominal。这意味着如果AB 两种类型具有相同的结构,那么它们就是相同的类型。它们是否有不同的名称声明并不重要。例如:

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
}    

这里,AB 有不同的声明站点和不同的名称,c 的类型被推断为 匿名 类型。然而,当你将c 传递给acceptA() 时,编译器没有问题,它也不关心你是否将B 类型的任意值传递给acceptA()。就编译器而言,ABtypeof C 是相同的类型。它们可能以不同的方式显示,但它们不是不同的类型。

因为它们是相同的类型,所以原则上应该不可能编写任何类型函数type F&lt;T&gt; = ... 使得F&lt;A&gt;F&lt;B&gt;F&lt;typeof c&gt; 导致任何不同的类型。如果您希望DynamicPropertyName&lt;A&gt; 生成包含literal type "A" 的类型,但DynamicPropertyName&lt;B&gt; 生成包含"B" 而不是"A" 的类型,那么您要查找的内容将违反结构类型,因此是不可能的.

嗯,呃……它有时会发生编译器确实产生了违反结构类型的类型,但这些是边缘情况,你不能依赖它们。当某些东西被认为是不可观察的并且你设法观察它时,你实际上是在欺骗编译器暴露一些可能会改变的内部实现细节。我特别在想converting unions to tuplestrying to infer values for unused type parameters。有时您可以从编译器中提取隐藏信息,但您得到的是未定义的行为,而不是问题的解决方案。


现在类名有点不同,因为类构造函数有时do have a name property at runtime。但是您不能依赖该属性在运行时特别是任何东西。

microsoft/TypeScript#43325 要求classes 的更强类型的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 被命名为 FooBar 被命名为 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",它与接口的声明名称无关。

也许这不适用于您的基础用例,但关键是您需要以其他方式实现这些目标。祝你好运!

Playground link to code

【讨论】:

  • 基本上,我的预测是,如果我能详细说明这一点,我将能够实现依赖属性stackoverflow.com/questions/56949513,但使用class,而不是type,因为我使用TypeORM 我有使用类
  • 我有一个字段,比如type,即enum,我还有另一个字段,比如obj,如果type === 'Foo',则为Foo类型,如果@987654400,则为Bar @...
  • 如果没有minimal reproducible example 你在谈论什么,很难知道该建议什么,但classes 自己不能实现联合。您大概可以使该类成为通用类,例如this,但同样,我需要查看一个完整的示例,其中包含成功和失败的标准。也许这应该是一个不同的问题,因为它超出了这个问题的范围。祝你好运!
  • 我明白可能不可能做我想做的事。我是 MRE tsplay.dev/wXkgJW 无论如何,谢谢你的帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-10
  • 1970-01-01
  • 1970-01-01
  • 2017-10-21
  • 1970-01-01
相关资源
最近更新 更多