哪种是在打字稿中定义对象并访问其值的最佳方法?

问题描述

我有一个格式的对象

export const IconSizeMap = {
  'extra-small': '0.75rem',small: '1rem',medium: '1.5rem',large: '2rem','extra-large': '4rem'
};

对打字稿不熟悉,我无法理解错误是什么

出现错误

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 'extra-small': string; small: string; medium: string; large: string; 'extra-large': string; }'.
  No index signature with a parameter of type 'string' was found on type '{ 'extra-small': string; small: string; medium: string; large: string; 'extra-large': string; }'.ts(7053)

当我尝试使用

IconSizeMap[size]

我该如何解决错误

解决方法

IconSizeMap[size]试图将字符串用作未使用字符串索引定义的对象的索引,这就是为什么会出现此错误的原因。

您的选择是:

  1. 向对象添加string index signature,或者

  2. 不要使用通用字符串将对象编入索引

您要做什么取决于您的用例。索引签名意味着TypeScript无法主动检查size是否与对象的属性匹配。但是,仅使用特定的属性名称意味着,如果起始点是可以有任何值的字符串,则将这些属性名称列出多个位置。

这里是#1的示例:

export const IconSizeMap: {[key: string]: string} = {
// −−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^
  'extra-small': '0.75rem',small: '1rem',medium: '1.5rem',large: '2rem','extra-large': '4rem'
};

Playground link

这里是#2的示例:

function getIconSize(size: string) {
    switch (size) {
        case 'extra-small':
            return IconSizeMap['extra-small'];
        case 'small':
            return IconSizeMap.small;
        case 'medium':
            return IconSizeMap.medium;
        case 'large':
            return IconSizeMap.large;
        case 'extra-large':
            return IconSizeMap['extra-large'];
        default:
            throw new Error(`Invalid 'size' for IconSizeMap: ${size}`);
    }
}

请注意在访问对象时我们如何使用特定的字符串文字,而不仅仅是任何字符串。

Playground link

,

您收到此错误,因为您的tsconfig.json已启用"noImplicitAny": true。这意味着TypeScript将查找并通知您使用未知类型的值的地方,以及您未明确告诉他应该使用哪种类型的地方。

您可以采用以下方法解决此问题:

  • 在配置中禁用noImplicitAny(但是我想这是有原因的,因此我不建议这样做)
  • 告诉TypeScript您正在使用的类型会导致错误。

在这里,TypeScript可以识别您定义的5个键(extra-smallsmallmediumlargeextra-large)中的每一个,因为您分配了一个值是string,因此可以判断它们是字符串。但是,只有这5个string是键。

当您使用“通用” string访问对象(例如我假设您的size是)时,您不确定它是否对应于5个键之一。因此,TypeScript会警告您。

一种解决方案是,告诉编译器您正在执行的操作确定,并更精确地指定size的类型。例如:

export const IconSizeMap = {
  'extra-small': '0.75rem','extra-large': '4rem'
};

let size: keyof typeof IconSizeMap; // Means "I'm sure that size is one of those strings
size = "small";

console.log(IconSizeMap[size]);

Playground link

,

自T.J. Crowder的回答是正确的,我只是添加一些注释,以防它有助于更​​好地了解正在发生的事情。

您必须记住TypeScript为JavaScript添加了静态类型。这意味着用该语言定义的每个实体都有一个确定的类型,无论是由您定义还是由TypeScript隐式推断,TypeScript都将强制遵守该类型。

在您的情况下,TypeScript隐式推断您的IconSizeMap对象是确定类型。它不是可以具有任何字符串键的通用JavaScript对象,而是具有某些确定键的对象:extra-smallsmall等。TypeScript可以从您对对象的定义中推断出这一点。

一旦TypeScript为实体推断出类型,它将为您每次使用该实体强制执行。这意味着,如果您尝试使用类似IconSizeMap['huge']之类的方法,则会因'huge'未以TypeScript推断的类型注册为有效键而抱怨。

这很容易理解,因为'huge'没有明确包含在对象中。但这会带来棘手的后果,就像您提到的问题一样。如果您使用字符串类型的变量size访问对象属性,那么TypeScript也会抱怨,因为size的值可能是未注册为对象键之一的字符串。这就是T.J.解决方案的原因:

  1. 您可以将对象的类型明确定义为允许任何字符串键。
  2. 您限制引用对象键的字符串变量的类型,以使它们不能是任何字符串,而只能是对象中包含的字符串之一。

我想第二种解决方案就是您所需要的,因为您的对象似乎是固定的映射,因此不允许进一步的字符串键。您可以让TypeScript隐式地推断出这一点,就像T.J的解决方案中那样,该解决方案使用仅带有预期字符串文字的开关。

但是,在这种情况下,有时我会明确定义包含这些字符串文字的并集类型。例如:

type IconSize = 'extra-small' | 'small' | 'medium' | 'large ' | 'extra-large';

然后我可以用该类型显式定义变量,而TypeScript将帮助我避免为该变量分配不同的字符串值:

let size: IconSize;

这意味着TypeScript将排除尝试将其他字符串值设置为size的任何尝试。由于size将具有预期值之一,因此下次尝试IconSizeMap[size] TypeScript时不会抱怨。