Massaging types in typescript (part 1)
TLDR;
In TypeScript, we can retrieve type information in many different ways. In type context we can use:
- typeof - refer to the type of a value or a property:
typeof "some string"
isstring
;typeof 1
isnumber
etc. - keyof - given an object, reduces it to an union of it’s keys:
type keys = keyof { name: "Bobi", age: 29 }
resolves to the following type:type keys = "name" | "age"
- indexed access - given
type Person = { name: "Bobi" }
andtype PersonName = Person["name"]
, thePersonName
resolves tostring
TypeScript is great when it comes to types reusability. Once defined, a type can be “massaged” in order to suit different business logic needs.
Here’s one simple example.
1 2 3 4 5 6 7 8 9 | type Person = { name: string, age: number }; const person: Person = { name: "Bobi", age: 29 }; const updateName = (p: Person, name: string) => { p.name = name; } updateName(person, "John"); // { name: "John", age: 29 } |
The updateName
function accepts a person (Person
) and a name, which is in the form of a string
.
It’s all great, but imagine we get new requirements, which say a user can have an empty (null
) name.
We do:
1 | type Person = { name: string | null, age: number };
|
Now we have to also update the type of the name
argument in updateName
like this:
1 2 3 | const updateName = (person: Person, name: string | null) => { person.name = name; } |
This move can be skipped if we were using indexed types access for the name
property of the Person
type. Let me show you.
Having this type definition, typescript is smart enough to resolve the type for the name
argument by itself, using the type of Person.name
. Which, of course, is string | null
after the update with our new imaginary requirements.
That’s the power of indexed types access
The typeof
Another handy keyword in TypeScript is typeof
.
1 2 3 4 5 6 7 8 9 10 11 | const names = ["John", "Mary"]; const emojify = (name: string) => { return `${name} 🤘`; } const process = (items: typeof names, processor: typeof emojify) => { return items.map(processor); } console.log(process(names, emojify)) // ["John 🤘", "Mary 🤘"] |
Pay attention to the process
function. It’s items
argument is anything that has the same type as names, which happens to be string[]
. And the processor
argument is anything that has the same type as emojify
, which in the example is (string) => string
.
We use type x = typeof y
when we want tell the compiler: “Hey, compiler, whatever the type of that variable (y
) is, use it as a type for this (x
) variable”.
The keyof
The last tool we’re going to cover in the first part is keyof
.
It’s quite self explanatory. Let me show you.
1 2 3 | type Subscriber = { id: number, email: string, topic: number };
type SubscriberProp = keyof subscriber; // "number" | email" | "topic"
|
Our SubscriptionDetail
gets resolved to the keys of the Subscriber
type. So we get type SubscriberDetail = "email" | "topic"
.
It’s handy when we need something in the following fashion:
1 2 3 | const getProp = (subscriber: Subscriber, prop: SubscriberProp) => { return subscriber[prop] } |
Part two is in the oven and is coming soon.
Comments
Post a comment