[英]Is this a Typescript bug? Error “not assignable to type” when adding an extra property to an object
我正在使用兩個 API,它們對經度坐標使用不同的屬性名稱: lon
和lng
。 我正在編寫一個轉換器 function,它添加了替代屬性名稱,以便可以互換使用兩個 API 中的位置。
這個 function 給出了一個錯誤,老實說,這對我來說似乎是一個錯誤。 我知道很多解決方法,但我有興趣了解為什么會發生此錯誤。 這是一個錯誤,還是我在這里遺漏了什么?
const fillLonLng1 = <T extends LatLng | LatLon>(loc: T): T & LatLon & LatLng => {
if ( isLatLon(loc) ) {
return {...loc, lng: loc.lon};
} else {
return {...loc, lon: loc.lng};
}
}
第一個return
語句給出了一個錯誤:
Type 'T & { lng: number; lat: number; lon: number; }' is not assignable to type 'T & LatLon & LatLng'.
Type 'T & { lng: number; lat: number; lon: number; }' is not assignable to type 'LatLon'.(2322)
Typescript 正確理解返回值包含一個lat
和lon
都是number
。 我不明白這怎么可能不能分配給LatLon
,它被定義為:
interface LatLon {
lat: number;
lon: number;
}
const isLatLon = <T extends Partial<LatLon>>(loc: T): loc is T & LatLon => {
return loc.lat !== undefined && loc.lon !== undefined;
}
完成 Typescript 游樂場。 我發現了兩種不同的方法,它們不會給出任何錯誤(也不依賴as
任何一個)。 一種是我將其分解為兩個獨立的函數,另一種是具有愚蠢復雜的類型。
我認為當有一個泛型類型參數T extends A | B
T extends A | B
並且您收到“ T & X
is not assignable to T & Y
”形式的錯誤,其中X
和Y
等效但不相同,這可能是microsoft/TypeScript#24688中提到的編譯器錯誤。
您可以通過將LatLon & LatLng
重構為編譯器認為與{ lng: number; lat: number; lon: number; }
相同的類型來說服編譯器接受它。 { lng: number; lat: number; lon: number; }
{ lng: number; lat: number; lon: number; }
:
interface LatLonLng extends LatLon, LatLng { }
const fillLonLng1 = <T extends LatLng | LatLon>(loc: T): T & LatLonLng => {
if (isLatLon(loc)) {
return { ...loc, lng: loc.lon };
} else if (isLatLng(loc)) {
return { ...loc, lon: (loc as LatLng).lng };
} else throw new Error();
}
下面的警告仍然有效(即使它們不像我最初想象的那樣直接適用)
正如在這個問題中提到的,function 聲稱要制作泛型類型的值的問題是,function 的調用者可以將泛型指定為他們想要滿足約束的任何內容,並且調用者可以在實現者沒有預料到的方式。
例如, T extends LatLng | LatLon
不僅可以通過向LatLng
或T extends LatLng | LatLon
添加新屬性,還可以通過縮小現有屬性來滿足LatLon
:
const crazyTown = { lat: 0, lon: 0, lng: 1 } as const;
const crazierTown = fillLonLng1(crazyTown);
const crazyTuple = [123, "hello"] as const;
const crazyString = crazyTuple[crazierTown.lng].toUpperCase(); // no compile error, but:
// RUNTIME ERROR! crazyTuple[crazierTown.lng].toUpperCase is not a function
在這里, crazyTown
有一個lon
和一個lng
,它們都是數字文字類型。 function fillLonLng1
聲稱返回typeof crazyTown & LatLon & LatLng
類型的值。 這要求傳入的 output lng
值為1
,但不幸的是,您在運行時得到了0
。 因此,編譯器對之后的(誠然非常不可能的)代碼感到滿意,看起來您在編譯時正在操作string
,但實際上會引發運行時錯誤。 哎呀。
一般來說,編譯器甚至不會嘗試驗證具體值可分配給未指定的泛型類型,因此很有可能編寫一個 100% 安全但仍會被編譯器拒絕的實現。 在您的情況下,通過Omit
實現的fillLonLng2()
function 具有更安全的類型簽名,但如果您以與fillLonLng1()
相同的方式實現它,編譯器將無法判斷。
而且,坦率地說,即使是上面的“正確”錯誤也可能是您不想擔心的事情,因為有人遇到這樣的邊緣情況的可能性太低了。 無論哪種方式,解決方案都是一樣的:使用類型斷言並繼續:
const fillLonLng1 = <T extends LatLng | LatLon>(loc: T): T & LatLon & LatLng => {
if (isLatLon(loc)) {
return { ...loc, lng: loc.lon } as any;
} else {
return { ...loc, lon: (loc as LatLng).lng } as any;
}
}
或者
const fillLonLngSafer = <T extends LatLng | LatLon>(loc: T
): Omit<T, keyof LatLng | keyof LatLon> & LatLon & LatLng => {
if (isLatLon(loc)) {
return { ...loc, lng: loc.lon } as any;
} else {
return { ...loc, lon: (loc as LatLng).lng } as any;
}
}
這應該這樣做:
interface LatLon {
lat: number;
lon: number;
}
namespace LatLon {
export function is(loc: object): loc is LatLon {
return ['lat', 'lon'].every(key => key in loc && loc[key] !== undefined);
}
}
type LatLng = { lat: number; lng: number; };
export function fillLonLng(loc: LatLng | LatLon): LatLon & LatLng {
return LatLon.is(loc) ? { ...loc, lng: loc.lon } : { ...loc, lon: loc.lng };
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.