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.

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

Popular posts from this blog

Meetolog: the beginning