【发布时间】:2021-11-09 21:25:48
【问题描述】:
我想使用泛型函数访问 typescript 中嵌套对象的值。
(TL;DR:帖子末尾带有游乐场链接的版本)。
设置
以下设置。我想通过连字 (CSS) 访问图标。另外我想给所有图标一个符号名,所以同一个图标的连字和符号名可以不同,例如:
symbolic name: 'home'
ligature : 'icon-home'
还可以添加自定义图标字体来扩展图标集合。因此必须编辑打字稿。为了防止名称冲突并使这种扩展成为可能,我为图标定义了一个命名空间,名为icon-source。
例如:用icon-source="car"调用engine返回engine,用icon-source="airplaine"调用engine返回turbine。
我的方法
所以我们假设有 3 个已定义的图标集,其中 icon-set="standard" 是默认情况。
首先我定义了一个enum(字符串),其中包括所有可用的图标集。
const enum GlobalIconSources {
'standard' = 'standard',
'airplaine' = 'airplaine',
'car' = 'car',
}
然后我为每个图标集定义另一个enum(字符串)和相应的类型。该类型的键仅限于枚举的字符串。
const enum GlobalIconSourcesStandard {
'none' = 'none',
'home' = 'home',
'power' = 'power',
};
type ListOfStandardIcons = {
[key in keyof typeof GlobalIconSourcesStandard]: string
}
之后我定义了一个接口和一个对应的包含所有图标的全局对象。
/**
* Defines interface which includes all icon sources with their types.
*/
interface GlobalIcons {
[GlobalIconSources.standard]: ListOfStandardIcons,
[GlobalIconSources.airplaine]: ListOfSource1Icons,
[GlobalIconSources.car]: ListOfSource2Icons,
}
/**
* Defines global object in which all used icon names are defined.
* [symbolic-name] : [css-name/ligature]
*/
const ICONS : GlobalIcons = {
'standard': {
'none': '',
'home': 'icon-home',
'power': 'icon-power'
},
'airplaine': {
'wing': 'ap-wing',
'turbine': 'ap-turbine',
'landing-gear': 'ap-lg',
},
'car': {
'brakes': 'car-brakes',
'engine': 'car-engine',
'car-tire': 'car-tire',
},
};
访问值的函数
然后有下面的函数来访问全局对象的值。如果存在图标/图标源组合,则该函数应返回图标的值(连字)。否则函数返回undefined。
/**
* Returns the ligature of an icon.
* @param {map} Global icon object.
* @param {test} Symbolic icon name.
* @param {source} Source, where icon is defined.
* @returns Icon ligature when icon is defined, otherwise undefined.
*/
function getIcon<T extends GlobalIcons, K extends keyof GlobalIcons, S extends keyof T[K]>(map: T, test: unknown, source: Partial<keyof typeof GlobalIconSources> = GlobalIconSources.standard) : T[K] | undefined{
if(typeof test === 'string' && typeof source === 'string' && typeof map === 'object'){
if(map.hasOwnProperty(source)){
const subMap = map[source as K];
if(subMap.hasOwnProperty(test)) return subMap[test as S];
}
}
return undefined;
}
这可行但是:
函数的返回类型是ListOfStandardIcons | ListOfSource1Icons | ListOfSource2Icons | undefined(参见 ts playground)。我希望 string 作为返回类型。
假设我用source="standard" 和test=""home 调用函数。
那么泛型应该是:
T : GlobalIcons
T[K] : ListOfStandardIcons | ListOfSource1Icons | ListOfSource2Icons (assuming K is keyof T)
T[K][S] : string (assuming K is keyof T and S is keyof T[K]
我知道我要返回T[K] | undefined。我想返回T[K][S] | undefined,但返回的类型始终是undefined(根据TS 游乐场)。
任何人知道我如何处理这个函数,返回的类型是 subobject (ListOfStandardIcons | ListOfSource1Icons | ListOfSource2Icons) 的正确类型?
TS 游乐场
编辑:设置已更改
我现在更改了设置并删除了枚举和使用对象。
// Defines all available icon sources
const iconSources = {
'standard': 'standard',
'anotherSource': 'anotherSource',
} as const;
// List of icon sources corresponding to the iconSource object
type IconSource = Partial<keyof typeof iconSources>;
// Defines list icon of the "standard" icon source
const StandardIcons = {
'none': '',
'home': 'icon-home',
'power': 'icon-power',
} as const;
// Defines list icon of the "anotherSource" icon source
const Source1Icons = {
'car': 'my-car',
'airplaine': 'my-airplaine',
} as const;
// Defines interface and global object
interface Icons {
[iconSources.standard]: { [key in keyof typeof StandardIcons]: string },
[iconSources.anotherSource]: { [key in keyof typeof Source1Icons]: string },
}
// Access icon ligatures using icons[iconSourceName][iconKey]
const icons: Icons = {
'standard': StandardIcons,
'anotherSource': Source1Icons,
};
我还更改了访问图标源的语法。现在我想传递 1 个参数,即"iconSource/iconName"。当字符串不包含/ 时,使用标准图标源。因此,现在需要 1 而不是 2 参数,但此 test 参数需要输入 unknown,因为它是迄今为止尚未验证的用户输入。
/**
* This function was copied from here: https://fettblog.eu/typescript-hasownproperty/
*/
function hasOwnProperty<X extends {}, Y extends PropertyKey>
(obj: X, prop: Y): obj is X & Record<Y, unknown> {
return obj.hasOwnProperty(prop)
}
function getIcon<L extends Icons, Source extends keyof L, Icon extends keyof L[Source]>(list: L, test: unknown): L[Source][Icon] | undefined {
if(typeof test === 'string'){
let icon: string = test;
let source: string = iconSources.standard; // Use the standard icon source, when no source is defined
if(test.indexOf('/') > -1){
const splitted = test.split('/');
source = splitted[0];
icon = splitted[1];
}
// If source is correct
if(hasOwnProperty(list, source)){
// If icon is correct return list[source][icon]
if(hasOwnProperty(list[source as Source], icon)) return list[source as Source][icon as Icon];
}
}
return undefined;
}
但我遇到了同样的问题,该函数总是返回类型undefined(返回值是正确的)。
// Test
const input1: unknown = 'home';
const input2: unknown = 'standard/none';
const input3: unknown = 'anotherSource/car';
const input4: unknown = 'abc';
const input5: unknown = 'anotherSource/abc';
// Expected results but type of all variables is undefined
const result1 = getIcon(icons, input1); // => 'icon-home' with typeof string
const result2 = getIcon(icons, input2); // => '' with typeof string
const result3 = getIcon(icons, input3); // => 'my-car' with typeof string
const result4 = getIcon(icons, input4); // => undefined
const result5 = getIcon(icons, input5); // => undefined
新游乐场
【问题讨论】:
-
也许我必须检查字符串枚举是否仍然是正确的选择。在第一次尝试中,我想使用字符串枚举来使用
Object.values(enum)访问平面对象。但是如果没有字符串枚举,也会返回索引,但是当someVarDefinedWhileRuntime可能是索引(0、1、...)时,我需要防止Object.values(enum).includes(someVarDefinedWhileRuntime : unknown) == true。 -
You can still use
Object.values()with the code I changed it to。请根据您的用例测试该代码,如果它对您有用或不适用,请回复我。 -
我更新了我的帖子,更改了设置并再次尝试,但遇到了同样的问题,即函数的返回类型未定义。在帖子底部添加了新的游乐场。也许你可以看看它。尝试使用您的解决方案并将其转移到我的新设置中,但我的尝试失败了。
-
这是一个相当大的范围变化。 this 是否满足您的需求?如果是这样,我会写一个答案。如果没有,我愿意看更多,但如果范围再次显着扩大,我不会。
标签: typescript typescript-generics