[英]Keys that sometimes exist on a typescript object depending on parameters
我有一個需要花費大量時間才能完全構建的對象(因為它需要查詢多個數據庫表),因此該對象的客戶端指定他們需要哪些部分。 例如,假設有一個 User 對象,有時有 Purchases 有時有 Friends。 現在,我為此使用了可選屬性:
interface User {
id: number
purchases?: Purchase[]
friends?: Friend[]
friendsOfFriends?: Friend[]
}
用戶的客戶可以說getUser(['purchases'])
來獲取定義了purchases
鍵的用戶。
然而,這意味着任何時候我使用purchases
、 friends
或friendsOfFriends
,我都需要告訴friendsOfFriends
類型在那里(例如, user.purchases![0]
或user.purchases && user.purchases[0]
),這有點煩。
有沒有辦法告訴 Typescript 傳入的參數決定了返回值上存在哪些鍵? 例如:
getUser([])
返回一個{id: number}
getUser(['purchases'])
返回一個{id: number; purchases: Purchase[]}
{id: number; purchases: Purchase[]}
getUser(['network'])
返回一個{id: number; friends: Friend[]; friendsOfFriends: Friend[]}
{id: number; friends: Friend[]; friendsOfFriends: Friend[]}
{id: number; friends: Friend[]; friendsOfFriends: Friend[]}
-注意, network
會讓我們friends
和friendsOfFriends
getUser(['purchases', 'network'])
返回一個{id: number; purchases: Purchase[]; friends: Friend[]; friendsOfFriends: Friend[]}
{id: number; purchases: Purchase[]; friends: Friend[]; friendsOfFriends: Friend[]}
在實際情況下,有超過 2 個可能的鍵可以包含,而且我不想組合許多重載類型。
這在打字稿中可能嗎?
謝謝!
我將把getUser()
的實現留給我們,同時讓編譯器相信你的實現符合類型定義。 也就是說,我的行為就像getUser()
在純 JavaScript 領域中一樣,我只是想出了它的類型聲明,以便編譯器可以正確處理對它的調用。
首先,編譯器需要某種方式來了解getUser()
參數之間的映射以及它們選擇的User
鍵集。 像這樣的東西,給你的例子:
interface KeyMap {
purchases: "purchases",
network: "friends" | "friendsOfFriends"
}
與武裝,我們需要告訴編譯器如何計算UserType<K>
對於一些組鍵K
從KeyMap
。 這是一種方法:
type UserType<K extends keyof KeyMap> =
User & Pick<Required<User>, KeyMap[K]> extends
infer O ? { [P in keyof O]: O[P] } : never;
重要的一點是User & Pick<Required<User>, KeyMap[K]>
:結果總是一個User
所以我們把它包含在一個交叉點中。 我們還KeyMap[K]
中, User
密鑰通過指出K
,和Pick
從這些特性Required<User>
。 Required<T>
類型使所有可選鍵都成為必需的。
下一個開始extends infer O...
實際上只是使用條件類型推斷技巧來獲取丑陋的類型User & Pick...
並將其轉換為具有拼寫屬性的單個對象類型。 如果您更喜歡看到User & Pick<Required<User>, "purchases">
而不是下面的對象類型,您可以刪除所有以extends
開頭的內容。
最后, getUser()
函數類型如下所示:
declare function getUser<K extends keyof KeyMap>(keys: K[]): UserType<K>;
它接受一組KeyMap
鍵並為這些鍵返回UserType<K>
。
讓我們使用您的示例調用確保它有效:
const user = getUser([]);
/* const user: {
id: number;
purchases?: Purchase[] | undefined;
friends?: Friend[] | undefined;
friendsOfFriends?: Friend[] | undefined;
} */
const userWithPurchases = getUser(['purchases']);
/* const userWithPurchases: {
id: number;
purchases: Purchase[];
friends?: Friend[] | undefined;
friendsOfFriends?: Friend[] | undefined;
} */
const userWithNetwork = getUser(['network']);
/* const userWithNetwork: {
id: number;
purchases?: Purchase[] | undefined;
friends: Friend[];
friendsOfFriends: Friend[];
} */
const userWithEverything = getUser(['purchases', 'network']);
/* const userWithEverything: {
id: number;
purchases: Purchase[];
friends: Friend[];
friendsOfFriends: Friend[];
} */
這些類型對我來說很合適。
好吧,再次查看您的示例,您沒有將可選屬性包含在輸出中作為可選; 他們只是被省略了。 如果你真的想看到與你的類型完全匹配,那就有點復雜了:
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
type UserType<K extends keyof KeyMap> =
Pick<User, RequiredKeys<User>> & Pick<Required<User>, KeyMap[K]> extends
infer O ? { [P in keyof O]: O[P] } : never;
declare function getUser<K extends keyof KeyMap>(keys: K[]): UserType<K>;
const user = getUser([]);
/* const user: {
id: number;
} */
const userWithPurchases = getUser(['purchases']);
/* const userWithPurchases: {
id: number;
purchases: Purchase[];
} */
const userWithNetwork = getUser(['network']);
/* const userWithNetwork: {
id: number;
friends: Friend[];
friendsOfFriends: Friend[];
} */
const userWithEverything = getUser(['purchases', 'network']);
/* const userWithEverything: {
id: number;
purchases: Purchase[];
friends: Friend[];
friendsOfFriends: Friend[];
} */
無論您想走哪條路,還是其他方式,都取決於您。 好的,希望有幫助; 祝你好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.