I’ve been looking at TypeScript recently (yes, I’m a bit late to this party) as a means to improve the readability and maintainability of an existing large web application. I thought I’d initially start small with something that I own, so in this post I’ll describe the steps that I took to convert a suite of small React apps to using TypeScript.
Setting Things Up
I have two small web apps - they’re small utilities that I host on my RPG website: here and here - and as such they use bare-bones React and nothing else. In the future I envisage these becoming either part of a larger web app, or for there to be a suite of similar-sized web apps. I also wanted to re-use some shared components between them. For that reason I imported the two existing git repos into a Lerna monorepo. This isn’t a necessity however and all of the steps below can be run on each project individually.
To use TypeScript, I need some new dependencies. I require the typing definitions for React and ReactDOM, which are shipped separately from the main packages. I also require a Webpack loader that can trigger TypeScript compilation. Using yarn/npm/lerna, install:
@types/react @types/react-dom @typescript awesome-typescript-loader --save-dev
TypeScript looks for a
tsconfig.json file, which instructs the TypeScript compiler where to look for code, where to produce code, and any specific compilation rules that should be followed during compilation. My configuration is explicitly disabling the
TypeScript’s compilation step replaces Babel’s transpilation step, meaning that I could delete Babel, it’s dependencies, and the
I then renamed all my
.jsx files to
.tsx. In Visual Studio Code this started to produce quite a lot of errors almost immediately!
I was already using Webpack to build my distributables and would like to continue to do so. After upgrading from Webpack 2 to 4, I then had to make some small changes to the config file - to tell it to look for TypeScript files, and run them through the TypeScript loader.
The way that imports works in TypeScript is slightly different to ES imports. Therefore I had to change all occurrences of:
import React from 'react' to:
import * as React from 'react'
Most functions are quite simple to convert to TypeScript - specify the types of any parameters and the type of the returned object:
Converting the React Components
The vast majority of the components that I use are stateless components. These are simple to convert manually and act as a good starting point. If you envisage your React components as a tree, the simplest ones will most likely live at the leaf nodes and will most likely be stateless, receiving properties from their parent components and producing output wholly dependent upon those inputted properties. Take the following React component for example:
The first step was to take that list of properties from the component and define an interface:
Then, I declared the type of your functional component to be one of
React.SFC. This is a generic type, with a type parameter corresponding to the interface declared for the component’s properties:
From there I progress upwards in the component, doing the same thing. tree. Every time I saw a property that was complex object, I gave it the type
any. These will be revisited later on, but help in the immediate sense that you can progressively work through your files and see them go from red to green.
Functions that you pass as properties into React components, for example for use as a callback function, must also have typing information supplied.
My top-level root React component is a stateful component. It’s using its internal state to track anything that can change within the application - I thought the state was small enough not to warrant using a state management library such as Redux. These too must be typed! Rather than using
React.SFC, I use
React.Component. It’s also a generic type with two type params - the first represents the type of the props, the second the type of the state. As I don’t care about the props I’m comfortable using
any. For my state I use a custom type called
Model, which I need to define myself.
Defining Domain Object Models
Let’s talk a bit about how to define those custom domain-based types. I need a custom type to define the shape of the top-level state, and also replace the instances of
any that I’ve sprinkled temporarily throughout the code into something more meaningful. This is as straightforward as defining some more interfaces:
For some of my domain types, I had some properties which were optional. TypeScript doesn’t expect optionals by default - you need to instruct the compiler to allow for them by denoting the property with a
One of the more esoteric types I had to define was for an object which was a map of
Map type, which I can then extend:
As this is such as small project I decided to extract the domain-based types into a separate
model.ts file. However as an application grows I could quite conceivably see that you would end up defining these in separate files. Once again by working upwards from the leaf React components up to the top-level root React component, I was able to replace all instances of
any which represented a domain object with the newly-defined types.
From here all I need to do is run webpack, load the
index.html file and everything is working!
This was a relatively straightforward exercise. I didn’t have any extra dependencies such as state management libraries to handle. Overall it took me a few hours to grasp (enough of) the TypeScript syntax and perform the conversion. Visual Studio Code is my primary IDE of choice and it’s great to see its TypeScript integration works so well that you can use it as a guide while converting. It also called out a few errors in my existing code (oops!) which I was able to correct as part of the conversion. I can totally see the benefits of introducing TypeScript, although hopefully
Next steps for me are to investigate how well TypeScript plays with a React-Redux project, and then something such as the Apollo GraphQL client - the prospects of using a GraphQL schema to strongly-type the frontend code is very exciting to me!