簡體   English   中英

有時根據參數存在於打字稿對象上的鍵

[英]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鍵的用戶。

然而,這意味着任何時候我使用purchasesfriendsfriendsOfFriends ,我都需要告訴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會讓我們friendsfriendsOfFriends
  • 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>對於一些組鍵KKeyMap 這是一種方法:

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[];
} */

這些類型對我來說很合適。

Playground 鏈接到代碼


好吧,再次查看您的示例,您沒有將可選屬性包含在輸出中作為可選; 他們只是被省略了。 如果你真的想看到與你的類型完全匹配,那就有點復雜了:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM