POSTS

Typescript Typing Power

Reading time: 6 min Written by Marco Tschannett

Hello dudes,

welcome back. It has been some time, since I released my last post. I’m not fully done with my CI for my blog. But I’m maybe getting there sometimes ^^.

So today, I want to talk a bit about Typescript and the power of typing.

In the last few days, I had a few occasions, where I really thought: “WOW. That’s insane.. This really got typed correctly..”

What’s Typescript?

This is no real introduction, nor a language guide or anything in that direction. Typescript is a superset for Javascript. It adds typings to Javascript, which helps while developing. If you want more information, visit their site.

Typing my messages

In the last few weeks I worked on a web extension, which I want to use myself for scraping pages to save for later use. Much like Pocket or Wallabag, but based on Markdown and with a better text selection.

Shameless plug: Look at it!

Because the browser vendors really care about not blocking the content script (the visited site of the user), to not seem slow, you have to use some form of message passing to move around your data and calculate the big things in the background.

So my first approach was something like this:

Hey, I know this event driven things from some of this redux things. I’m doing it like that.

Ok, to be fair, with message passing, you almost get pressured into some kind of types to tell what you need todo and some form of payload.

My first iteration looked something like this:

    // This are the available types to send. I chose string literals, because I like the way they look in the source code :D.
    export type MessageTypes = 'take-selection' | 'selection' | 'make-download';
    
    // Message interace. Do you see the mistake? No relation between message type and it's content.
    export interface ContentMessage<T> {
        type: MessageTypes;
        content: T;
    }

This went fairly well, but there was always this nagging feeling in the back of may head, that told me to be carefull. And it was right. This approach seems ok at first glance, but as mentioned in the comment, there is no relation between the type and the content. This leads to false safty, where a type could be sent with a wrong content, without us noticing it while developing.

But here the typescript type interference kicks in hard^^. You can really do fancy things with it. But first the example then the explanation.

    // Still the same as above, but a few more
    export type MessageTypes =
      | "take-selection"
      | "selection"
      | "make-download"
      | "delete-scrape"
      | "success-reload"
      | "start-element-tagging";
    
    // This is a content payload, that can be sent.
    export type SelectionPayload = {
      selection: ContentType | TitleType | MetaDataType;
      title: string;
      url: string;
    };
    
    // Another payload, that can be sent.
    export type IdPayload = { id: string };
    
    // This is the crucial part! Here we are mapping each type to a Payload.
    // This is still typing, this will not be part of the compiled JS!!
    export type EventPayloads = {
      "take-selection": null;
      selection: SelectionPayload;
      "make-download": IdPayload;
      "delete-scrape": IdPayload;
      "success-reload": null;
      "start-element-tagging": null;
    };
    
    // New typed defenition of the Message interace
    export interface ContentMessage<T extends MessageTypes> {
      type: MessageTypes;
      content: EventPayloads[T];
    }

There are a few things going on here!

  1. We have some payloads defined, this helps us with the typing of the content.
  2. We have a defined Map between our types and our content. This means that typescript will look at what we are doing and will tell us our mistakes at compile time!
  3. The content message is now typed by it’s message type and will throw errors, when we try to set a wrong payload!

Wow. With this we cannot set a wrong content with a wrong type. And with a message passing infrastructure, this is really needed ^^.

Seting Settings Values

The other place where typings can be really helpfull, is when you want to type what can be set as value on a field of a class.

In this case, I wanted to populate my Settings object with either a saved value or a sane default. But I don’t want to get it clustered with wrong parameters or things that were set by mistake.

A naive way doing this could be this:

    class Settings {
    	value1: string;
    	value2: boolean
    }
    
    // Mixing up two objects... Yeay
    function getSettings(saved: any): Settings {
    	let value = Object.assing(new Settings(), saved);
    
    	value = {value1: "default", value2: false, ...value}
    
    	return value
    }

This has a few drawbacks. First we set whatever is coming our way and munch it into the other. To set default values, we would need to either set them in the object constructor or afterwards. Both is very implicit and can be overseen easily. And if a parameter changes, we don’t have typings either.

Using typescripts typing power again:

    export class Settings {
        addMetaDataToDocument: boolean;
    
    		// Small static helper function
        static fromDatabase(object: { [key: string]: any } = {}): Settings {
            const set = new Settings();
    
            setValueOrDefault(set, object, 'addMetaDataToDocument', false);
    
            return set;
        }
    }
    
    // Do you see whats going on?
    function setValueOrDefault<T extends keyof Settings>(
        set: Settings,
        external: { [key: string]: any },
        name: T,
        defaultValue: Settings[T]
    ): void {
        if (name in external) {
            set[name] = external[name];
        } else {
            set[name] = defaultValue;
        }
    }

The interesting part here is the setValueOrDefault function. This is some good thing^^.

So we are doing the following:

  1. We say every name can be something that extends the keys of our Settings value. This means basically, every name we want to use, must be a valid paramter of our settings object.
  2. Our default value gets the type from the settings object.

With this typing, if we either change the name of a parameter or it’s type, we would be made aware of this at least at compile time. This can safe us many hours of looking for a setting that worked in the past but just recently stopped working, because you refactored something and forgot to change it everywhere and now you feeling lika a dumpass. I mean if you are programming for longer than a hour you had this case.

Conclusion

Typing is a nice thing. It makes the developers live easier. You know what you get, you know what you have..

In my opinion, there should be a strictly typed Javascript. Typescript is good, but it’s also only duck typing javascript. There are a few cases, where a typing is correct, but the object is something else.

An example is the usage of JSON.parse(yourObject) . You don’t know what the structure of the given JSON is and also after parsing it and giving it a type you cannot be sure that everything is correct.

On the other hand, these typings really adds some DEV UX, where atleast mistakes made “by hand” can be caught early.

Tags

comments powered by Disqus