打字稿:使用通用函数获取嵌套对象的值
Typescript: Get value of nested object using a generic function
我想使用通用函数访问打字稿中嵌套对象的值。
(TL;DR:在 post 末尾带有 link 的游乐场版本)。
设置
以下设置。我想通过连字 (CSS) 访问图标。另外我想给所有的图标一个符号名称,所以同一个图标的连字和符号名称可以不同,例如:
symbolic name: 'home'
ligature : 'icon-home'
还可以添加自定义图标字体以扩展图标集。因此必须编辑打字稿。为了防止名称冲突并使这种扩展成为可能,我为图标定义了一个名为 icon-source
.
的命名空间
例如:用icon-source="car"
调用engine
returns engine
,用[调用engine
=24=] returns 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',
},
};
访问值的函数
然后有下面的函数来访问全局对象的值。如果图标/图标源组合存在,该函数应该 return 图标的值(连字)。否则函数 returns 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;
}
这有效但是:
函数的 returned 类型是 ListOfStandardIcons | ListOfSource1Icons | ListOfSource2Icons | undefined
(参见 ts playground)。我预计 string
为 returned 类型。
假设我用 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]
我知道我正在 returning T[K] | undefined
。我想 return T[K][S] | undefined
但是 returned 类型总是 undefined
(根据 TS playground)。
任何人都知道我如何处理这个 returned 类型是 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;
}
但是我 运行 在同一个问题中函数总是 return 类型 undefined
(returned 值是正确的)。
// 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
新游乐场
您需要做的就是重载您的函数:
console.clear();
/**
* Defines all available icon sources.
*/
const enum GlobalIconSources {
'standard' = 'standard',
'airplaine' = 'airplaine',
'car' = 'car',
}
/**
* Defines standard icons with corresponding type.
*/
const enum GlobalIconSourcesStandard {
'none' = 'none',
'home' = 'home',
'power' = 'power',
};
type ListOfStandardIcons = {
[key in keyof typeof GlobalIconSourcesStandard]: string
}
/**
* Defines custom icons with corresponding type.
*/
const enum GlobalIconSourcesSource1 {
'wing' = 'wing',
'turbine' = 'turbine',
'landing-gear' = 'landing',
};
type ListOfSource1Icons = {
[key in keyof typeof GlobalIconSourcesSource1]: string
}
/**
* Defines more custom icons with corresponding type.
*/
const enum GlobalIconSourcesSource2 {
'brakes' = 'brakes',
'engine' = 'engine',
'car-tire' = 'car-tire',
};
type ListOfSource2Icons = {
[key in keyof typeof GlobalIconSourcesSource2]: 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 = {
'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',
},
} as const;
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
function getIcon<
IconMap extends GlobalIcons,
Source extends keyof IconMap,
Test extends keyof IconMap[Source],
>(map: IconMap, test: Test, source: Source): IconMap[Source][Test]
function getIcon<
IconMap extends GlobalIcons,
Source extends keyof IconMap,
Test extends keyof IconMap[Source],
>(map: IconMap, test: Test, source: Source) {
if (typeof test === 'string' && typeof source === 'string' && typeof map === 'object') {
if (hasProperty(map, source)) {
return map[source][test]
}
}
return undefined;
}
// Test
const a = getIcon(ICONS, 'home', 'standard'); // => "icon-home"
const b = getIcon(ICONS, 'turbine', 'airplaine'); // => ap-turbine
const c = getIcon(ICONS, 'engine', 'car'); // => 'car-engine'
console.log(a);
console.log(b);
console.log(c);
我已经使用 as const
代替 ICONS
来推断整个对象。如果不允许使用 as const
,则需要将文字对象作为参数而不是引用传递。
顺便说一句,您可能根本不想使用 hasProperty
,因为只允许文字参数。
P.S。你可以在我的 blog
中找到更多关于函数参数推断的信息
我在这里倾向于将 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
的key (编译器从中推断出类型参数S
),斜线后的部分是Icons[S]
的键(编译器从中推断出类型参数I
)。这是可能的,因为从 template literal type introduces in TypeScript 4.1. Note that we need to constrain I
到 string
的推断也是为了让编译器乐于将 I
包含在模板文字类型中。本次调用的 return 类型为 Icons[S][I]
.
// call signature #2
function getIcon<I extends keyof Icons['standard']>(
list: Icons, test: I): Icons['standard'][I];
如果第一个调用签名不是,则将调用第二个调用签名,并且如果您传入 test
参数,该参数是 Icons['standard']
的键,编译器会从中推断出类型参数 I
。它 returns Icons['standard'][I]
。此调用签名的行为类似于第一个调用签名,其中 S
参数已指定为 "standard"
.
// call signature #3, optional
function getIcon(list: Icons, test: string): undefined;
如果前两个不是,则调用最后一个调用签名,它接受 test
和 returns undefined
的任何 string
值。这是编译器失败时尝试匹配调用签名但失败时发生的默认行为。这在技术上是您要求的,但我认为 不 包含此调用签名可能更好。
如果您将其注释掉,那么当有人调用 getIcon(icons, "someRandomCrazyString")
时,编译器将警告您正在调用 undefined
,而不是允许它和 returning undefined
17=]错了。这是静态类型系统吸引力的一部分;在有机会在 运行 时间执行之前捕获此类不需要的代码。
总之,一旦你定义了那些调用签名,你就可以实现这个功能了。下面的实现与您的几乎相同,只是它不需要做很多 运行 时间检查。如果调用 getIcon()
的人正在编写 TypeScript,那么他们将收到警告,例如,如果他们为 test
传递非 string
值,例如 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 the types of icons
and input1
, etc. Type annotations (of non-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"
而不是string
或unknown
。如果你注解为string
或unknown
,编译器将不知道getIcon()
会return,或者不让你调用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
识别为有效输入。在前一种情况下,input4
和 input5
被接受,undefined
被 return 编辑,而在后一种情况下,input4
和 input5
被接受带有红色波浪线的下划线,并警告您没有 getIcon()
的重载与该调用匹配。
我想使用通用函数访问打字稿中嵌套对象的值。
(TL;DR:在 post 末尾带有 link 的游乐场版本)。
设置
以下设置。我想通过连字 (CSS) 访问图标。另外我想给所有的图标一个符号名称,所以同一个图标的连字和符号名称可以不同,例如:
symbolic name: 'home'
ligature : 'icon-home'
还可以添加自定义图标字体以扩展图标集。因此必须编辑打字稿。为了防止名称冲突并使这种扩展成为可能,我为图标定义了一个名为 icon-source
.
例如:用icon-source="car"
调用engine
returns engine
,用[调用engine
=24=] returns 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',
},
};
访问值的函数
然后有下面的函数来访问全局对象的值。如果图标/图标源组合存在,该函数应该 return 图标的值(连字)。否则函数 returns 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;
}
这有效但是:
函数的 returned 类型是 ListOfStandardIcons | ListOfSource1Icons | ListOfSource2Icons | undefined
(参见 ts playground)。我预计 string
为 returned 类型。
假设我用 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]
我知道我正在 returning T[K] | undefined
。我想 return T[K][S] | undefined
但是 returned 类型总是 undefined
(根据 TS playground)。
任何人都知道我如何处理这个 returned 类型是 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;
}
但是我 运行 在同一个问题中函数总是 return 类型 undefined
(returned 值是正确的)。
// 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
新游乐场
您需要做的就是重载您的函数:
console.clear();
/**
* Defines all available icon sources.
*/
const enum GlobalIconSources {
'standard' = 'standard',
'airplaine' = 'airplaine',
'car' = 'car',
}
/**
* Defines standard icons with corresponding type.
*/
const enum GlobalIconSourcesStandard {
'none' = 'none',
'home' = 'home',
'power' = 'power',
};
type ListOfStandardIcons = {
[key in keyof typeof GlobalIconSourcesStandard]: string
}
/**
* Defines custom icons with corresponding type.
*/
const enum GlobalIconSourcesSource1 {
'wing' = 'wing',
'turbine' = 'turbine',
'landing-gear' = 'landing',
};
type ListOfSource1Icons = {
[key in keyof typeof GlobalIconSourcesSource1]: string
}
/**
* Defines more custom icons with corresponding type.
*/
const enum GlobalIconSourcesSource2 {
'brakes' = 'brakes',
'engine' = 'engine',
'car-tire' = 'car-tire',
};
type ListOfSource2Icons = {
[key in keyof typeof GlobalIconSourcesSource2]: 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 = {
'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',
},
} as const;
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
function getIcon<
IconMap extends GlobalIcons,
Source extends keyof IconMap,
Test extends keyof IconMap[Source],
>(map: IconMap, test: Test, source: Source): IconMap[Source][Test]
function getIcon<
IconMap extends GlobalIcons,
Source extends keyof IconMap,
Test extends keyof IconMap[Source],
>(map: IconMap, test: Test, source: Source) {
if (typeof test === 'string' && typeof source === 'string' && typeof map === 'object') {
if (hasProperty(map, source)) {
return map[source][test]
}
}
return undefined;
}
// Test
const a = getIcon(ICONS, 'home', 'standard'); // => "icon-home"
const b = getIcon(ICONS, 'turbine', 'airplaine'); // => ap-turbine
const c = getIcon(ICONS, 'engine', 'car'); // => 'car-engine'
console.log(a);
console.log(b);
console.log(c);
我已经使用 as const
代替 ICONS
来推断整个对象。如果不允许使用 as const
,则需要将文字对象作为参数而不是引用传递。
顺便说一句,您可能根本不想使用 hasProperty
,因为只允许文字参数。
P.S。你可以在我的 blog
中找到更多关于函数参数推断的信息我在这里倾向于将 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
的key (编译器从中推断出类型参数S
),斜线后的部分是Icons[S]
的键(编译器从中推断出类型参数I
)。这是可能的,因为从 template literal type introduces in TypeScript 4.1. Note that we need to constrain I
到 string
的推断也是为了让编译器乐于将 I
包含在模板文字类型中。本次调用的 return 类型为 Icons[S][I]
.
// call signature #2
function getIcon<I extends keyof Icons['standard']>(
list: Icons, test: I): Icons['standard'][I];
如果第一个调用签名不是,则将调用第二个调用签名,并且如果您传入 test
参数,该参数是 Icons['standard']
的键,编译器会从中推断出类型参数 I
。它 returns Icons['standard'][I]
。此调用签名的行为类似于第一个调用签名,其中 S
参数已指定为 "standard"
.
// call signature #3, optional
function getIcon(list: Icons, test: string): undefined;
如果前两个不是,则调用最后一个调用签名,它接受 test
和 returns undefined
的任何 string
值。这是编译器失败时尝试匹配调用签名但失败时发生的默认行为。这在技术上是您要求的,但我认为 不 包含此调用签名可能更好。
如果您将其注释掉,那么当有人调用 getIcon(icons, "someRandomCrazyString")
时,编译器将警告您正在调用 undefined
,而不是允许它和 returning undefined
17=]错了。这是静态类型系统吸引力的一部分;在有机会在 运行 时间执行之前捕获此类不需要的代码。
总之,一旦你定义了那些调用签名,你就可以实现这个功能了。下面的实现与您的几乎相同,只是它不需要做很多 运行 时间检查。如果调用 getIcon()
的人正在编写 TypeScript,那么他们将收到警告,例如,如果他们为 test
传递非 string
值,例如 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 the types of icons
and input1
, etc. Type annotations (of non-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"
而不是string
或unknown
。如果你注解为string
或unknown
,编译器将不知道getIcon()
会return,或者不让你调用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
识别为有效输入。在前一种情况下,input4
和 input5
被接受,undefined
被 return 编辑,而在后一种情况下,input4
和 input5
被接受带有红色波浪线的下划线,并警告您没有 getIcon()
的重载与该调用匹配。