我有以下接口定义一个对象,其中属性可以是两种不同的类型。
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
接口,因为它具有@
前缀。
或者,还有另一种方法可以实现类似的限制。
答案 0 :(得分:2)
TL;DR:复制第一个代码块?
虽然在提出这个问题时这可能是不可能的,但我在寻找可以简单复制和粘贴的解决方案时遇到了这个问题。 TypeScript 4.1 gave us template literals 使这种实现成为可能。
TypeScript 操场上的完整示例是 here。
首先,我们需要定义一些实用程序类型来自动为对象类型添加前缀。这可以通过以下代码块完成。如果你还不熟悉它们,我建议你先阅读 template literal 和 conditional 类型,它们都在下面的代码中大量使用⬇️
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
扩展了类型字符串。如果它不扩展类型 string
,never
将作为类型返回。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, '@'>;
所有这些都可以进一步优化,因此如果您有任何建议,请发表评论。祝你有个美好的一天?
TypeScript 操场上的完整示例是 here。