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.
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:
type Person = { name: string | null, age: number };
Now we have to also update the type of the name
argument in updateName
like this:
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.
const updateName = (person: Person, name: Person["name"]) => {
person.name = name;
}
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
.
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.
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:
const getProp = (subscriber: Subscriber, prop: SubscriberProp) => {
return subscriber[prop]
}
Part two is in the oven and is coming soon.
Photo by Hans Vivek on Unsplash