TypeScript - 根据通用枚举或字符串列表限制抽象成员的键



我很难弄清楚如何找到合适的词来解释我想要做的事情,所以如果我混淆了术语,请道歉。我正在尝试做的是将对象/接口的键限制为属于枚举(或 string[] 列表(的键列表。这在基本层面上已经是可能的,如下所示:

enum RestrictKeysTo {
name = 'name',
age = 'age'
}
type Person = {
[K in RestrictKeysTo]?: string | number;
}
const phil: Person = {
// Allowed
age: 24,
name: 'Phillip'
// Throws error
occupation: 'developer'
};

这太好了!但是,我想以更灵活的方式使用它,其中接受的密钥列表可以通过接口或类传入,并且该列表可用于限制其他地方的密钥。我正在寻找的具体最终目标是这样的:


abstract class BaseClass {
public abstract keys: string[];
// How do I restrict ages to be keyed by keys?
public abstract ages: //...
}
class Final extends BaseClass {
public keys = ['joe', 'ralph'];
public ages = {
joe: 24,
ralph: 50
}
}

我猜如果存在解决方案,它涉及将泛型传递给基类,但我在制定细节时遇到了麻烦......例如,这是我似乎最接近的(工作(:

abstract class BaseClass<T extends string> {
public abstract ages: {
[K in T]: number
}
}
type Keys = 'joe' | 'ralph' | 'betty'
class Final extends BaseClass<Keys> {
public ages = {
joe: 24,
ralph: 50,
betty: 32
};
}

但是,这不太理想,因为我希望能够跨基本方法和填充(抽象(方法重用键。我以为我会通过构造函数传递它,但我遇到了两个问题:

  • 它要求开发人员将联合重新键入为数组
  • 我不能让它完全正确键入保护

这是一个非功能性示例,我可能很接近:

type KeyArr<T extends string> = {
[K in T]?: keyof T;
}
abstract class BaseClass<T extends string> {
public keys: KeyArr<T>;
constructor(keys: KeyArr<T>) {
this.keys = keys;
}
public abstract ages: {
[K in T]?: number
}
}
type Keys = 'joe' | 'ralph' | 'betty'
class Final extends BaseClass<Keys> {
constructor() {
// This should ideally throw an error, since it is missing 'betty', and 'joe' does not belong to Keys
// Not working - says nothing in common with KeyArr
super(['joe', 'ralph', 'joe'])
}
public ages = {
joe: 24,
ralph: 50,
betty: 24
};
}

老实说,出于某种原因,我对 TS 泛型有心理障碍 - 所以我很可能在这里缺少一些基本的东西。任何帮助不胜感激!

使用代码显示以下解决方案正常工作,并且它应该符合您的要求。

带枚举的解决方案

abstract class BaseClass<T extends string> {
public abstract keys: T[];
public abstract ages: {
[K in T]?: number
}
}
enum Keys {
Joe = "Joe",
Ralph = "Ralph",
Betty = "Betty"
}
class Final extends BaseClass<Keys> {
public keys = [Keys.Betty, Keys.Joe, Keys.Ralph];
public ages = {
[Keys.Betty]: 24
};
}
const final = new Final();
const a = final.ages[Keys.Betty] // number

如您所见,我做了几件事: 1.我正在使用枚举而不是字符串文字 2.我删除了构造函数,因为在最终类中您没有使用它的参数 3.我将键的类型更改为简单的T[]

现在为什么我做了这些更改。

字符串文字的问题是 - 在扩展类传递 ['betty', 'ralph'
  1. ] 将其推断为字符串 [],为了修复它,我需要做 - ['betty' 作为 const,'ralph' 作为 const]。由于我想避免这种情况,我选择了枚举。

  2. 我删除了构造函数,因为您的实现表明您希望在类中定义键,而不是从外部传递它们。

  3. KeyArr与简单的T[]没有区别

具有字符串文本类型的解决方案

下面实现与字符串文字形式比较:

abstract class BaseClass<T extends string> {
public abstract keys: T[];
public abstract ages: {
[K in T]?: number
}
}
type Keys = 'joe' | 'ralph' | 'betty'
class Final extends BaseClass<Keys> {
public keys = ['betty' as const, 'joe' as const]; // as const because inference showing string
public ages = {
'betty': 24
};
}
const final = new Final();
const a = final.ages.betty // number

这两种解决方案看起来都有效。

关于详尽性。

我给出的两种解决方案都允许放置并非所有键的数组。这样做是因为您的ages类型不需要每个键的值 -{[K in T]?: number}意味着它是部分记录。在我看来,如果keys不是详尽无遗的,那么整体就会有意义,但年龄会是。考虑:

abstract class BaseClass<T extends string> {
public abstract keys: T[];
public abstract ages: {
[K in T]: number // pay attention removed ?
}
}
type Keys = 'joe' | 'ralph' | 'betty'
class Final extends BaseClass<Keys> {
public keys = ['betty' as const, 'joe' as const]; // as const because inference showing string
public ages = {
'betty': 24 // error total record of all keys needs to be provided
};
}

这种差异意味着在我们的 Final 类中,我们可以定义我们需要提供哪些键。

最新更新