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

Rea*_*lar 6 generics typescript

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

export interface OptionsA {
    name: string;
}

export interface OptionsB {
    parts: number;
}

export interface OptionsConfig {
    [key: string]: OptionsA | OptionsB;
}
Run Code Online (Sandbox Code Playgroud)

这工作正常,但有一个限制,即类型的属性OptionsB必须以"@".

例如;

const example: OptionsConfig = {
    '@sample': {parts: 1},
    other: {name: 'example'}
};
Run Code Online (Sandbox Code Playgroud)

所以上面的工作正常,但下面的例子是不正确的。

const example: OptionsConfig = {
    '@sample': {parts: 1},
    other: {name: 'example'},
    '@wrong': {name: 'error'}
};
Run Code Online (Sandbox Code Playgroud)

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

或者,是否有另一种方法来实现类似的限制。

Azi*_*ved 34

Typescript 4.1 为我们提供了按键重新映射,因此您可以执行以下操作:

type addPrefixToObject<T, P extends string> = {
  [K in keyof T as K extends string ? `${P}${K}` : never]: T[K]
}
Run Code Online (Sandbox Code Playgroud)

那么,你的OptionsConfig就变成了

export interface OptionsConfig {
    [key: string]: OptionsA | addPrefixToObject<OptionsB, '@'>;
}
Run Code Online (Sandbox Code Playgroud)

Typescript Playground中提供了示例。

  • `K in keyof T` 迭代 `T` 中的每个键并将其分配给类型 `K`。“as”子句允许您映射每个“K”键。您期望 `[K in keyof T as ${P}${K}]: T[K]` 能够工作,但这会抱怨说 `K` 可以是一个符号,并且您不能将符号与细绳。所以我们首先检查它是否是一个字符串,然后映射它。 (4认同)

Nik*_*las 13

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

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

TypeScript Playground 的完整示例位于此处


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

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>
} 
Run Code Online (Sandbox Code Playgroud)
  • addPrefix如果扩展类型字符串,则接受现有的并向其TKey添加 a 。如果它不扩展类型,则作为类型返回。TPrefixTKeystringnever
  • removePrefix接受 aTPrefixedKey和 aTPrefix并通过使用检索用于创建 的原始密钥TPrefix来从密钥中删除。inferTPrefixedKey
  • prefixedValue接受TObject没有前缀a 。然后它TValueTPrefixedKey使用 删除前缀后的进行推断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, '@'>;
Run Code Online (Sandbox Code Playgroud)

自动补全也相应地工作: 自动完成的屏幕截图


所有这些可能都可以进一步优化,所以如果您有任何建议,请发表评论。

TypeScript Playground 的完整示例位于此处


小智 5

使用类型强制执行前缀的一个快速答案(从 TS4.1 开始)-

type strWithPre = `pre-${string}`
Run Code Online (Sandbox Code Playgroud)