DEV Community

Cover image for Massaging types in typescript (part 1)
Borislav Grigorov
Borislav Grigorov

Posted on • Updated on • Originally published at bobi.page

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" is string; typeof 1 is number 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" } and type PersonName = Person["name"], the PersonName resolves to string

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 }
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 🤘"]
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

Part two is in the oven and is coming soon.

Photo by Hans Vivek on Unsplash

Discussion (0)