TypeScript:对象.键返回字符串[]

ffx8fchx  于 2022-11-26  发布在  TypeScript
关注(0)|答案(9)|浏览(220)

当使用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:编号; b:编号;“}"没有索引签名。
使用strict: true的TypeScript 3.1。Playground:在这里,请选中Options中的所有复选框以激活strict: true

xdnvmnnf

xdnvmnnf1#

Object.keys会传回string[]。这是根据此issue中所述的设计
这是有意的。TS中的类型是开放式的。所以keysof可能会少于你在运行时得到的所有属性。
有几种解决方案,最简单的一种是只使用一个类型Assert:

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

您也可以在Object中建立keys的别名,以传回您想要的类型:

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

declare global {
    interface ObjectConstructor {
        typedKeys<T>(obj: 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])[]);
oknwwptz

oknwwptz2#

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

只有当你知道你的对象没有额外的属性时,才使用类型Assert(这是对象文字的情况,而不是对象参数)。

显式Assert

Object.keys(obj) as Array<keyof typeof obj>

隐藏的声明

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

使用getKeys代替Object.keysgetKeysObject.keys的引用,但返回值是按字面键入的。
讨论
TypeScript的核心原则之一是类型检查关注值所具有的形状。(reference

interface SimpleObject {
   a: string 
   b: string 
}

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

x可以被称为SimpleObject,因为它有它的形状,这意味着当我们看到SimpleObject时,我们知道它有属性ab,但它可能还有其他属性。

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

someFunction(x)

让我们看看如果默认情况下我们按照OP“字面上”的要求键入Object.keys会发生什么:
我们会得到typeof k"a"|"b"。当迭代时,实际值是abc。Typescript通过将k作为字符串键入来防止这样的错误。
类型Assert正好适用于这种情况-当程序员有额外的知识时。如果你知道obj没有额外的属性,你可以使用文字类型Assert。

ars1skjm

ars1skjm3#

(作者:辛德雷胡斯)
1.使用它:

import { objectKeys } from 'ts-extras'
objectKeys(yourObject)

就这样了

下面是我在知道ts-extras之前做的另一个例子:
第一次
这将返回['a']类型的数组

4szc88ey

4szc88ey4#

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

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

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

将保留密钥类型而不是string[]

os8fio9y

os8fio9y5#

我完全不同意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;

注:

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

所以,小心使用这个......

q1qsirdb

q1qsirdb6#

您可以使用Extract实用程序类型使您的参数仅符合obj中的keys,这些obj是字符串(因此,在编码时忽略任何数字/符号)。

const obj = {
  a: 'hello',
  b: 'world',
  1: 123 // 100% valid
} // if this was the literal code, you should add ` as const` assertion here

// util
type StringKeys<objType extends {}> = Array<Extract<keyof objType, string>>

// typedObjKeys will be ['a', 'b', '1'] at runtime
// ...but it's type will be Array<'a' | 'b'>
const typedObjKeys = Object.keys(obj) as StringKeys<typeof obj>

typedObjKeys.forEach((key) => {
  // key's type: 'a' | 'b'
  // runtime: 'a', 'b', AND '1'
  const value = obj[key]
  // value will be typed as just `string` when it's really `string | number`
})

尽管如此,大多数开发人员可能会认为使用数字作为键是一个糟糕的设计决策/需要修复的bug。

mdfafbf1

mdfafbf17#

下面是我用类型安全的方式复制对象的一种模式。它使用字符串收缩,这样编译器就可以推断出键实际上是类型。这是用类演示的,但也适用于接口之间或相同形状的匿名类型。
它有点冗长,但可以说比公认的答案更直接。如果你必须在多个地方进行复制操作,它确实保存了打字。
请注意,如果类型不匹配,* 将 * 抛出一个错误,这是您希望的,但 * 不会 * 抛出一个错误,如果在thingNum中有丢失的字段。

class thing {
    a: number = 1;
    b: number = 2;
    }
    type thingNum = 'a' | 'b';
    const thingNums: thingNum[] = ['a', 'b'];
    
    const thing1: thing = new thing();
    const thing2: thing = new thing(); 
...
    
    thingNums.forEach((param) => {
        thing2[param] = thing1[param];
    });

运动场连接

y53ybaqx

y53ybaqx8#

下面是一个更精确的效用函数:

const keys = Object.keys as <T>(obj: T) =>
    (keyof T extends infer U ? U extends string ? U : U extends number ? `${U}` : never : never)[];

说明:keyof T扩展了string | number | symbol,但是Object.keys省略了symbol键,并以字符串形式返回number键。我们可以使用模板文字${U}将数字键转换为字符串。
使用此keys实用程序:

const o = {
    x: 5,
    4: 6,
    [Symbol('y')]: 7,
};
for(const key of keys(o)) {
    // key has type 'x' | '4'
}
cwdobuhd

cwdobuhd9#

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

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!
}

相关问题