代码之家  ›  专栏  ›  技术社区  ›  jeanpaul62

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

  •  1
  • jeanpaul62  · 技术社区  · 6 年前

    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 . 游乐场: here ,请选中中的所有复选框 Options 激活 严格:正确 .

    1 回复  |  直到 6 年前
        1
  •  91
  •   Devin Rhode    4 年前

    Object.keys 返回一个 string[] . 这是根据本节所述的设计 issue

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

    还可以为创建别名 keys 在里面 Object 将返回所需的类型:

    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])[]);
    
        2
  •  30
  •   Ben Carp    4 年前

    基于提香·塞尔尼科娃·德拉戈米尔的回答和评论

    显式断言

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

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

    getKeys 而不是 Object.keys . 获取密钥 是指 对象.键 ,但报税表是逐字输入的。

    讨论

    TypeScripts的核心原则之一是,类型检查的重点是值所具有的形状( reference )

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

    x SimpleObject 因为它有它的形状。这意味着当我们看到 ,我们知道它有属性 a b ,但它也可能具有其他属性。

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

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

    typeof k "a"|"b" . 迭代时,实际值将是 , b , c . Typescript通过将k作为字符串输入来保护我们不受这种错误的影响。

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

        3
  •  6
  •   Jeff Stock    5 年前

    看到了吗 https://github.com/microsoft/TypeScript/issues/20503 .

    declare const BetterObject: {
      keys<T extends {}>(object: T): (keyof T)[]
    }
    
    const icons: IconName[] = BetterObject.keys(IconMap)
    

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

        4
  •  4
  •   Devin Rhode    4 年前


    按照他们的逻辑, Object.values

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

    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)的Object.keys/Object.entries将返回never[]和[never,never][],而不是普通字符串[]和[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][]
    

    所以, 小心使用

        5
  •  2
  •   Devin Rhode    4 年前

    你可以用 Extract 实用程序类型,使您的参数仅与 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。

        6
  •  1
  •   Devin Rhode    3 年前

    如果您可能在monorepo中工作,或者从10年的角度来看,您可能更喜欢基于导入的方法(无论如何,在vscode中导入是相当容易的,字面上就是点击 <tab> )

    把这个放在什么地方然后打字 ObjectT <选项卡> 当使用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>,
    }
    

    全局

    如果你喜欢globals,就加上这个 src/index.ts

    [抱歉删除了代码示例,以实现简单性和易维护性。不要使用globals。或者,参见答案历史。]

    这是你在上空盘旋时看到的 TypedObject (重命名为 ObjectTyped Object 你可能会注意到 对象类型

    TypedObject info hover box

    .entries :

    dot entries info hover box

    以及 yourEntries :

    yourEntries info hover box

    这两种方法都避免了变异 Object.keys 行为 无论如何

    当然,开发人员需要发现 ObjectTyped.keys ,但是,如果它在整个代码库中都被使用,则不需要很长时间就可以发现它。

    ======

    这两种方法都避免使用 Extract<keyof T, string> ,因为这意味着要跨整个代码库使用,所以它可能不是最安全的默认值(您可能想知道是否有非字符串键,并根据具体情况进行处理,或者解决非字符串键的根本原因)

    我认为这是不必要的预防措施,只会增加复杂性。请参阅下面的评论BenCarp的答案。只是,不要写这种奇怪的代码:

    {
      1: 'asdf'
    }
    

    由于 typescript将对您隐藏此数字键。但是,如果你了解javascript,你可能知道 将转换 1 "1" 最好是默认键入Object.keys 但有时 .

    如果您只想始终看到字符串键,可以更改 Array<keyof T> Array<Extract<keyof T, string>> ,你应该还是没事的 大多数时候。 是的,我们现在只是在做一件好事。

        7
  •  -1
  •   Devin Rhode    4 年前

    作为一种可能的解决方案,您可以使用 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!
    }