網站搜尋

typescript - 定義一個物件,其鍵的數量可以是可選的,但至少要有一個鍵


我該如何達成這個目標?

type Fruit = "apple" | 'banana' | 'coconut'

type FruitCollection = { [f in Fruit]?: number }

const validFruitCollection: FruitCollection = { apple: 1, coconut: 2 } 

const emptyCollectionShouldNotPass: FruitCollection = {} // I don't want typescript to let this pass

答案#1

你可以將具有所有可選成員的類型與所有屬性的聯合相交,其中在聯合的每個成員中,至少有一個成員是必需的。所以基本上你會得到:

type WhatWeWant = {
    apple?: number | undefined;
    banana?: number | undefined;
    coconut?: number | undefined;
} & (
    | { apple: number; }
    | { banana: number; }
    | { coconut : number ;})

要在不寫出來的情況下獲取這種類型,我們可以使用映射類型:

type RequireOne<T> = T & { [P in keyof T]: Required<Pick<T, P>> }[keyof T]
type FruitCollection = RequireOne<{ [f in Fruit]?: number }>

遊樂場連結

RequireOne 中映射類型的想法是要在上面的 WhatWeWant 類型中創建聯合類型(T 將是包含所有可選屬性的原始類型)。所以我們在映射類型中所做的,就是取 T 中的每個屬性,並將其類型設置為 Required<Pick<T, P>>。這意味著對於每個鍵,我們得到一個只包含該鍵的類型,基本上對於這個例子就是這種類型:

{
  apple: { apple: number; }
  banana: { banana: number; }
  coconut: { coconut: number ;}
}

對於這種類型,獲取我們想要的聯合類型只是與索引 keyof T 有關的問題,用來獲取我們對象中所有屬性類型的聯合類型。

答案#2

我們需要的是一種類型,它可以排除空對象的可能性。為了實現這一點,我們需要工具類型和值構造函數。考慮如下:

type Fruit = "apple" | 'banana' | 'coconut'

type FruitCollection = { [f in Fruit]?: number }

// type which will exclude empty object
type NotEmpty<T> = {} extends T ? never : T

// value constructor
const makeFruitCollection = <T extends FruitCollection>(c: NotEmpty<T>) => c; 

// use cases
const validFruitCollection = makeFruitCollection({ apple: 1, coconut: 2 }) // ok 👌
const emptyCollectionShouldNotPass = makeFruitCollection({}) // error 👌

類型 NotEmpty 正在檢查我們的 T 是否不是空對象,這個 T 已經通過了 FruitCollection 的所有需求,如果它是空對象,我們會得到 never,而類型 never 沒有任何值,因此使用 {} 作為函數將無法編譯。

遊樂場