Explaining the Pick<Type, Keys> typescript challenge
I found TypeScript challenges on GitHub when I wanted to improve my TypeScript skills. Before then, I knew basic typescript such as
const name: string = "John Doe";
const age: number = 100;
type Person = {
name: string;
age: number
}
const person: Person = { name, age }
const getPersonName = (person: Person) => person.name; // -? string
But I had not fully understood that one of typescript’s most powerful features is its ability to derive types from other types. E.g.
const a = { name: "John Doe" } as const
const b = { age: 25 } as const
const c = {
...a,
...b
} // -? { name: "John Doe", age: 25 }
In the above, we see that typescript knows that the type of c
is { name: “John Doe”, age: 25 }
because it can derive it from a combination of the types of a
and b
.
To assist with deriving types from other types, TypeScript exposes a few utility types, one of which is Pick<Type, Keys>
, that helps us derive a new type that has only the keys we pick from a record type. e.g.
type Person = {
name: string;
age: number;
}
type AgelessPerson = Pick<Person, "name">
const john: Person = { name: "John Doe", age: 25 };
const tom: AgelessPerson = { name: "Tom" };
// @ts-expect-error
const jerry: AgelessPerson = { name: "Jerry", age: 30};
// throws error because `age` does not belong in type `AgelessPerson`
So Pick<Person, "name">
gives us a new type { name: string }
which has the picked “name”
key.
Our Challenge
The Pick<Type, Keys> challenge is to implement a MyPick<Type, Keys>
type, which does exactly the same thing as Pick<Type, Keys>
.
So we’re going to do that, and then explain the code to understand what is happening.
type MyPick<T, K extends keyof T> = { [key in K]: T[key] }
Generics in typescript are a way to tell typescript to keep track of specific parts of a type.
Here, our MyPick
type has two generic parameters, T
and K
.
K extends keyof T
gives us a constraint for what the type of K
should be, and it also informs typescript about what type T
can accept.
By using extending keyof T
, our T
can only be a Record type such as {}
, { [key: string}: any }
, and our K
can only be keys of T
.
This means that if we wrote something like:
MyPick<{ name: string }, "age"> // will throw an error
we will have an error thrown on "age"
because it is not a keyof { name: string }
.
Having written these constraints, we can now derive our new type:
{ [key in K]: T[key] }
This syntax lets us know our new type is a Record, and that every key in our new type is the values we supply for K
, while the corresponding Record value will be T[key].
type MyPick<
T,
K extends keyof T
> = {
[key in K]: T[key]
}
And there you have it. The explanation of MyPick<T, K>
, a reimplementation of the Pick<T, K>
utility type.