Massaging types in typescript (part 1)

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 }

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