使用TypeScript中的类型为对象键定义前缀

时间:2019-08-15 13:27:03

标签: typescript generics

我有以下接口定义一个对象,其中属性可以是两种不同的类型。

export interface OptionsA {
    name: string;
}

export interface OptionsB {
    parts: number;
}

export interface OptionsConfig {
    [key: string]: OptionsA | OptionsB;
}

这很好用,但是有一个限制,类型OptionsB的属性必须以"@"为前缀。

例如;

const example: OptionsConfig = {
    '@sample': {parts: 1},
    other: {name: 'example'}
};

因此上面的方法工作正常,但以下示例将是错误的。

const example: OptionsConfig = {
    '@sample': {parts: 1},
    other: {name: 'example'},
    '@wrong': {name: 'error'}
};

我想知道TypeScript是否有可能声明@wrong只能实现OptionsB接口,因为它具有@前缀。

或者,还有另一种方法可以实现类似的限制。

1 个答案:

答案 0 :(得分:2)

TL;DR:复制第一个代码块?

虽然在提出这个问题时这可能是不可能的,但我在寻找可以简单复制和粘贴的解决方案时遇到了这个问题。 TypeScript 4.1 gave us template literals 使这种实现成为可能。

TypeScript 操场上的完整示例是 here


首先,我们需要定义一些实用程序类型来自动为对象类型添加前缀。这可以通过以下代码块完成。如果你还不熟悉它们,我建议你先阅读 template literalconditional 类型,它们都在下面的代码中大量使用⬇️

type addPrefix<TKey, TPrefix extends string> = TKey extends string
  ? `${TPrefix}${TKey}`
  : never;

type removePrefix<TPrefixedKey, TPrefix extends string> = TPrefixedKey extends addPrefix<infer TKey, TPrefix>
  ? TKey
  : '';

type prefixedValue<TObject extends object, TPrefixedKey extends string, TPrefix extends string> = TObject extends {[K in removePrefix<TPrefixedKey, TPrefix>]: infer TValue}
  ? TValue
  : never;

type addPrefixToObject<TObject extends object, TPrefix extends string> = {
  [K in addPrefix<keyof TObject, TPrefix>]: prefixedValue<TObject, K, TPrefix>
} 
  • addPrefix 接受现有的 TKey 并向其添加 TPrefix,如果 TKey 扩展了类型字符串。如果它不扩展类型 stringnever 将作为类型返回。
  • removePrefix 接受一个 TPrefixedKey 和一个 TPrefix 并通过使用 TPrefix 检索用于创建的原始密钥从密钥中删除 infer TPrefixedKey
  • prefixedValue 接受一个以 not 为前缀的 TObject。然后在用 TValue 删除前缀后从 TPrefixedKey 推断出 removePrefix。如果成功,则返回 TValue。否则,返回一个空字符串,它仍然是一个有效的对象签名。
  • addPrefixToObject 将以上所有内容放在一起。它映射当前位于 TObject 中的所有键并为其添加前缀。使用 prefixedValue 检索该值。

如果你把它付诸行动,它似乎工作得很好:

const myConfig: OptionsConfig = {};

// Works:
myConfig.attr1 = {name: 'name'};
myConfig.attr2 = {"@parts": 1};
myConfig.attr3 = {"@parts": 1, name: 'name'};

// Error: Type '{ parts: number; }' is not assignable to type 'OptionsA | addPrefixToObject<OptionsB, "@">'.
myConfig.attr4 = {"parts": 1};

// Prints as:
// type prefixedOptionB = {
//    "@parts": number;
// }
type PrefixedOptionB = addPrefixToObject<OptionsB, '@'>;

并且自动完成也相应地工作: screenshot of autocompletion


所有这些都可以进一步优化,因此如果您有任何建议,请发表评论。祝你有个美好的一天?

TypeScript 操场上的完整示例是 here