【问题标题】:Typescript Key-Value relation preserving Object.entries type保留 Object.entries 类型的 Typescript 键值关系
【发布时间】:2023-01-10 03:35:55
【问题描述】:

打字稿提供的 Object.entries 的类型具有返回类型 [string, T][] 但我正在搜索通用类型 Entries<O> 来表示此函数的返回值,该函数保持键和值之间的关系。

例如。当有像这样的对象类型时

type Obj = {
    a: number,
    b: string,
    c: number
}

我正在寻找一种 Entries<O> 类型,当与 Obj 一起提供时,它会导致以下类型之一(或类似的类型):

(["a", number] | ["b", string] | ["c", number])[]
[["a", number], ["b", string], ["c", number]]
(["a" | "c", number] | ["b", string])[]

这对于 Object.entries (see here) 的所有用例都是不正确的,这对我的具体案例来说没有问题。


尝试和失败的解决方案:

type Entries<O> = [keyof O, O[keyof O]][] 对此不起作用,因为它只保留可能的键和值,但不保留它们之间的关系,因为 Entries<Obj>["a" | "b" | "c", number | string]

type Entry<O, K extends keyof O> = [K, O[K]]
type Entries<O> = Entry<O, keyof O>[]

这里 Entry 的定义按预期工作,例如。 Entry&lt;Obj, "a"&gt;["a", number],但在第二行中将它应用为 keyof O 作为第二类型变量,再次导致与第一次尝试相同的结果。

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    这是一个解决方案,但是谨防当使用它作为 Object.entries 的返回类型时;这样做并不总是安全的(见下文)。


    当您想将每个键与依赖于该键类型的东西配对时,请使用 mapped type

    type Entries<T> = {
        [K in keyof T]: [K, T[K]];
    }[keyof T][];
    
    type Test = Entries<Obj>;
    // (["a", number] | ["b", string] | ["c", number])[]
    

    第二个版本具有包含属性而不是联合的元组类型,构造起来要困难得多; it is possible to convert a union to a tuple 但你基本上不应该这样做。

    第三个版本易于管理,但比第一个版本复杂一点:您需要来自 this answerPickByValue

    type Entries3<T> = {
        [K in keyof T]: [keyof PickByValue<T, T[K]>, T[K]]
    }[keyof T][];
    
    type Test3 = Entries3<Obj>;
    // (["a" | "c", number] | ["b", string])[]
    

    Playground Link


    我想我也应该解释为什么打字稿没有Object.entries一个更强的类型。当你有像 type Obj = {a: number, b: string, c: number} 这样的类型时,只能保证一个值具有这些属性;这是不是保证该值不具有其他属性。例如,值 {a: 1, b: 'foo', c: 2, d: false} 可分配给类型 Obj(撇开对象文字的多余属性检查)。

    在这种情况下,Object.entries 将返回一个包含元素 ['d', false] 的数组。 Entries&lt;Obj&gt; 类型说这不可能发生,但实际上它能够发生;所以 Entries&lt;T&gt; 通常不是 Object.entries 的合理返回类型。当您自己知道这些值没有多余的属性时,您应该只将上述解决方案与 Object.entries 一起使用; Typescript 不会为你检查这个。

    【讨论】:

    • 嗯,虽然这可能是一个真正的动机,但他们很容易将返回类型设置为 Entries&lt;T&gt; &amp; [string, T][]。它可能不是有效的 TS,因为我不太擅长类型级别,但他们可以保留类型 T 的想法有至少这些属性,可能还有更多属性(例如您示例中的 d)。然后绝对有可能从这些值构造一个对象并保证它仍然处于类型 T 的有效形状
    • 这比 type-fest 的 Entries 好吗?
    • @transang 我对那个图书馆一无所知; Peter Cardenas 的另一个答案提到了它,但没有详细说明。
    【解决方案2】:

    目前,已经引入了一个名为type-fest 的非常好的实用程序库,以Entries 的形式为您提供此功能。你可以像这样使用它:

    import { Entries } from 'type-fest';
    
    Object.entries(obj) as Entries<typeof obj>;
    

    【讨论】:

      【解决方案3】:

      根据 Peter Cardenas 的回答,我还创建了一个辅助函数

      const getEntries = <T extends object>(obj: T) => Object.entries(obj) as Entries<T>;
      

      【讨论】:

        【解决方案4】:

        我们可以制作如下专用功能:

        depictObjectKeyType<O>(o: O) {
            return Object.keys(o) as (keyof O)[];
        }
        
        depictEntriesKeyType<T>(obj: T): Entries<T> {
            return Object.entries(obj) as any;
        }
        

        并用作:

        this.depictEntriesKeyType(data).forEach(....
        

        【讨论】:

        • 这个答案没有按照 OP 的要求定义 Entries 类型,并建议使用 any 来完全克服缺乏打字的问题。使用any 永远是最后的手段。我不明白为什么它有4票......
        • 嘿@JHH,谢谢你的建议。但是,我相信人们应该了解所有的解决方案,而且对于在初始阶段使用 TS 的开发人员来说,可能需要这个。 Stack-overflow 是一个完美的地方,在这里我们可以从不同背景的开发人员那里获得各种不同观点的解决方案。选择适合您需要的。谢谢!祝你今天过得愉快。
        • “选你需要的”,不用的any ? ?
        • 在这种情况下,as any 真的没什么大不了的,因为它只影响从Object.entries 返回的类型,所以它不会抱怨它不匹配Entries&lt;T&gt;。无论如何,函数的返回类型将替换 any。如果你真的不喜欢它,你可以轻松地将它替换为as Entries&lt;T&gt;
        【解决方案5】:

        我坚信这篇文章的答案应该是这个页面上两个答案的组合:

        type Entries<T> = {
            [K in keyof T]: [K, T[K]];
        }[keyof T][];
        

        @kaya3

        const getEntries = <T extends object>(obj: T) => Object.entries(obj) as Entries<T>;
        

        @minlare

        谢谢你们的出色回答。

        【讨论】:

          猜你喜欢
          • 2020-05-25
          • 1970-01-01
          • 2020-09-15
          • 1970-01-01
          • 1970-01-01
          • 2023-03-12
          • 2015-09-30
          • 1970-01-01
          • 2020-02-01
          相关资源
          最近更新 更多