ChatGPT解决这个技术问题 Extra ChatGPT

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

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


t
thoughtrepo

添加索引签名将使 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; 然后您需要像上面那样转换类型。


如果你的界面看起来像 interface ISomeObject { firstKey: string; secondKey:IOtherObject;我猜这是不可能的?
谢谢!将任何类型与为每个案例强制转换类型相结合似乎是一种可行的方法。
嗨,如何处理“anyObject[key:Object]['name']”?
或者说像 _obj = {};让 _dbKey = _props[key]['name']; _obj[_dbKey] = this[key];这里 _props 是 object 并且 object[key] 也将返回一个具有 name 属性的对象。
那么缩小a键的解决方法就是改变对象的接口!?这不是颠倒的世界吗……?
P
Pedro Villa Verde

避免错误的另一种方法是像这样使用演员表:

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

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

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


为避免 tslint 警告,您还可以使用:let secondValue: string = (someObject as any)[key];
@briosheje 这很有用:)
P
Piotr Lewandowski

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 进一步阅读。


但是,如果我们将 key 声明为 const key = (keyof ISomeObject) = 'second' + 'Key' 它将不起作用
S
Scott Munro

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

suppressImplicitAnyIndexErrors 为索引缺少索引签名的对象抑制 noImplicitAny 错误。


这是您不应该做的事情 - 可能您团队中的某个人已明确设置此编译器选项以使代码更加防弹!
我不同意这正是此选项的用途:允许使用 --noImplicitAny 的括号表示法。完美匹配 op 的问题。
我同意@Ghetolay。如果无法修改界面,这也是唯一的选择。例如,使用 XMLHttpRequest 等内部接口。
我也同意@Ghetolay。我很好奇这与 Pedro Villa Verde 的回答有何质的不同(除了代码不那么难看的事实)。我们都知道应该尽可能避免使用字符串访问对象属性,但我们有时会在了解风险的同时享受这种自由。
这只是权衡。选择你喜欢的:更少的错误表面积和严格的索引访问,或者有更多的错误表面积并轻松访问未知索引。 TS2.1 keyof 运算符可以帮助保持一切严格,请参阅 Piotr 的回答!
a
alsotang

使用keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]

像魅力一样工作。请解释 keyof typeof 的用法
@Dweep 使用 as keyof 当您想说您所指的密钥属于接口时。当您没有某个对象的接口并希望编译器猜测您所指的对象的类型时,请使用 as keyof typeof
实际上不要这样做。这与使用 key: any 一样错误。相反,您应该使 key 的类型实际上 BE keyof typeof cat: const key: keyof typeof cat = 'name' 这样您就不会盲目地转换字符串以使编译器安静。或者停止调用 key 一个字符串,使其类型推断:const key = 'name'。或者使用 as const 来保证类型不是字符串,以防您希望 key 是变量:let key = 'name' as const。有很多更好的选择,而不仅仅是对编译器撒谎,所以它保持安静。
@Vectorjohn key 可能来自其他地方,例如用户输入或解析的 json。
@alsotang您的代码给人一种错误的印象,即存在任何类型安全,而实际上没有,您实际上关闭了类型检查。只是说“as any”(即关闭类型检查)是一种更令人困惑的方式,因为它只是说“取任何 key 并假装它是 cat 中的键。在示例中,输入不是用户输入或 JSON,这是一个非常常见的用例,我们应该向人们展示如何正确和专业地做它,而不是围绕类型安全进行黑客攻击。
K
Karna

上面提到的“keyof”解决方案有效。但是如果变量只使用一次,例如循环一个对象等,你也可以对它进行类型转换。

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

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

像这样声明对象。

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

C
Community

创建一个接口来定义'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];

Typescript Playground

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

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

然后类内的 T 可以被索引:-)


我认为没有代表 { [key: string]: T }Dictionary 的标准“内置”界面,但如果有,请编辑此问题以删除我的 ObjectIndexer
简单且被低估的答案。
C
Community

没有索引器?那就自己做吧!

我已将其全局定义为定义对象签名的简单方法。如果需要,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,因此它与 typescript 为您创建 _this = this 时非常相似。


在某些情况下,您可以改用 Record<T> 类型 - 我现在无法研究这方面的详细信息,但对于一些有限的情况,它可能会更好。
S
Steve Brush

类似于@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)。
O
O.AbedElBaset

声明类型,其键是字符串,值可以是任何类型,然后用这种类型声明对象,lint 不会显示

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

A
Artokun

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

1)制作界面

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

2) 复印一份

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

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

<input customProp={copy} />

A
Artsiom Tymchanka

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

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

Y
Yuvraj Patil

我有两个接口。首先是别人的孩子。我做了以下工作:

在父界面中添加了索引签名。使用 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;
}


W
Wilt

无需使用 ObjectIndexer<T>,或更改原始对象的接口(如大多数其他答案中所建议的那样)。您可以使用以下命令将 key 的选项缩小为字符串类型的选项:

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


E
Ernesto

原因

您只能在索引时使用类型,这意味着您不能使用 const 来进行变量引用:

例子

type Person = { age: number; name: string; alive: boolean };

const key = "age";
type Age = Person[key];

结果

Type 'any' cannot be used as an index type.

解决方案

使用类型来引用 props

例子

type key = "age";
type Age = Person[key];

结果

type Age = number