我在考虑是否有可能在TypeScript中创建一个类型,以允许使用允许的值安全地映射配置。
鉴于我有以下接口,它充当某些配置节点的定义:
interface CategoryDefinition {
property1: string,
actions: { [key: string]: string }
}
以及实现该接口的实际配置(通常可以从文件中加载JSON,该示例只是为了更好地说明问题):
const definitions: { [key: string]: CategoryDefinition } = {
key1: {
property1: "PROPERTY_NAME_1",
actions: {
action1: "ACTION_1",
action2: "ACTION_2"
}
},
key2: {
property1: "PROPERTY_NAME_2",
actions: {
action3: "ACTION_3",
action4: "ACTION_4"
}
},
// etc...
};
该配置将映射到内部具有其他方法的对象,因此action
中的每个CategoryDefinition
都有一个来自父级的property1
,例如:
class ConfigValue {
private propertyName: string;
private action: string;
constructor(category: string, action: string) {
this.category = category;
this.action = action;
}
methondOne(): void { /* doesn't really matter what's inside */}
}
我想将这样的映射对象保留在配置对象/哈希图中,该对象具有上一示例中的definitions
键,并且每个对象都具有嵌套在Category.action
对象中的键。
export const configMapped /*: TypeImLookingFor */ = {
key1: {
action1: ConfigValue("PROPERTY_NAME_1", "ACTION_1"),
action2: ConfigValue("PROPERTY_NAME_1", "ACTION_2"),
},
key2: {
action3: ConfigValue("PROPERTY_NAME_2", "ACTION_3"),
action4: ConfigValue("PROPERTY_NAME_2", "ACTION_4"),
},
}
我应该使用哪种类型来确保这些actions
属于上一步中定义的有限有效键集?我的目标是通过以下方式使用此配置:
// valid call, `action1` is valid key for `key1`
configMapped.key1.action1.methodOne();
// invalid call
configMapped.key1.action4;
在TypeScript中甚至可能吗?它不必是复杂的类型,可以设置彼此相关的不同类型。
感谢您的帮助和意见!
答案 0 :(得分:2)
您应该能够使用mapped type来获得所需的行为。这是我编写映射的方法:
type MapConfig<T extends Record<keyof T, CategoryDefinition>> =
{ [K in keyof T]: { [P in keyof T[K]['actions']]: ConfigValue } };
然后,对于足够具体的 definitions
,您可以编写
type MappedDefinitions = MapConfig<typeof definitions>.
这可能是答案的结尾,但是我在评论中提到了一个问题。您不能做您在这里所做的事情:
const badDefinitions: { [key: string]: CategoryDefinition } = { /* config */ }
如果您将定义变量明确注释为类型{ [key: string]: CategoryDefinition }
,则您实际上已经舍弃了编译器可能具有的有关分配给它的特定键和子键的任何信息。编译器说:“好,我对badDefinitions
所了解的就是它的所有属性都是CategoryDefinition
s。”
如果您尝试将MapConfig
应用于那个,丢失的信息仍然会丢失,并且您将获得最通用的类型:{{1}的未指定字典的未指定字典} s:
ConfigValue
因此,您需要备份并让编译器推断type BadMappedDefinitions = MapConfig<typeof badDefinitions>;
/* type BadMappedDefinitions = { [x: string]: { [x: string]: ConfigValue; }; } */
的类型。如果您使用问题中的玩具示例进行此操作,那么这没问题,只需保留批注:
definitions
,或者如果您最后关心属性的确切字符串值,则可以使用const
assertion(您不在此问题中,但可能在您的实际代码中):
const definitions = { /* same as yours */ }
如果您是从静态JSON文件导入的,则可以使用--resolveJsonModule
进行此操作,但不能使用const definitions = { /* same as yours */ } as const;
进行此操作(请参见microsoft/TypeScript#32063
的功能请求)。
如果在编译代码后通过获取来加载它,则无能为力。直到您的JS运行时,编译器已经不复存在了,除非您事先知道确切的键和属性,否则您将无法使用类型注释。而且,如果您知道您不妨使用静态资源而不是获取任何东西。
假设这只是内联代码,并且让编译器推断as const
的类型,那么您可以验证definitions
的计算结果是否为所需的类型:
MappedDefinitions
并使用它,
/*
type MappedDefinitions = {
key1: {
action1: ConfigValue;
action2: ConfigValue;
};
key2: {
action3: ConfigValue;
action4: ConfigValue;
};
}
*/
根据键名为您提供允许/禁止属性访问所需的类型信息:
const configMapped: MappedDefinitions = { /* same as yours */ };
好的,希望能有所帮助;祝你好运!