在启用noImplicitAny标志的打字稿中编译时,如何防止出现“对象类型的索引签名隐式具有'any'类型”错误?


309

我总是用--noImplicitAny标志来编译Typescript。这很有意义,因为我希望我的类型检查尽可能严格。

我的问题是,使用以下代码,我得到了错误Index signature of object type implicitly has an 'any' type

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

需要注意的重要一点是,该想法是key变量来自应用程序中的其他位置,并且可以是对象中的任何键。

我尝试通过以下方式显式转换类型:

let secondValue: string = <string>someObject[key];

还是我的方案无法实现--noImplicitAny

Answers:


337

添加索引签名将使TypeScript知道应该是什么类型。

在你的情况下 [key: string]: string;

interface ISomeObject {
    firstKey:      string;
    secondKey:     string;
    thirdKey:      string;
    [key: string]: string;
}

但是,这也将强制所有属性类型匹配索引签名。由于所有属性都是a,string因此可以使用。

虽然索引签名是描述数组和“字典”模式的强大方法,但它们也强制所有属性与其返回类型匹配。

编辑:

如果类型不匹配,则可以使用联合类型 [key: string]: string|IOtherObject;

对于联合类型,最好让TypeScript推断类型而不是对其进行定义。

// Type of `secondValue` is `string|IOtherObject`
let secondValue = someObject[key];
// Type of `foo` is `string`
let foo = secondValue + '';

尽管如果索引签名中有很多不同的类型,可能会有些混乱。替代方法是any在签名中使用。[key: string]: any;然后,您将需要像上面一样转换类型。


并且如果您的接口看起来像接口ISomeObject {firstKey:string; secondKey:IOtherObject; 我猜这不可能吗?
贾斯珀·舒尔特

谢谢!任何类型的组合以及每种情况下的类型转换似乎是一种行之有效的方法。
贾斯珀·舒尔特

嗨,如何处理“ anyObject [key:Object] ['name']”?
Code_Crash

或者说类似_obj = {}的话;让_dbKey = _props [key] ['name']; _obj [_dbKey] = this [key]; 这里_props是object,object [key]也将返回一个具有name属性的对象。
Code_Crash

180

避免错误的另一种方法是使用如下所示的强制类型转换:

let secondValue: string = (<any>someObject)[key]; (请注意括号)

唯一的问题是,当您转换为时,这不再是类型安全的any。但是您总是可以转换回正确的类型。

ps:我使用的是打字稿1.7,不确定以前的版本。


20
为避免tslint警告,您还可以使用:let secondValue: string = (someObject as any)[key];
briosheje

96

TypeScript 2.1引入了优雅的方式来处理此问题。

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

我们可以在编译阶段通过keyof关键字访问所有对象属性名称(请参见changelog)。

您只需要用替换string变量类型keyof ISomeObject。现在,编译器知道key允许变量仅包含来自的属性名称ISomeObject

完整示例:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   number;
}

const someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   3
};

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

// You can mix types in interface, keyof will know which types you refer to.
const keyNumber: (keyof ISomeObject) = 'thirdKey';
const numberValue: number = someObject[keyNumber];

实时代码 typescriptlang.org(设置noImplicitAny选项)

进一步阅读更多keyof用法


6
但是,如果我们将其声明keyconst key = (keyof ISomeObject)=“ second” +“ Key” ,将无法正常工作
令人失望的

55

以下tsconfig设置将允许您忽略这些错误-将其设置为true。

inhibitorImplicitAnyIndexErrors

禁止为缺少索引签名的对象建立索引的noImplicitAny错误。


14
那是您不应该做的事情-可能是您团队中的某人已经明确设置了此编译器选项,以使代码更安全!
atsu85'9

12
我不同意此选项的确切含义:允许使用括号表示法--noImplicitAny。完全匹配op的问题。
Ghetolay

4
我同意@Ghetolay。如果无法修改接口,这也是唯一的选择。例如,使用的内部接口XMLHttpRequest
马尔科·罗伊

1
我也同意@Ghetolay。我很好奇这与Pedro Villa Verde的回答在质量上有何不同(除了代码不太丑的事实)。我们都知道,应尽可能避免使用字符串访问对象属性,但有时我们会在理解风险的同时享受自由。
斯蒂芬·保罗

这只是权衡。选择您喜欢的对象:更少的错误表面积和严格的索引访问权限,或者更大的错误表面积并轻松访问未知索引。TS2.1 keyof操作员可以帮助您确保一切严格,请参阅Piotr的答案!
trusktr

24

上面提到的“ keyof”解决方案有效。但是,如果变量仅使用一次(例如遍历对象等),则也可以进行类型转换。

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}

谢谢。当迭代另一个对象的键时,这适用于任意键访问。
bucabay

18

采用 keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]

7

与@Piotr Lewandowski的答案类似,但在范围内forEach

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });

您是如何使它工作的?我想同样的事情(TS 3.8.3),尽管它抛出一个错误说:Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'。我的代码看起来像这样Object.keys(components).forEach((comp: Component) => {...},哪里Component是类型(如MyConfig)。
theGirrafish

6

这样声明对象。

export interface Thread {
    id:number;
    messageIds: number[];
    participants: {
        [key:number]: number
    };
}

6

没有索引器?然后自己做!

我已将其全局定义为定义对象签名的简便方法。如果需要T可以是any

type Indexer<T> = { [ key: string ]: T };

我只是添加indexer为班级成员。

indexer = this as unknown as Indexer<Fruit>;

所以我最终得到了这个:

constructor(private breakpointResponsiveService: FeatureBoxBreakpointResponsiveService) {

}

apple: Fruit<string>;
pear: Fruit<string>;

// just a reference to 'this' at runtime
indexer = this as unknown as Indexer<Fruit>;

something() {

    this.indexer['apple'] = ...    // typed as Fruit

这样做的好处是您可以找回正确的类型-使用的许多解决方案<any>都会为您丢失键入内容。请记住,这不会执行任何运行时验证。如果您不确定某物是否存在,则仍然需要检查该物是否存在。

如果您要过于谨慎,并且正在使用strict,可以执行此操作以显示可能需要进行显式未定义检查的所有位置:

type OptionalIndexed<T> = { [ key: string ]: T | undefined };

我通常认为这不是必需的,因为如果我从某处获得字符串属性,通常我就知道它是有效的。

如果我有很多需要访问索引器的代码,并且输入可以在一个地方更改,我发现这种方法特别有用。

注意:我正在使用strict模式,unknown绝对是必要的。

编译后的代码将是indexer = this,因此它与_this = this为您创建typescript时非常相似。


1
在某些情况下,您可能可以使用Record<T>类型来代替-我目前无法研究此类型的详细信息,但在某些有限的情况下,它可能会更好。
Simon_Weaver

5

创建一个接口来定义“索引器”接口

然后使用该索引创建对象。

注意:在执行每个项目的类型时,其他答案也将描述同样的问题-但这通常正是您想要的。

您可以根据需要设置通用类型参数: ObjectIndexer< Dog | Cat>

// this should be global somewhere, or you may already be 
// using a library that provides such a type
export interface ObjectIndexer<T> {
  [id: string]: T;
}

interface ISomeObject extends ObjectIndexer<string>
{
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

打字稿游乐场


定义泛型时,甚至可以在泛型约束中使用它:

export class SmartFormGroup<T extends IndexableObject<any>> extends FormGroup

然后T可以在类内部进行索引:-)


我认为Dictionary该代表没有标准的“内置”界面{ [key: string]: T },但是如果有的话,请编辑此问题以删除我的ObjectIndexer
Simon_Weaver

3

声明其键为字符串且值可以是任意值的类型,然后使用该类型声明对象,并且不会显示皮棉

type MyType = {[key: string]: any};

所以你的代码将是

type ISomeType = {[key: string]: any};

    let someObject: ISomeType = {
        firstKey:   'firstValue',
        secondKey:  'secondValue',
        thirdKey:   'thirdValue'
    };

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];

1

在今天,更好的解决方案是声明类型。喜欢

enum SomeObjectKeys {
    firstKey = 'firstKey',
    secondKey = 'secondKey',
    thirdKey = 'thirdKey',
}

let someObject: Record<SomeObjectKeys, string> = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue',
};

let key: SomeObjectKeys = 'secondKey';

let secondValue: string = someObject[key];

1

使用Typescript 3.1可以找到的最简单的解决方案 3个步骤:

1)制作界面

interface IOriginal {
    original: { [key: string]: any }
}

2)制作打字副本

let copy: IOriginal = (original as any)[key];

3)在任何地方使用(包括JSX)

<input customProp={copy} />
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.