TypeScript:Object.keys返回字符串[]

ama*_*iny 21 typescript

使用时Object.keys(obj),返回值为a string[],而我想要一个(keyof obj)[]

const v = {
    a: 1,
    b: 2
}

Object.keys(v).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, []);
Run Code Online (Sandbox Code Playgroud)

我有错误:

元素隐式地具有“ any”类型,因为类型为“ {{a:number; b:数字;}'没有索引签名。

带有TypeScript 3.1 strict: true。游乐场:在这里,请选中所有复选框以Options将其激活strict: true

Ben*_*arp 41

基于 Titian Cernicova-Dragomir 的回答和评论

仅当您知道对象没有额外属性时才使用类型断言(对象字面量就是这种情况,而不是对象参数就是这种情况)。

显式断言

Object.keys(obj) as Array<keyof typeof obj>
Run Code Online (Sandbox Code Playgroud)

隐藏断言

const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>
Run Code Online (Sandbox Code Playgroud)

使用getKeys代替Object.keysgetKeys是对 的引用Object.keys,但返回是按字面输入的。

讨论

TypeScript 的核心原则之一是类型检查侧重于值的形状。(参考

interface SimpleObject {
   a: string 
   b: string 
}

const x = {
   a: "article", 
   b: "bridge",
   c: "Camel" 
}

Run Code Online (Sandbox Code Playgroud)

x有资格作为 aSimpleObject因为它有它的形状。这意味着当我们看到 a 时SimpleObject,我们知道它有属性ab,但它也可能有其他属性。

const someFunction = (obj: SimpleObject) => {
    Object.keys(obj).forEach((k)=>{
        ....
    })
}

someFunction(x)
Run Code Online (Sandbox Code Playgroud)

让我们看看如果默认情况下我们按照 OP 的“字面意思”键入 Object.keys 会发生什么:

我们会得到那typeof k"a"|"b"。迭代时,实际值将是a, b, c。Typescript 通过输入 k 作为字符串来保护我们免受此类错误的影响。

类型断言正好适用于这种情况——当程序员有额外的知识时。如果你知道obj它没有额外的属性,你可以使用文字类型断言。

  • 应该选答案! (3认同)
  • 这实际上非常清晰和简洁......谢谢。 (3认同)

Tit*_*mir 18

Object.keys返回string[]。如本期所述,这是设计使然

这是故意的。TS中的类型是开放式的。因此keysof可能会小于您在运行时获得的所有属性。

有几种解决方案,最简单的一种是只使用类型断言:

const v = {
    a: 1,
    b: 2
};

var values = (Object.keys(v) as Array<keyof typeof v>).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, [] as (typeof v[keyof typeof v])[]);
Run Code Online (Sandbox Code Playgroud)

您还可以创建一个别名keysObject将返回你想要的类型:

export const v = {
    a: 1,
    b: 2
};

declare global {
    interface ObjectConstructor {
        typedKeys<T>(o: T) : Array<keyof T>
    }
}
Object.typedKeys = Object.keys as any

var values = Object.typedKeys(v).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, [] as (typeof v[keyof typeof v])[]);
Run Code Online (Sandbox Code Playgroud)

  • [在使用“typedKeys”(或任何您不拥有的东西)扩展“Object”之前,请花一些时间阅读其优点和缺点。](/sf/ask/982392631/) (6认同)
  • 您能否解释一下为什么“因此 keyof 可能会少于您在运行时获得的所有属性”。? (2认同)
  • @user2010955 这是来自 GitHub 线程的引用,我的理解是,由于您可以向 JS 的任何对象添加更多属性,因此 `keyof` 运算符不会返回与 `Objects.keys` 相同的内容 (2认同)
  • 还有 [Anders 的引用](https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208) 显示了他们的思维过程。 (2认同)
  • 正如 @EmileBergeron 指出的,您可能会考虑定义自己的本地函数,而不是扩展“Object”的本机属性。例如: `function getTypedKeys&lt;T&gt;(obj: T): Array&lt;keyof T&gt; { return Object.keys(obj) as Array&lt;keyof typeof obj&gt;; }`。然后,在通常编写“Object.keys(obj)”的地方,您可以编写“getTypedKeys(obj)”。 (2认同)

Dev*_*ode 9

1. npm install ts-extras (作者:辛德雷索胡斯)

  1. 用它:
import { objectKeys } from 'ts-extras'

objectKeys(yourObject)
Run Code Online (Sandbox Code Playgroud)

就是这样。


这是我在知道之前制作的另一个包ts-extras

import { objectKeys } from 'ts-extras'

objectKeys(yourObject)
Run Code Online (Sandbox Code Playgroud)
import { ObjectTyped } from 'object-typed'

ObjectTyped.keys({ a: 'b' })
Run Code Online (Sandbox Code Playgroud)

这将返回一个类型的数组['a']


小智 6

请参阅https://github.com/microsoft/TypeScript/issues/20503

declare const BetterObject: {
  keys<T extends {}>(object: T): (keyof T)[]
}

const icons: IconName[] = BetterObject.keys(IconMap)
Run Code Online (Sandbox Code Playgroud)

将保留键的类型而不是 string[]


Ale*_*ias 5

我完全不同意 Typescript 团队的决定......
按照他们的逻辑,Object.values应该总是返回 any,因为我们可以在运行时添加更多属性......

我认为正确的方法是创建具有可选属性的接口并在您进行时设置(或不设置)这些属性......

因此,我ObjectConstructor通过向我的项目添加一个声明文件(又名:whatever.d.ts)来简单地在本地覆盖接口,其中包含以下内容:


declare interface ObjectConstructor extends Omit<ObjectConstructor, 'keys' | 'entries'> {
    /**
     * Returns the names of the enumerable string properties and methods of an object.
     * @param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
     */
    keys<O extends any[]>(obj: O): Array<keyof O>;
    keys<O extends Record<Readonly<string>, any>>(obj: O): Array<keyof O>;
    keys(obj: object): string[];

    /**
     * Returns an array of key/values of the enumerable properties of an object
     * @param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
     */
    entries<T extends { [K: Readonly<string>]: any }>(obj: T): Array<[keyof T, T[keyof T]]>
    entries<T extends object>(obj: { [s: string]: T } | ArrayLike<T>): [string, T[keyof T]][];
    entries<T>(obj: { [s: string]: T } | ArrayLike<T>): [string, T][];
    entries(obj: {}): [string, any][];
}

declare var Object: ObjectConstructor;
Run Code Online (Sandbox Code Playgroud)

笔记:

Object.keys/Object.entries 的原始类型(object)将返回 never[] 和 [never, never][] 而不是普通的 string[] 和 [string, any][]。如果有人知道解决方案,请随时在评论中告诉我,我将编辑我的答案

const a: {} = {};
const b: object = {};
const c: {x:string, y:number} = { x: '', y: 2 };

// before
Object.keys(a) // string[]
Object.keys(b) // string[]
Object.keys(c) // string[]
Object.entries(a) // [string, unknown][]
Object.entries(b) // [string, any][]
Object.entries(c) // [string, string|number][]

// after
Object.keys(a) // never[]
Object.keys(b) // never[]
Object.keys(c) // ('x'|'y')[]
Object.entries(a) // [never, never][]
Object.entries(b) // [never, never][]
Object.entries(c) // ['x'|'y', string|number][]
Run Code Online (Sandbox Code Playgroud)

因此,请谨慎使用它...