TypeScript 介面與複合屬性名稱
我需要接受兩組命令列參數來描述兩個不同的資料庫連線(連接到 PostgreSQL 資料庫,但這對這個主題而言並不相關)。
我沒有引入可怕的代碼重複,而是製作了描述連接參數集合的通用類型的 interface。
我快完成了,但我在最後一步卡住了:編寫一個通用函數來訪問這個 interface 的屬性。
interface BasePosgresProgramOptions {
postgresConnectionString?: string;
postgresSrvServiceName?: string;
postgresDb?: string;
postgresDbFile?: string;
postgresUser?: string;
postgresUserFile?: string;
postgresPassword?: string;
postgresPasswordFile?: string;
postgresHost?: string;
postgresPoolMax?: number;
postgresPort?: number;
postgresSslCaFile?: string;
}
type ConnectionNames = "" | "Secondary";
export type PosgresProgramOptions<Suffix extends ConnectionNames> = {
[k in keyof BasePosgresProgramOptions as `${k}${Suffix}`]: BasePosgresProgramOptions[k];
};
到目前為止一切運作良好!現在開始問題:我將函數寫成如下,但返回類型不是很令人興奮...(注意:請查看後面連結的 playground 了解詳情)
export const getPostgresOption = <Suffix extends ConnectionNames>(
args: PosgresProgramOptions<Suffix> | undefined,
suffix: Suffix,
option: keyof BasePosgresProgramOptions
) => args?.[`${option}${suffix}`];
所以我嘗試通過如下更改函數來強制返回類型,但現在它甚至無法編譯了。:(
export const getPostgresOption = <Suffix extends ConnectionNames, Option extends keyof BasePosgresProgramOptions>(
args: PosgresProgramOptions<Suffix> | undefined,
suffix: Suffix,
option: Option
) => args?.[`${option}${suffix}`];
我錯過了什麼?
這裡有一個遊樂場,可以輕鬆地檢查這個問題。
附註:我知道我可以透過指定來強制返回類型,但我更希望採用一種從良好程式碼中推斷類型的解決方案。
答案#1
編譯器無法對泛型類型進行太多抽象推理。如果你希望它理解 getPostgresOption() 返回的類型取決於 suffix 和 option 的類型,你需要用它可以處理的方式來表達這個操作。在以下內容中,我將使用 microsoft/TypeScript#47109 中描述的技術。
我看到的主要問題是編譯器並不真正明白你可以使用類型為 `$ {K}$ {S}` 的鍵來索引 PosgresProgramOptions<K>,其中 S extends ConnectionNames。說服它這是可能的一種方法是將 PosgresProgramOptions 改為也依賴 S。如果我們寫出類似 Pick<PosgresProgramOptions<K>, `$ {K}$ {S}`> 的東西(這是 PosgresProgramOptions 的一個超型別),那麼編譯器就肯定能夠使用 `$ {K}$ {S}` 來索引它。
像這樣:
type PosgresProgramOptions<
S extends ConnectionNames,
K extends keyof BasePosgresProgramOptions = keyof BasePosgresProgramOptions
> = { [P in K as `${P}${S}`]?: BasePosgresProgramOptions[P]; };
現在這個實作成功了:
const getPostgresOption = <
S extends ConnectionNames,
K extends keyof BasePosgresProgramOptions
>(
args: PosgresProgramOptions<S, K> | undefined,
suffix: S,
option: K
) => args?.[`${option}${suffix}`]; // okay
// return type
// PosgresProgramOptions<S, K>[`${K}${S}`] | undefined
你可以看到回傳類型取決於 S 和 K。讓我們來測試它:
const sppo: SecondPosgresProgramOptions = {
postgresConnectionStringSecondary: "cxstr",
postgresPortSecondary: 123
}
const str = getPostgresOption(sppo, "Secondary", "postgresConnectionString");
console.log(str?.toUpperCase()); // "CXSTR"
const num = getPostgresOption(sppo, "Secondary", "postgresPort");
console.log(num?.toFixed(2)) // "123.00"
看起來不錯。str 被推斷為類型 string | undefined(我假設消除 undefined 不在本問題的範圍內;大概可以開始關注 SecondPosgresProgramOptions 的 子類型。在這裡,我們將僅依賴可選鏈來處理 undefined。)而 num 被推斷為類型 number | undefined。
程式碼的 Playground 連結
