typescript - 從已類型化的物件推導出精確類型
在 TypeScript 中,我想從常量推導出類型,像這樣
const iso3ToCountry = {
DEU: {
name: "Germany",
continent: "Europe",
language: "de",
},
GBR: {
name: "United Kingdom of Great Britain and Northern Ireland",
continent: "Europe",
language: "en",
},
} as const;
type ISOCode = keyof typeof iso3ToCountry // same as "DEU" | "GBR";
但是,我也想確保該物件的值實現以下介面:
interface CountryDetails {
name: string;
continent: string;
language: string;
}
我找到的唯一方法來同時達成兩者,是使用一個輔助函數
function ensureType<T extends Record<string, CountryDetails>>(obj: T): T {
return obj;
}
const iso3ToCountry = ensureType({
DEU: {
name: "Germany",
continent: "Europe",
language: "de",
},
GBR: {
name: "United Kingdom",
continent: "Europe",
language: "en",
},
} as const);
type ISOCode = keyof typeof iso3ToCountry;
現在 ISOCode 相當於 "DEU" | "GBR",而 iso3ToCountry 中的每個值都必須有一個 name、continent 和 language。
缺點是 ensureType 函數會出現在編譯後的 JavaScript 中。我們在應用程式中添加代碼,只是為了解決一些編譯時的問題。
問題:
我可以在「iso3ToCountry」對象中實現類型檢查,並且能夠推導鍵類型,而不必添加會保留在編譯後 JavaScript 中的代碼嗎。
答案#1
你真的想要所謂的 satisfies 運算子,如 microsoft/TypeScript#7481 所要求,但目前在 TypeScript 中並不存在(截至 4.5 版本)。你正在使用的泛型輔助函數方法是這方面非常常見的替代方案,尤其是因為它幾乎適用於任何表達式。發出的 JavaScript 中額外的函數調用通常不會造成大問題,但你希望的是對運行時沒有任何影響的方式。
另一種變通方法是使用與您想要檢查的表達式相同類型的變數,對其使用 typeof 類型運算子來定義一種類型,這將根據您期望的約束使編譯器成功或失敗。
你不能對任意表達式使用 typeof 運算子;它必須是一個變數或變數的屬性。因此,如果你需要聲明一個新變數,這個變通方法可能會對運行時產生一些影響。
既然你已經有一個名為 iso3ToCountry 且類型正確的變數,你就不需要定義一個新的變數,所以不會有運行時影響。
這是一種做法:
type CheckIso3ToCountry<T extends Record<keyof T, CountryDetails> =
typeof iso3ToCountry> = void;
//^^^^^^^^^^^^^^^^^^^^ <-- you've got a problem with
// iso3ToCountry if and only if there's an error here
這裡的 CheckIso3ToCountry<T> 類型是一個虛擬類型,不論如何都會評估為 void。重要的部分是對 T 類型參數的泛型約束,強制它擴展 Record<keyof T, CountryDetails>,以及 typeof iso3ToCountry 的泛型參數預設值。只要後者類型可指派給前者,一切就沒問題。
但是如果你在 iso3ToCountry 上犯錯:
const iso3ToCountry = {
DEU: {
name: "Germany",
continent: "Europe",
language: "de",
},
GBR: {
name: "United Kingdom of Great Britain and Northern Ireland",
continant: "Europe",
language: "en",
},
} as const;
然後你會收到一個錯誤:
type CheckIso3ToCountry<T extends Record<keyof T, CountryDetails> =
typeof iso3ToCountry> = void; // error!
//~~~~~~~~~~~~~~~~~~~~ <--
// Property 'continent' is missing in type
// '{ readonly name: "United Kingdom of Great Britain and Northern Ireland";
// readonly continant: "Europe"; readonly language: "en"; }'
// but required in type 'CountryDetails'.
程式碼的 Playground 連結
