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

时间:2018-10-17 13:48:16

标签: typescript

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

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

Object.keys(v).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, []);

我有错误:

  

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

带有strict: true的TypeScript 3.1。游乐场:here,请选中Options中的所有复选框以激活strict: true

7 个答案:

答案 0 :(得分:5)

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

.TextFileColumnDataTypes = arrV

将保留键的类型,而不是declare const BetterObject: { keys<T extends {}>(object: T): (keyof T)[] } const icons: IconName[] = BetterObject.keys(IconMap)

答案 1 :(得分:4)

Object.keys返回一个string[]。这是根据issue

中的描述而设计的
  

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

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

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

您还可以在keys中为Object创建别名,该别名将返回所需的类型:

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

答案 2 :(得分:2)

我完全不同意Typescript团队的决定...
按照其逻辑,Object.values应该始终返回任何值,因为我们可以在运行时添加更多属性...

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

因此,我只需在项目中添加以下内容的声明文件(aka:whatever.d.ts)即可在本地覆盖ObjectConstructor接口。


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;

注意:

原始类型(对象)的Object.keys / Object.entries将返回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][]

因此,请谨慎使用 ...

答案 3 :(得分:0)

作为一种可能的解决方案,您可以在对象上使用for..in进行迭代:

for (const key in myObject) {
  console.log(myObject[key].abc); // works!
}

正如您所说,这是行不通的:

for (const key of Object.keys(myObject)) {
  console.log(myObject[key].abc); // doesn't!
}

答案 4 :(得分:0)

您可以使用Extract实用程序类型使您的参数仅符合obj的keyof类型(即字符串)(无论如何)。


    Object.keys(obj).forEach((key: Extract<keyof typeof obj, string>) => {
      const item = obj[key]
    })

答案 5 :(得分:0)

您可以使用以下常规函数包装器

const getObjectKeys = <O extends object>(obj:O) => Object.keys(obj) as Array<keyof O>

答案 6 :(得分:0)

我喜欢@Titian的全局别名Object.typedKeys = Object.keys

这是一个精致的声明,您可以在src/index.js

的顶部添加
declare global {
  interface ObjectConstructor {
    /**
     * Object.keys, but with nice typing (e.g.`Array<keyof T>`)
     *
     * PSA: Don't mutate `yourObject`s
     *
     * Don't use numerical keys, your types will be awkward.
     * - `{  1 : 123 }` is bad
     * - `{ "1": 123 }` is... better...
     *
     * Object.keys coerces numerical keys to strings anyway.
     * See https://stackoverflow.com/a/65117465/565877
     */
    typedKeys<T extends {}>(yourObject: T): Array<keyof T>
  }
}
Object.typedKeys = Object.keys as any

将鼠标悬停在Object.typedKeys上会显示以下内容:

enter image description here

避免将此导入到任何地方都很好。这种方法避免了在整个代码库中以任何方式改变Object.keys行为 。这是一个明确的选择加入。定义Object.typedKeys的JavaScript就在TS声明的旁边。

当然,开发人员需要发现Object.typedKeys,但是,如果在整个代码库中都使用了它,发现它就不会花很长时间。

======

这是一个很好的非全局版本,您必须将其导入到任何地方,如果您具有vscode自动导入扩展名,那就可以了:

export const Object_typedKeys = Object.keys as <
  T extends object //
>(
  obj: T
) => Array<keyof T>

这两个函数都避免使用Extract<keyof T, string>,因为它们打算在整个代码库中使用,所以它可能不是最安全的默认设置(您可能想知道是否有非字符串键并处理视情况而定,或解决非字符串键的根本原因)

如果只想始终查看字符串键,可以将Array<keyof T>更改为Array<Extract<keyof T, string>>,并且在大多数情况下还是可以的。

=====

有关Extract和类型混合的更多信息:

const obj = { 
  a: "hello",
  b: "world",
  1: 100
} as const

type Keys<objType extends {}> = Array<keyof objType>
type StrKeys<objType extends {}> = Array<Extract<keyof objType, string>>

const typedObjKeys = Object.keys(obj) as Keys<typeof obj>
typedObjKeys.forEach(
  (key) => { // key is 'a' | 'b' | 1 but should be 'a' | 'b' | '1'
    // If you need to pass `key` to a function that expects a string,
    // use Extract at the last possible moment:
    giveMeString(key as Extract<typeof key, string>)
    // giveMeString will see 'a' | 'b'

    // String(key) is not as nice as Extract, since
    // giveMeString will only see `string`, not 'a' | 'b' #TS_FIX_ME

    // Note: there is no way to convert a literal number to it's literal string
    const aNumber = 1 as const
    // no matter how you convert a number to a string,
    // you can't get the type to be '1' instead of `string` (v4.1.2)
    const itsString = aNumber.toString()

    // `value` is correctly typed:
    const value = obj[key]
    // with `as const`: 'hello' | 'world' | 100
    //  w/o `as const`:  string | number
  }
)