Consider the two following type definitions (which I believe are equivalent):
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] }
Both work, but the second one gives more useful type information in my IDE—it seems to go one level deeper.
But, the following type declaration seems to require the Omit style:
type RequireProperties<T, TKeys extends keyof T> =
Omit<T, TKeys> & Required<Pick<T, TKeys>>
Is there some way to rewrite this so that IDE hinting doesn't give less useful type information?
Here's an example of what "less useful type information" means when type information is displayed in IntelliJ/WebStorm. Consider these types:
interface Person {
name: string,
age: number
}
type Person1 = ChangePropertyType1<Person, 'age', string>
type Person2 = ChangePropertyType2<Person, 'age', string>
let person1: Person1
let person2: Person2
Hovering over Person1
gives:
Aias for: ChangePropertyType1<Person, "age", string>
Initial type: Omit<Person, "age"> & {age: string}
But hovering over Person2
gives the much more helpful:
Alias for: ChangePropertyType2<Person, "age", string>
Initial type: {name: Person["name"], age: string}
Similar results for person1
vs. person2
(let person1: Person1
, let person2: ChangePropertyType2<Person, "age", string>
, which is still not great, but does give one level deeper of type information)
Ideally, the type hint would (also?) show the final "computed type":
{
name: string
age: string
}
CodePudding user response:
These types are not equivalent; the second one is a homomorphic mapped type since its key set is keyof T
, whereas the first one is not homomorphic because its key set is TKey
, which is not of the form keyof Something
.
The difference is evident when T
has optional and/or readonly properties: ChangePropertyType2
preserves the readonly
and ?
status of each property, whereas ChangePropertyType1
does not.
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>
So which one you should prefer depends on which of these behaviours you want. If you want the behaviour of ChangePropertyType1
but you don't like the way it is shown in the IDE, you can use the Util_FlatType
utility type from this answer:
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>