Switching to Typescript

I finally gave in and learned the language that caused me to drop Angular for React. Sorry Google.

I didn’t really have a good a reason for it but I decided to give it a try on one utility module on Statutes.ca and then another… It quickly became clear to me that the benefits of Typescript don’t really manifest if you don’t use it for everything (common assertions about being able to mix and match aside). By the end of the week I had converted the entire code base (about 7000 lines of Javascript according to cloc).

Getting over the hump

About halfway through, I started to think really hard about what I was doing. This is the point where my Google searches changed from “React.ReactNode vs. JSX.Element” and “type boolean is not assignable to type true” to “should I use Typescript”. I suspect that this may be common. The specific realization was that I was spending a significant amount of time trying to rescue perfectly workable code from compiler errors. I wasn’t improving it in any noticeable way. I was just moving it around and adding more null checks. In my case, I figured I would just push ahead and hope that I’d finally figure out a reason for my choice. I think I did.

How Typescript improved my code base

The first and most obvious benefit was the handful of mistakes that I caught. Many of the articles I read in my why-am-I-doing-this phase commented that the errors caught by Typescript will usually pop up in testing and get caught a little bit later. In my case, that wasn’t completely true. Some of the mistakes were insulated by other logic errors (e.g., an attempt to guard against bad input that didn’t work but never happened). Others happened to be harmless for now (e.g., extraneous properties passed to objects that didn’t use or no longer used them).

The next benefit was helping to finish migrating from the old version of my API to the new one. A few months ago, I made some significant changes. This included renaming some properties. In the short term, I decided to leave or emulate the old properties to avoid crashes when people with cached code went to the site. Modelling my API with types helped me catch all the places where I still used those old properties.

In a general sense, the switch to Typescript helped clear out the cruft of several years of development. Every refactor leaves a few properties that are no longer used but don’t break anything. Typescript made me clean those up.

The coolest thing

Typescript helped me be more explicit about partial data structures. On Statutes.ca, there are three major display levels:

  1. Statute list: names, last modified date, view counts.
  2. Statute: name, more dates, view counts, table of contents (section numbers and titles), version history
  3. Section: title, number, dates, content, notes, offences, references

Each level contains a little bit of information about the next level down. For example, if you load the statute list, you have the titles of all the statutes. So, if the list is loaded and the user tries to view a single statute, I can start rendering the statute view even while the data is still being fetched. It makes for snappier navigation without using generic placeholders. However, it’s easy to accidentally depend on data that doesn’t exist.

In Typescript, you can define types that depend on each other (you can do it with interfaces too). So I defined types as follows:

export type StatutePartial = {
   _id: string;
   slug: string;
   shortTitle: string;
   citation: string;
   startDate: string;
   endDate: string;
   createdDateTime: Date;
   views: number;
 };

 export type Statute = StatutePartial & {
   type: string;
   source: string;
   longTitle: string;
   toc: StatuteTocEntry[];
   href: string;
   refreshDateTime: Date;
   updatedDateTime: Date;
 };

In my props, I require Statute or StatutePartial depending on the requirements of the component. Now, Typescript will complain if I try to use a component that depends on, e.g., the table of contents in circumstances were I may only have the preloaded data. To be clear, I was already doing this, but before Typescript it was not explicit and not enforced. My code feels significantly more solid with a clear line between partial renders and full functionality.

Note: Typescript has a built in Partial<T> generic but that sets all properties to optional which is not what I want here. I know that the passed object will either have 8 of the properties or all 15.

Related Post

Read More
Read More
Read More
Read More