網站搜尋

TypeScript - 從常量物件文字獲取鍵


請考慮以下內容:

const foobar = [
  {
    name: 'foo',
    map: { a: {}, }
  },
  {
    name: 'bar',
    map: { b: {}, }
  }
];

type FoobarMapItem = keyof typeof foobar[number]['map']

在這裡,我得到 type FoobarMapItem="a" | "b"。然而,如果我添加 as const:

const foobar = [
  {
    name: 'foo',
    map: { a: {}, }
  },
  {
    name: 'bar',
    map: { b: {}, }
  }
] as const;

type FoobarMapItem = keyof typeof foobar[number]['map']

該類型現在是 type FoobarMapItem=never(Playground)。

為什麼 readonly (const) 物件會有這種行為?
是否仍有方法可以從 readonly foobar[number]['map'] 中提取這些鍵?

看起來相關的是,如果 map 屬性的子屬性在 foobar 中的各個元素之間相同,我就不再像第一個例子那樣得到 never 作為我的類型。

答案#1

讓我們簡化一下這個:

const obj = [
  { a: 'a string' },
  { b: 'b string' }
]

type A = typeof obj[number]
type B = keyof A // 'a' | 'b'

這裡的 IDE 告訴我們 obj 被推斷為類型:

const obj: ({
    a: string;
    b?: undefined;
} | {
    b: string;
    a?: undefined;
})[]

所以這意味著一個陣列,其項目具有 a 和 b 鍵,其中一個存在,而另一個要麼被省略,要麼設置為 undefined。

Typescript 假設你的意思是一個未知長度的陣列,裡面充滿了有一個鍵對應一個值,還有一個未定義鍵的對象。


在這一點上,我想提到這種類型,我稍後會再回到這個話題,但請先記住它。

type IAmNever = keyof ({ a: 1 } | { b: 2 }) // never

那這個物體呢?

const objAsConst = [
  { a: 'a string' },
  { b: 'b string' }
] as const

推斷為類型:

const objAsConst: readonly [{
    readonly a: "a string";
}, {
    readonly b: "b string";
}]

TypeScript 現在不需要做任何假設,它會將這個鎖定得非常嚴格,確切符合字面值的形狀。它知道你有一個長度為 2 的已知陣列,其中第一個物件只有一個 a 屬性,第二個物件只有一個 b 屬性。即使其他鍵被賦值為 undefined,也不允許存在其他鍵。

請注意,每個項目不再有 a?: undefined 條目。Typescript 甚至不會允許你訪問它們來賦值 undefined,因為它已經確定那些鍵不存在。

所以這裡的 C 類型是:

type C = {
    readonly a: "a string";
} | {
    readonly b: "b string";
}

這與上面的 IAmNever 情況相同。

當你對聯合類型使用 keyof 時,它會返回所有聯合成員共有的鍵。在這個例子中沒有共同的鍵,所以你會得到 never。

遊樂場


你到底是怎麼做的?

你可以使用聯集到交集的泛型類型來合併所有聯集成員,然後從中獲取鍵。

那意味著:

type T = UnionToIntersection<{a:1} | {b:2}> // { a: 1, b: 2 }

現在你有一種類型的單一物件,並且取得鍵值很容易。

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never


const objAsConst = [
  { a: 'a string' },
  { b: 'b string' }
] as const

type C = UnionToIntersection<typeof objAsConst[number]>
type D = keyof C // 'a' | 'b'

遊樂場

不過我不太確定那有多大用處,因為你 無法 使用那些鍵來索引 objectAsConst,因為 TypeScript 無法保證每個物件都支援用所有那些鍵來存取。