我总是用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?
添加索引签名将让TypeScript知道应该是什么类型。
在你的例子中,这将是[key: string]: string;
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: string;
[key: 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;然后需要像上面那样强制转换类型。
TypeScript 2.1引入了优雅的方法来处理这个问题。
const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];
我们可以在编译阶段通过keyof关键字(参见changelog)访问所有对象属性名。
你只需要用keyof ISomeObject替换字符串变量类型。
现在编译器知道关键变量只允许包含来自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选项)
进一步阅读,了解更多用法要点。
目前更好的解决方案是声明类型。就像
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];
创建一个接口来定义'indexer'接口
然后用该索引创建对象。
注意:这仍然会有其他答案所描述的关于强制每个项目的类型的相同问题-但这通常正是你想要的。
您可以根据需要设置泛型类型参数: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];
打印稿操场
你甚至可以在定义泛型类型时在泛型约束中使用this:
导出类SmartFormGroup<T extends IndexableObject<any>> extends FormGroup
然后类中的T可以被索引:-)
没有索引器?那就自己做吧!
我将其全局定义为定义对象签名的一种简单方法。如果需要,T可以是任何:
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 };
我通常不认为这是必要的,因为如果我有来自某处的字符串属性,我通常知道它是有效的。
我发现,如果我有很多代码需要访问索引器,并且可以在一个地方更改类型,那么这个方法特别有用。
注意:我使用的是严格模式,未知是绝对必要的。
编译后的代码将是indexer = this,这与typescript为你创建_this = this非常相似。
我有两个界面。第一个是别人的孩子。我做了以下几点:
父接口增加索引签名。
使用适当的类型使用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;
}
不需要使用ObjectIndexer<T>,也不需要更改原始对象的接口(就像大多数其他答案中建议的那样)。
你可以简单地缩小key的选项,使用下面的字符串类型:
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
这个伟大的解决方案来自于这里一个相关问题的答案。
就像你缩小到T里面包含V值的键。所以在你的例子中,to to limit to string你会这样做:
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];
你可以在这个操场上找到这个例子。