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

TypeScript:Object.keys返回字符串[],typescript,Typescript,使用Object.keys(obj)时,返回值是字符串[],而我需要(keyof obj)[ const v = { a: 1, b: 2 } Object.keys(v).reduce((accumulator, current) => { accumulator.push(v[current]); return accumulator; }, []); 我有一个错误: 元素隐式具有“any”类型,因为类型“{a:number;b:number;}”没

使用
Object.keys(obj)
时,返回值是
字符串[]
,而我需要
(keyof obj)[

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

Object.keys(v).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, []);
我有一个错误:

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


键入脚本3.1,带有
严格:true
。操场:,请选中
选项
中的所有复选框以激活
严格:true
对象。键
返回一个
字符串[]
。这是本节中所述的设计

这是故意的。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])[]);

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

for (const key in myObject) {
  console.log(myObject[key].abc); // works, but `key` is still just `string`
}
正如你所说,这是行不通的:

for (const key of Object.keys(myObject)) {
  console.log(myObject[key].abc); // doesn't!
}
您可以使用实用程序类型将参数仅与
obj
键一致,这些键是字符串(因此,在编码时忽略任何数字/符号)

const obj={
a:你好,
b:‘世界’,
1:123//100%有效
}//如果这是文字代码,则应在此处添加'as const'断言
//利用率
键入StringKeys=Array
//typedObjKeys在运行时将为['a','b','1']
//…但它的类型将是数组
const typedObjKeys=Object.keys(obj)作为StringKeys
typedObjKeys.forEach((键)=>{
//钥匙类型:“a”|“b”
//运行时:“a”、“b”和“1”
常量值=对象[key]
//值的类型将仅为'string',而实际上为'string | number`
})

所有的开发者都可能认为数字是关键的一个糟糕的设计决策/ bug,基于Titian Cernicova Dragomir的回答和评论 仅当您知道对象没有额外属性时才使用类型断言(这是对象文字而不是对象参数的情况)

明确断言

Object.keys(obj) as Array<keyof typeof obj>
const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>
x
符合
SimpleObject
的条件,因为它有自己的形状。这意味着当我们看到一个
SimpleObject
时,我们知道它有属性
a
b
,但它也可能有其他属性

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

someFunction(x)
让我们看看如果默认情况下,我们按照OP“字面上”的要求键入Object.keys会发生什么:

我们将得到k的
类型是
“a”|“b”
。迭代时,实际值为
a
b
c
。Typescript通过将k作为字符串键入来保护我们不受此类错误的影响

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

请参阅

declare const BetterObject:{
键(对象:T):(键of T)[]
}
常量图标:IconName[]=BetterObject.keys(IconMap)

将保留密钥类型而不是字符串[]

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

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

因此,我只是在本地重写了
ObjectConstructor
接口,向我的项目添加了一个声明文件(aka: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;
因此,谨慎使用此方法

导入方法 如果您可能在monorepo中工作,或者考虑10年的前景,那么您可能更喜欢基于导入的方法(无论如何,在vscode中导入是非常容易的,只需点击

只要把它放在某个地方,然后键入
ObjectT
hit
,当使用vscode时,它就会自动导入

type KeyOf<T> = Extract<keyof T, string>
type ValueOf<T> = T[KeyOf<T>]

/**
 * Nicely typed aliases for some `Object` Methods
 * - PSA: Don't mutate `yourObject`s
 * - Numerical keys are BAD `{ 1: 'ha!' }` may not appear in your resulting types
 * - Discussion: https://stackoverflow.com/a/65117465/565877
 */
export const ObjectTyped = {
  /**
   * Object.keys, but with nice typing (`Array<keyof T>`)
   */
  keys: Object.keys as <T extends {}>(yourObject: T) => Array<KeyOf<T>>,
  /**
   * Object.values, but with nice typing
   */
  values: Object.values as <T extends {}>(yourObject: T) => Array<ValueOf<T>>,
  /**
   * Object.entries, but with nice typing
   */
  entries: Object.entries as <T extends {}>(yourObject: T) => Array<[KeyOf<T>, ValueOf<T>]>,
  /**
   * Object.fromEntries, but with nice typing
   */
  fromEntries: Object.fromEntries as <K extends string, V>(
    yourObjectEntries: Array<[K, V]>
  ) => Record<K, V>,
}
由于
Extract
typescript将对您隐藏此数字键。但是,如果您了解javascript,您可能知道
Object.keys
1
转换为字符串
“1”
。理想情况下,typescript只是对Object.keys有更好的默认类型,但有时你不能有蛋糕吃

如果您只想始终看到字符串键,则可以将
Array
更改为
Array
,并且在大多数情况下您仍然可以正常工作。
是的,我们现在正在做一件非常明显的事情。

不要认为你能比类型断言(Object.keys(v)as Array)做得更好。
定义就是这样的,你能解释一下为什么“所以keysof可能比运行时得到的所有属性都要少。”?@user2010955这是GitHub线程的一段引用,我的理解是,由于您可以向JS的任何对象添加更多属性,因此
keyof
操作符不会返回与
对象相同的内容。keys
还有一个属性显示了它们的思维过程。正如@EmileBergeron指出的,您可以考虑定义自己的本地函数,而不是扩展<代码>对象< /代码>的本机属性。例如:
函数getTypedKeys(obj:T):数组{return Object.keys(obj)as Array;}
。然后,在通常编写
Object.keys(obj)
的地方,改为编写
getTypedKeys(obj)
。不适用于。在中,在给定的游乐场链接中,k只是一个字符串,一定有一些TS错误导致此操作现在失败。它当前失败,在
obj[k]
上有一条红色下划线:
元素隐式地具有“any”类型,因为类型为“string”的表达式不能用于索引类型“obj”。在类型“Obj”上找不到参数类型为“string”的索引签名。(7053)
使用TS 4.1.2,因为..in-loop似乎与当前对象没有什么不同。keys,使用这两个键,您只需选择
键:string
即可回答!这是非常清楚和简洁的。。
const someFunction = (obj: SimpleObject) => {
    Object.keys(obj).forEach((k)=>{
        ....
    })
}

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

const icons: IconName[] = BetterObject.keys(IconMap)

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;
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][]
type KeyOf<T> = Extract<keyof T, string>
type ValueOf<T> = T[KeyOf<T>]

/**
 * Nicely typed aliases for some `Object` Methods
 * - PSA: Don't mutate `yourObject`s
 * - Numerical keys are BAD `{ 1: 'ha!' }` may not appear in your resulting types
 * - Discussion: https://stackoverflow.com/a/65117465/565877
 */
export const ObjectTyped = {
  /**
   * Object.keys, but with nice typing (`Array<keyof T>`)
   */
  keys: Object.keys as <T extends {}>(yourObject: T) => Array<KeyOf<T>>,
  /**
   * Object.values, but with nice typing
   */
  values: Object.values as <T extends {}>(yourObject: T) => Array<ValueOf<T>>,
  /**
   * Object.entries, but with nice typing
   */
  entries: Object.entries as <T extends {}>(yourObject: T) => Array<[KeyOf<T>, ValueOf<T>]>,
  /**
   * Object.fromEntries, but with nice typing
   */
  fromEntries: Object.fromEntries as <K extends string, V>(
    yourObjectEntries: Array<[K, V]>
  ) => Record<K, V>,
}
{
  1: 'asdf'
}