gpt4 book ai didi

typescript :使用泛型函数获取嵌套对象的值

转载 作者:行者123 更新时间:2023-12-04 14:50:18 24 4
gpt4 key购买 nike

我想使用泛型函数访问 typescript 中嵌套对象的值。
(TL; DR:在帖子末尾带有游乐场链接的版本)。
设置
以下设置。我想通过他们的连字 (CSS) 访问图标。另外我想给所有图标一个符号名称,所以同一个图标的连字和符号名称可以不同,例如:

symbolic name: 'home'
ligature : 'icon-home'
也可以添加自定义图标字体来扩展图标集合。因此必须编辑 typescript 。为了防止名称冲突并使这种扩展成为可能,我为图标定义了一个名为 icon-source 的命名空间。 .
例如 : 调用 engineicon-source="car"返回 engine , 调用 engineicon-source="airplaine"返回 turbine .
我的方法
所以让我们假设有 3 个定义的图标集,其中 icon-set="standard"是默认情况。
首先我定义了一个 enum (字符串)其中包括所有可用的图标集。
const enum GlobalIconSources {
'standard' = 'standard',
'airplaine' = 'airplaine',
'car' = 'car',
}
然后我为每个图标集定义另一个 enum (string) 和相应的类型。该类型的键仅限于枚举的字符串。
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操场)。我期待 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 游乐场)。
任何人都知道我如何处理这个函数,即返回的类型是子对象的正确类型( ListOfStandardIcons | ListOfSource1Icons | ListOfSource2Icons )?
TS游乐场
TypeScript Playground Demo
编辑:设置已更改
我现在更改了设置并删除了枚举和使用对象。
// 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
新游乐场
New Playground

最佳答案

我在这里的倾向是声明 getIcon()作为 overloaded function具有多个调用签名,对应于函数的多种调用方式。以下是调用签名:

// call signature #1
function getIcon<S extends keyof Icons, I extends string & keyof Icons[S]>(
list: Icons, test: `${S}/${I}`): Icons[S][I];
如果您传入 test,将调用第一个调用签名包含斜杠( / )字符的参数,其中斜杠前的部分是 Icons 的键(编译器从中推断出类型参数 S ),斜线后面的部分是 Icons[S] 的键(编译器从中推断类型参数 I )。由于来自 template literal type 的推断,这是可能的在 TypeScript 4.1 中引入。请注意,我们需要 constrain Istring也是为了让编译器对包含 I 感到满意在模板文字类型中。此调用的返回类型为 Icons[S][I] .
// call signature #2
function getIcon<I extends keyof Icons['standard']>(
list: Icons, test: I): Icons['standard'][I];
如果第一个调用签名不是,并且如果您传入 test,则将调用第二个调用签名。参数是 Icons['standard'] 的键,编译器从中推断出类型参数 I .它返回 Icons['standard'][I] .此调用签名的行为类似于带有 S 的第一个调用签名。参数已指定为 "standard" .
// call signature #3, optional
function getIcon(list: Icons, test: string): undefined;
如果前两个不是,则调用最后一个调用签名,并且它接受任何 string test 的值, 并返回 undefined .这是编译器失败时发生的默认行为,尝试匹配调用签名但失败。从技术上讲,这就是您所要求的,但我认为最好不要包含此调用签名。
如果你把它注释掉,那么当有人打电话时 getIcon(icons, "someRandomCrazyString") , 而不是允许它返回 undefined ,编译器会警告你你正在调用 getIcon()错误的。这是静态类型系统吸引力的一部分;在有机会在运行时执行之前捕获此类不需要的代码。

无论如何,一旦定义了这些调用签名,就可以实现该功能。下面的实现与您的几乎相同,只是它不需要做太多的运行时检查。如果人们打电话 getIcon()正在编写 TypeScript,然后他们会收到警告,例如,如果他们传入非 string test 的值喜欢 getIcon(icons, 123) .在这里进行运行时检查的唯一原因是您是否担心有人会运行 getIcon()来自未经类型检查的 JavaScript 代码。由你决定:
// implementation
function getIcon(list: any, test: string) {
let source: string = iconSources.standard
let icon: string = test;
if (test.indexOf('/') > -1) {
const splitted = test.split('/');
source = splitted[0];
icon = splitted[1];
}
return list[source]?.[icon];
}

那么,让我们来测试一下。请注意,如果您不这样做,您将最快乐 annotate icons的种类和 input1等。类型注释(非 union 类型)往往会使编译器忘记任何传入的实际值:
const icons = {
'standard': StandardIcons,
'anotherSource': Source1Icons,
};
const input1 = 'home';
const input2 = 'standard/none';
const input3 = 'anotherSource/car';
const input4 = 'abc';
const input5 = 'anotherSource/abc';
这里编译器知道 input1是字符串 literal type "home"而不是 stringunknown .如果您注释为 stringunknown ,编译器不会知道什么 getIcon()会回来,不然不让你打电话 getIcon() .
好的,现在开始测试。如果您调用 getIcon(),这就是您得到的结果具有三个调用签名:
const result1 = getIcon(icons, input1); // const result1: string
const result2 = getIcon(icons, input2); // const result2: string
const result3 = getIcon(icons, input3); // const result3: string
const result4 = getIcon(icons, input4); // const result4: undefined
const result5 = getIcon(icons, input5); // const result5: undefined
如果你注释掉第三个,那么这就是你得到的:
const result1 = getIcon(icons, input1); // const result1: string
const result2 = getIcon(icons, input2); // const result2: string
const result3 = getIcon(icons, input3); // const result3: string
const result4 = getIcon(icons, input4); // compiler error!
const result5 = getIcon(icons, input5); // compiler error!
在这两种情况下,编译器都识别出 input1 , input2 , 和 input3是有效的输入。在前一种情况下, input4input5被接受并 undefined返回,在后一种情况下, input4input5用红色波浪线下划线并警告您 getIcon() 没有过载匹配那个电话。
Playground link to code

关于 typescript :使用泛型函数获取嵌套对象的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69182106/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com