DEV Community

loading...
Cover image for DRY Typescript types

DRY Typescript types

Joan Llenas Masó
・2 min read

Typescript gives us a few tools that we can use to reference existing types, which is very useful if we want to be consistent across our codebase.

Let's see a few examples:

Attention: contrived examples ahead.

Indexed type access

Say we have an interface like this one:

export interface User {
    username: string;
    age: number;
    fullName: {
        firstname: string;
        lastname: string;
    }
}
Enter fullscreen mode Exit fullscreen mode

We can reference all those types inside User by using index syntax:

let userAge: User['age']; // number
let fullName: User['fullName']; // { firstname: string; lastname: string; }
let lastname: User['fullName']['lastname']; // string
Enter fullscreen mode Exit fullscreen mode

This also works for function signatures, take a look at the fullName parameter:

function setFullName (user: User, fullName: User['fullName']): User {
    return {
        ...user,
        fullName
    };
};
Enter fullscreen mode Exit fullscreen mode

Class methods can also be targetted with index syntax, the whole signature or just the return type, which is useful when some method uses a complex return type that doesn't reference any exported type:

class MyClass {
    myMethod() {
        return {
            foo: 'bar'
        }
    }
}

let myMethod: MyClass['myMethod'] // () => {foo: string;}
let myReturn: ReturnType<MyClass['myMethod']> // {foo: string;}
Enter fullscreen mode Exit fullscreen mode

Indexed type access + instances

We can also apply the same techniques we've seen so far within the realm of instances thanks to the typeof operator:

const user = {
    username: 'picklerick',
    age: 70,
    fullName: {
        firstname: 'Rick',
        lastname: 'Sanchez'
    }
}

let fullName: typeof user['fullName'] // { firstname: string; lastname: string; }
Enter fullscreen mode Exit fullscreen mode

Last but not least, typeof with class instances and function signatures:

class MyClass {
    prop = 'bar';
    method() {
        return {
            foo: this.prop
        }
    }
}
const myClass = new MyClass();
let myProp: typeof myClass['prop'] // string
let myMethod: typeof myClass['method'] // () => {foo: string;}
let myReturn: ReturnType<typeof myClass['method']> // {foo: string;}
Enter fullscreen mode Exit fullscreen mode

Indexed type access + Generics

Generic types are inferred, so everything works the same:

interface Action<T> {
    type: string;
    payload: T;
}

interface User{
    id: string;
    name: string;
}

let addUserAction: Action<User>; // {type:string; payload: {id:string; name:string}}
let payload : typeof addUserAction['payload'] // User
Enter fullscreen mode Exit fullscreen mode

Indexed type access + tuples

Tuples have similar syntax, but indexes are numbers instead of strings:

type MyTuple = [number, string];

let fst: MyTuple[0] // number
let snd: MyTuple[1] // string
let nope: MyTuple[3] // Error: Tuple type 'MyTuple' of length '2' has no element at index '3'.
Enter fullscreen mode Exit fullscreen mode

We can also use the typeof operator here:

const myTupleValue: MyTuple = [3, 'hello'];
let fst: typeof myTupleValue[0] // number
let snd: typeof myTupleValue[1] // string
Enter fullscreen mode Exit fullscreen mode

But we have to be explicit with the tuple type otherwise, what used to be a tuple becomes an array and the outcome is very different:

const myTupleValue = [5, 'hello'];
let fst: typeof myTupleValue[0] // string | number
let snd: typeof myTupleValue[1] // string | number
let yep: typeof myTupleValue[3] // string | number
Enter fullscreen mode Exit fullscreen mode

Working with keys

Type keys are also accessible thanks to the keyof operator:

interface User {
    username: string;
    age: number;
}

let userKeys: keyof User // "username" | "age"
Enter fullscreen mode Exit fullscreen mode

And, as you may have guessed, our friend typeof helps us with instances again:

const user = {
    username: 'picklerick',
    age: 70
}

let userKeys: keyof typeof user // "username" | "age"
Enter fullscreen mode Exit fullscreen mode

Wrapping up

Creating new types with Typescript is super easy and cheap thanks to its structural typing nature, but that doesn't mean we should be careless about that. Having these techniques in our toolbelt can help us keep consistency across our codebase.

Discussion (0)