[英]TypeScript: Can I use type generics to return a different value and avoid defining the same function twice?
我有一個帶有返回用戶位置的函數的 React 項目,它可以返回位置對象GeoLocation
或錯誤對象(如果發生這種情況) GeoError
:
export function getPosition(): GeoLocation | GeoError {
// ...
}
問題是,這個函數的大多數用法只關心是否有位置對象,為了避免檢查返回對象的類型,我添加了一個默認參數來指示調用者是否想要錯誤:
export function getPosition(withError: boolean = false): GeoLocation | GeoError | undefined {
let position: GeoLocation = ...;
let positionError: GeoError = ...;
if (!position) {
if (withError)
return positionError;
else
return undefined;
}
return position;
}
然后不需要GeoError
的調用者可以強制轉換並避免檢查返回值的類型:
let userPosition = getPosition() as GeoLocation | undefined;
if (!userPosition) {
alert("No position!")
return;
}
// ... do something with position
但我想避免強制轉換,所以我發現有兩個功能很好:
function getPosition(): GeoLocation | undefined;
function getPositionOrError(): GeoLocation | GeoError;
但我不想用不同的返回值實現兩次編寫相同的位置檢索邏輯,所以我做了這樣的事情:
function _getPosition(withError: boolean = false): GeoLocation | GeoError | undefined {
let position: GeoLocation = ...;
let positionError: GeoError = ...;
if (!position) {
if (withError)
return positionError;
else
return undefined;
}
return position;
}
export function getPositionOrError(): GeoLocation | GeoError {
return _getPosition(true) as GeoLocation | GeoError;
}
export function getPosition(): GeoLocation | undefined {
return _getPosition(false) as GeoLocation | undefined;
}
哪個有效,但在我看來,轉換看起來有點丑陋,所以我想知道是否可以使用類型泛型讓編譯器幫助並推斷類型:
function _getPosition<WError=false>(): GeoLocation | WError ? GeoError : undefined {
let position: GeoLocation = ...;
let positionError: GeoError = ...;
if (!position) {
if (WError)
return positionError;
else
return undefined;
}
return position;
}
但不幸的是,這不起作用,而且我不知道我可以查找哪些搜索詞來正確地做到這一點,或者它是否可能。
謝謝!
使用重載怎么樣?
function getPosition(): GeoLocation | undefined;
function getPosition(withError: true): GeoLocation | GeoError;
function getPosition(withError: boolean = false): GeoLocation | GeoError | undefined {
let position: GeoLocation = ...;
let positionError: GeoError = ...;
if (!position) {
if (withError)
return positionError;
else
return undefined;
}
return position;
}
正如@Andreas 的回答所述,您可以使用重載; 這很簡單:
function getPosition(withError?: false): GeoLocation | undefined;
function getPosition(withError: true): GeoLocation | GeoError;
function getPosition(withError: boolean = false): GeoLocation | GeoError | undefined {
let position: GeoLocation = null!;
let positionError: GeoError = null!;
if (!position) {
if (!withError) // oops!
return positionError;
else
return undefined;
}
return position;
}
const y = getPosition(true); // GeoLocation | GeoError;
const n = getPosition(false); // GeoLocation | undefined;
const n2 = getPosition(); // GeoLocation | undefined;
主要缺點是編譯器並沒有很好地檢查實現。 例如,您可以在您的實現中翻轉一個布爾值,而編譯器不會捕獲它。 事實上,我在上面這樣做了 ( // oops!
) 並且沒有編譯器錯誤。 不過,這仍然是一種合理的前進方式。
如果您想按照問題中描述的方式使用泛型,那么您希望將函數的返回類型設為條件類型。 所以調用簽名可以是:
declare function getPosition<B extends boolean = false>(
withError: B = false as B
): GeoLocation | (B extends true ? GeoError : undefined);
因此,如果withError
為true
,則B
類型參數將為true
,如果withError
為false
或被省略,則為false
(因為默認類型參數為false
, 默認函數參數也是如此)。 返回類型是GeoLocation
與條件類型B extends true? GeoError: undefined
的聯合? B extends true? GeoError: undefined
。
請注意,這還允許您為withError
傳遞一個boolean
,返回類型將為GeoLocation | GeoError | undefined
GeoLocation | GeoError | undefined
GeoLocation | GeoError | undefined
。 像這樣:
const y = getPosition(true); // GeoLocation | GeoError;
const n = getPosition(false); // GeoLocation | undefined;
const n2 = getPosition(); // GeoLocation | undefined;
const yn = getPosition(Math.random() < 0.5) // GeoLocation | GeoError | undefined
不幸的是,實現具有條件返回類型的泛型函數很煩人。 編譯器仍然無法驗證您在實現中所做的事情是否安全,這一次它會抱怨您返回的所有內容,而不是接受您返回的所有內容。 也就是說,你會得到錯誤的警告而不是無聲的失敗:
export function getPosition<B extends boolean = false>(
withError: B = false as B
): GeoLocation | (B extends true ? GeoError : undefined) {
let position: GeoLocation = null!;
let positionError: GeoError = null!;
if (!position) {
if (withError)
return positionError as (B extends true ? GeoError : undefined);
else
return undefined as (B extends true ? GeoError : undefined);
}
return position;
}
export function getPosition<B extends boolean = false>(
withError: B = false as B
): GeoLocation | (B extends true ? GeoError : undefined) {
let position: GeoLocation = null!;
let positionError: GeoError = null!;
if (!position) {
if (withError)
return positionError; // error!
else
return undefined; // error!
}
return position;
}
這是 TypeScript 的限制; 請參閱microsoft/TypeScript#33912上的功能請求,尋求更好的東西。 現在,您需要類型斷言之類的東西來抑制錯誤(您稱之為“轉換”):
export function getPosition<B extends boolean = false>(
withError: B = false as B
):
GeoLocation | (B extends true ? GeoError : undefined) {
let position: GeoLocation = null!;
let positionError: GeoError = null!;
if (!position) {
if (withError)
return positionError as (B extends true ? GeoError : undefined);
else
return undefined as (B extends true ? GeoError : undefined);
}
return position;
}
這有效但讓我們回到靜默失敗而不是錯誤警告......你可以將if (withError)
更改為if (!withError)
並且不會有錯誤。
因此,是否需要重載或條件泛型取決於您。 沒有一個是完美的。
現在,可以從實現中獲得某種類型的安全性,但坦率地說它很丑陋和奇怪。 編譯器非常擅長驗證對通用索引訪問類型的可分配性,只要您實際索引到具有通用鍵的對象即可。 你的withError
輸入不像一個鍵,它是一個boolean
(或undefined
)。 但是我們可以使用模板文字將true
、 false
或undefined
轉換為字符串文字"true"
、 "false"
或"undefined"
,並將它們用作鍵。 並且編譯器可以使用模板字面量類型來遵循這個邏輯。
這里是:
function getPosition<T extends [withError?: false] | [withError: true]>(
...args: T
) {
let position: GeoLocation = null!;
let positionError: GeoError = null!;
if (!position) {
const withError: T[0] = args[0];
return {
true: positionError,
false: undefined,
undefined: undefined,
}[`${withError}` as const];
}
return position;
}
我在args
rest 參數的rest 元組類型T
中使函數泛型化,這樣編譯器就會明白,即使withError
輸入也可用於確定泛型類型參數。 withError
的類型是T[0]
。
然后我們索引一個對象,並將其序列化版本作為鍵。 這會產生以下瘋狂的調用簽名類型:
/* function getPosition<
T extends [withError?: false | undefined] | [withError: true]
>(...args: T): GeoLocation | {
true: GeoError;
false: undefined;
undefined: undefined;
}[`${T[0]}`] */
但它有效:
const y = getPosition(true); // GeoLocation | GeoError;
const n = getPosition(false); // GeoLocation | undefined;
const n2 = getPosition(); // GeoLocation | undefined;
const yn = getPosition(Math.random() < 0.5) // GeoLocation | GeoError | undefined
而這一次,如果您更改實現,輸出類型將相應更改:
return {
true: undefined,
false: positionError,
undefined: positionError,
}[`${withError}` as const];
const y = getPosition(true); // GeoLocation | undefined;
const n = getPosition(false); // GeoLocation | GeoError;
所以編譯器真的在為你驗證類型。 萬歲!
但代價是什么? 我不想在任何生產環境中看到這樣的代碼; 重載或什至帶有類型斷言的通用條件更合理,即使它們在實現中不是類型安全的。 類型安全並不總是比慣用的編碼風格更重要。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.