在编译带有noImplicitAny标志的typescript时,如何防止错误“对象类型的索引签名隐式具有'任何'类型”?

时间:2015-10-06 11:02:48

标签: typescript

我总是使用标志--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];

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

我已尝试通过以下方式明确转换类型:

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

或者--noImplicitAny无法实现我的方案?

15 个答案:

答案 0 :(得分:258)

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

在您的情况下,[key: string]: string;

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

但是,这也强制所有属性类型与索引签名匹配。由于所有属性都是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;然后你需要像上面那样投射类型。

答案 1 :(得分:164)

避免错误的另一种方法是使用这样的强制转换:

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

唯一的问题是这不再是类型安全的,因为你正在向any施放。但是你总是可以回到正确的类型。

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

答案 2 :(得分:65)

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选项)

使用more keyof usages进一步阅读。

答案 3 :(得分:51)

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

  

<强> suppressImplicitAnyIndexErrors

     

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

答案 4 :(得分:17)

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

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

答案 5 :(得分:8)

使用keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]

答案 6 :(得分:7)

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

const config: MyConfig = { ... };

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

答案 7 :(得分:6)

声明这样的对象。

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

答案 8 :(得分:5)

没有索引器?然后自己动手!

我已将其全局定义为定义对象签名的简便方法。如果需要,//= require rails-ujs //= require jquery //= require materialize //= require turbolinks //= require_tree . $(document).on('turbolinks:load', function() { $('.dropdown-trigger').dropdown(); }); 可以是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时非常相似。

答案 9 :(得分:3)

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

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

注意:仍然存在其他问题,这些问题在执行每个项目的类型方面都存在相同的问题-但这通常正是您想要的。

您可以根据需要设置通用类型参数: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];

Typescript Playground


定义通用类型时,甚至可以在通用 constraint 中使用它:

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

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

答案 10 :(得分:1)

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

1)制作界面

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

2)键入副本

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

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

<input customProp={copy} />

答案 11 :(得分:1)

声明类型,其键为字符串,值可以为任意 然后用这种类型声明对象,棉绒就不会出现

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];

答案 12 :(得分:0)

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

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];

答案 13 :(得分:0)

我有两个界面。首先是其他的孩子。我做了以下事情:

  1. 在父界面中添加了索引签名。
  2. 使用as关键字使用适当的类型。

完整代码如下:

子界面:

interface UVAmount {
  amount: number;
  price: number;
  quantity: number;
};

父界面:

interface UVItem  {
// This is index signature which compiler is complaining about.
// Here we are mentioning key will string and value will any of the types mentioned.
  [key: string]:  UVAmount | string | number | object;

  name: string;
  initial: UVAmount;
  rating: number;
  others: object;
};

反应组件:

let valueType = 'initial';

function getTotal(item: UVItem) {
// as keyword is the dealbreaker.
// If you don't use it, it will take string type by default and show errors.
  let itemValue = item[valueType] as UVAmount;

  return itemValue.price * itemValue.quantity;
}

答案 14 :(得分:0)

无需使用ObjectIndexer<T>或更改原始对象的界面(如大多数其他答案中所建议的)。 您可以使用以下命令将关键字的选项范围缩小到字符串类型:

type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];

这个很棒的解决方案来自an answer to a related question here

就像您缩小到T中容纳V值的键一样。因此,要限制为字符串,您可以这样做:

type KeysMatching<ISomeObject, string>;

在您的示例中:

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

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

let key: KeysMatching<SomeObject, string> = 'secondKey';

// secondValue narrowed to string    
let secondValue = someObject[key];

优点是您的ISomeObject现在甚至可以容纳混合类型,并且无论如何您只能将键范围缩小到字符串值,其他值类型的键将被视为无效。为了说明:

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

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


// Type '"fourthKey"' is not assignable to type 'KeysMatching<ISomeObject, string>'.(2322)
let otherKey: KeysMatching<SomeOtherObject, string> = 'fourthKey';

let fourthValue = someOtherObject[otherKey];

您找到此示例 in this playground