英文:
Use class name to name interface properties
问题
I want to use Class.name
as a variable to name my properties.
class MyClass {
readonly a: {b: number} = { b: 123 };
constructor() {
this.a.b = 1;
}
}
// try1
interface Actions {
[MyClass.name + '_action1']: string,
[MyClass.name + '_action2']: number
}
interface Actions2 {
MyClass_action1: string,
MyClass_action2: number
}
// expect a and b to behave the same
const a: Actions2 = {
MyClass_action1: '4',
MyClass_action2: 1
}
const b: Actions = {
MyClass_action1: '4',
MyClass_action2: 1
}
Is this even possible?
英文:
I think this TS Playground shows what Im trying to achieve:
I want to use Class.name
as a variable to name my properties.
class MyClass {
readonly a: {b: number} = { b: 123 };
constructor() {
this.a.b = 1;
}
}
// try1
interface Actions {
[MyClass.name + '_action1']: string,
[MyClass.name + '_action2']: number
}
interface Actions2 {
MyClass_action1: string,
MyClass_action2: number
}
// expect a and b to behave the same
const a: Actions2 = {
MyClass_action1: '4',
MyClass_action2: 1
}
const b: Actions = {
MyClass_action1: '4',
MyClass_action2: 1
}
Is this even possible?
答案1
得分: 1
这是没有解决方法的。
类构造函数的 name
属性的类型是 string
,继承自 Function 接口。编译器不知道 name
的 string
字面类型。请参见 microsoft/TypeScript#43325 以及相关的问题。
假设我们确切地知道在运行时,class Foo {⋯}
的 Foo.name === "Foo"
(这意味着我们不担心奇怪的边界情况,例如被静态属性覆盖或者类被压缩或混淆等情况),TypeScript 仍然无法轻松地允许 Foo.name
的类型为 "Foo"
。这是因为类的静态部分形成了一个类型层次结构,所以如果你有 class Bar extends Foo {⋯}
,那么 TypeScript 期望 Bar.name
可以赋值给 Foo.name
。因此,如果 Foo.name
的类型是 "Foo"
,那么 Bar.name
也需要是 "Foo"
的类型。但在运行时这几乎不太可能成立。基本上,围绕静态继承存在一大堆问题,TypeScript 大多数情况下试图避免,导致类型没有人们期望的那么强。
有可能在将来,JavaScript 会引入像 C# 中的 nameof
操作符,然后 TypeScript 将支持它(参见 microsoft/TypeScript#1579),然后我们可以以某种官方支持的方式从 TypeScript 类型系统中提取类的名称。
但在这些发生之前,你必须自己指定它。你可以通过手动合并信息来做到这一点,就像这样:
declare namespace MyClass {
export const name: "MyClass"
}
然后,你可以通过模板文字类型来定义你的键(这是在类型级别获取字符串连接的唯一方法):
const action1 = `${MyClass.name}_action1` as const;
const action2 = `${MyClass.name}_action2` as const;
interface Actions {
[action1]: string,
[action2]: number
}
这可能或可能不满足你的需求,但现在要么像这样做一些事情,要么放弃。
英文:
This isn't possible without workarounds.
The name
property of class constructors is of type string
, as inherited from the Function
interface. The compiler doesn't know the string
literal type of the name
. See microsoft/TypeScript#43325 and the issues to which it links.
Assuming that we know for sure that at runtime, a class Foo {⋯}
will have Foo.name === "Foo"
(meaning that we don't worry about weird edge cases where it is overridden by a static
property or where the class is minified or mangled, etc), TypeScript still can't easily allow the type of Foo.name
to be "Foo"
. That's because the static side of a class forms a type hierarchy, so if you have class Bar extends Foo {⋯}
, then TypeScript expects Bar.name
to be assignable to Foo.name
. So if Foo.name
is of type "Foo"
then Bar.name
also needs to be of type "Foo"
. But that's quite unlikely to be true at runtime. Essentially there's a whole mess surrounding static inheritance that TypeScript mostly tries to avoid, leading to somewhat less strong types than people would like.
It's vaguely possible that someday JavaScript will introduce a nameof
operator like C# has, and then TypeScript will support that (see microsoft/TypeScript#1579), and then we can extract the name of a class in the TypeScript type system in some officially supported way.
But until and unless any of these happen, you're stuck specifying it yourself. You can do so by manually merging the information in, like this:
declare namespace MyClass {
export const name: "MyClass"
}
And then you can define your keys via template literal types (that's the only way to get string concatenation at the type level):
const action1 = `${MyClass.name}_action1` as const;
const action2 = `${MyClass.name}_action2` as const;
interface Actions {
[action1]: string,
[action2]: number
}
This may or may not meet your needs, but for now, it's either do something like this, or give up.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论