typescript - Omit + Pick ger mindre användbar typinformation än mappade typer
Tänk på följande två typdefinitioner (som jag tror är ekvivalenta):
type ChangePropertyType1<T, TKeys extends keyof T, TNew> =
Omit<T, TKeys> & { [K in TKeys]: TNew }
type ChangePropertyType2<T, TKeys extends keyof T, TNew> =
{ [K in keyof T]: K extends TKeys ? TNew: T[K] }
Båda fungerar, men det andra ger mer användbar typsinformation i min IDE—det verkar gå ett steg djupare.
Men följande typdeklaration verkar kräva Omit-stilen:
type RequireProperties<T, TKeys extends keyof T> =
Omit<T, TKeys> & Required<Pick<T, TKeys>>
Finns det något sätt att skriva om detta så att IDE-förslag inte ger mindre användbar typinformation?
Här är ett exempel på vad "mindre användbar typinformation" betyder när typinformation visas i IntelliJ/WebStorm. Betrakta dessa typer:
interface Person {
name: string,
age: number
}
type Person1 = ChangePropertyType1<Person, 'age', string>
type Person2 = ChangePropertyType2<Person, 'age', string>
let person1: Person1
let person2: Person2
Att hovra över Person1 ger:
Alias for: ChangePropertyType1<Person, "age", string>
Initial type: Omit<Person, "age"> & {age: string}
Men att hovra över Person2 ger det mycket mer hjälpsamma:
Alias for: ChangePropertyType2<Person, "age", string>
Initial type: {name: Person["name"], age: string}
Liknande resultat för person1 vs. person2 (let person1: Person1, let person2: ChangePropertyType2<Person, "age", string>), vilket fortfarande inte är bra, men ger en nivå djupare av typinformation)
Idealiskt sett skulle typindikationen (också?) visa den slutliga "beräknade typen":
{
name: string
age: string
}
Svar #1
Dessa typer är inte ekvivalenta; den andra är en homomorfiskt mappad typ eftersom dess nyckeluppsättning är keyof T, medan den första inte är homomorfisk eftersom dess nyckeluppsättning är TKey, vilket inte är av formen keyof Something.
Skillnaden är tydlig när T har valfria och/eller skrivskyddade egenskaper: ChangePropertyType2 bevarar readonly och ?-statusen för varje egenskap, medan ChangePropertyType1 inte gör det.
type Foo = {a: number; readonly b: number; c?: number;}
// type FooChanged1 = Omit<Foo, keyof Foo> & {a: string; b: string; c: string;}
type FooChanged1 = ChangePropertyType1<Foo, keyof Foo, string>
// type FooChanged2 = {a: string; readonly b: string; c?: string | undefined;}
type FooChanged2 = ChangePropertyType2<Foo, keyof Foo, string>
Så vilken du bör föredra beror på vilket av dessa beteenden du vill ha. Om du vill ha beteendet hos ChangePropertyType1 men inte gillar hur det visas i IDE:n, kan du använda hjälpverktygstypen Util_FlatType från detta svar:
type ChangePropertyType3<T, TKey extends keyof T, TNew> =
Util_FlatType<Omit<T, TKey> & { [K in TKey]: TNew }>
// type FooChanged3 = {a: string; b: string; c: string;}
type FooChanged3 = ChangePropertyType3<Foo, keyof Foo, string>
Lekplatslänk
