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 沒有任何值,因此使用 {} 作為函數將無法編譯。
遊樂場
