Powering Up ReactJS With Typescript

How I learned to stop worrying and love strong typings

Gian Marco Toso

@gianmarcotoso

gianmarcotoso

gianmarcotoso.com

polarityb.it

Software Engineer, The Pirate I Was Meant To Be

🎩 About Me 🎩

Born and raised in Turin, Italy

@gianmarcotoso

gianmarcotoso

gianmarcotoso.com

polarityb.it

MSc in Computer Engineering 

Self Employed Software Engineer

Javascript and PHP Developer

🍔 About My Stack 🍔

Docker

PHP/Laravel

"It was working on my machine!"

The monolith is here to stay

NodeJS/TypeScript

Seriously, it's awesome!

React + Redux

The Flux Capacitor!

What is TypeScript?

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript

- Official TypeScript Definition

What is TypeScript?

  • Appeared in 2012
  • Developed and maintained by Microsoft
  • Open Source 👍

What is TypeScript?

class Person {
    constructor(name) {
        this.name = name
        this.steps = 0
    }

    walk(steps) {
        this.steps += steps
    }

    getSteps() {
        return this.steps
    }
}
interface CanWalk {
    walk(steps: number): void
    getSteps(): number
}

class Person implements CanWalk {
    public name: string
    protected steps: number

    constructor(name) {
        this.name = name
        this.steps = 0
    }

    walk(steps: number): void {
        this.steps += steps
    }

    getSteps(): number {
        return this.steps
    }
}

Why TypeScript?

  • More readable code
  • Better structured code
  • Errors stand out immediately
  • Tight integration with VSCode
  • Compile-time checks
  • 100% compatible with plain JS

Why TypeScript?

interface JSONSerializable {
    toJSON(): object
}

class SomeComplexObject implements JSONSerializable {
    /* ... */
}

function log(
    message: string, 
    payload: JSONSerializable
) { /* ... */ }

More readable, better structured code

Why TypeScript?

Errors stand out immediately

Why TypeScript?

VSCode Integration + Intellisense

ReactJS

Consider a simple React Component

import React from 'react'
import * as PropTypes from 'prop-types'

class Image extends React.Component {
    static propTypes = {
        source: PropTypes.string.isRequired,
        size: PropTypes.arrayOf(PropTypes.number).isRequired
    }

    render() {
        const { source, size } = this.props
        const [ width, height ] = size

        const style = { width, height }

        return (
            <img 
                style={style}
                src={source}
            />
        )
    }
}

export default Image

ReactJS

First issue: propTypes

static propTypes = {
    source: PropTypes.string.isRequired,
    size: PropTypes.arrayOf(PropTypes.number).isRequired
}

Checked at runtime (only in dev mode)

 

If the type is wrong, we'll see it at runtime within the browser's console

 

Wouldn't it be better if we knew right away?

ReactJS

Second issue: code completion

render() {
    const { source, size } = this.props
    const [ width, height ] = size

    const style = { width, height }

    // ...
}

Our editor has no way of knowing how the `props` object or the `size` is made, and cannot help us.

 

The `style` object could have any member, the editor doesn't know that we want it to be passed to a components' `style` prop.

We can do better

ReactJS + TypeScript

What does it look like?

import React from 'react'
import { CSSProperties } from 'react'
import { ImageProps } from './ImageProps'

class Image extends React.Component<ImageProps> {
    render() {
        const { source, size } = this.props
        const [ width, height ] = size

        const style: CSSProperties = { width, height }

        return (
            <img 
                style={style}
                src={source}
            />
        )
    }
}

export default Image

Interfaces and types

Let's take a look

// Size.ts

export type Size = [number, number]

// ImageProps.ts

interface ImageProps {
    source: string
    size: Size
}

export { ImageProps }

By defining how the component's props are shaped using a TS Interface, we are moving the prop type check at compile-time rather than at run-time.

Interfaces and types

Intellisense to the rescue

The editor now knows exactly what we are working with (and we can even document it nicely with JSDoc!)

Recap

When using TypeScript with ReactJS, we can define the structure of both the component's Props and the component's State using interfaces.

Interfaces will make the component easier to work with, and can even be used as part of the component's documentation!

Best of all, if we do something wrong we'll know it straight away and not later, while running the app in our browser

What about Redux?

Just like with React, we can use TypeScript to enhance our Redux Developer experience

Redux comes with TypeScript typings built-in, so we have TS support literally out the box!

Action Types

Action Types can be defined as literal enums:

enum CounterActionsTypes {
    INCREMENT = 'COUNTER@INCREMENT',
    RESET = 'COUNTER@RESET'
}

Actions

Actions can be shaped as interfaces

interface CounterAction {
    type: CounterActionTypes
    payload?: any
}

Action Creators

Action Creators are function returning a specific type of Action

const increment: ActionCreator<CounterAction> = function() {
    return {
        type: CounterActionTypes.INCREMENT
    }
}

const reset: ActionCreator<CounterAction> = function() {
    return {
        type: CounterActionTypes.RESET
    }
}

State

Our state can be defined as an interface

interface CounterState {
    count: number
}

const defaultState: CounterState = {
    count: 0
}

Reducers

Our reducer is a pure function, returning a specific type of state

const counterReducer: Reducer<CounterState> = (
	state: CounterState = defaultState,
	action: CounterAction
) => {
	switch (action.type) {
		case CounterActionTypes.INCREMENT: {
			return {
				...state,
				count: state.count + 1
			}
		}
		case CounterActionTypes.RESET: {
			return {
				...state,
				count: defaultState.count
			}
		}
		default: {
			return state
		}
	}
}

Store

Our store also has a strong definition

interface ApplicationState {
	counter: CounterState
}

const reducers: ReducersMapObject = {
	counter: counterReducer
}

const store: Store<ApplicationState> = 
    createStore<ApplicationState>(combineReducers(reducers))

What about... ?

Many libraries include their .d.ts definitions inside their main npm package, supporting TypeScript out of the box

For those that don't, try:

user@machine$ npm install @types/<library-name>

# npm install @types/react
# npm install @types/react-dom
# npm install @types/react-redux

Great! What now?

Using TypeScript in an existing project is possible - although a little bit tedious

We "just" need to add TypeScript to our toolchain and change the extensions of (all?) our files from js(x) to ts(x)

Then we need to fix all errors TypeScript is going to show us, for each of our files!

# ... make a branch (you'll break everything :P)
user@machine$ git checkout -b typescript-integration

# ... add the required packages and some types
user@machine$ yarn add -D typescript ts-loader @types/react @types/react-dom 

# ... initialize TypeScript
user@machine$ npx tsc --init --target es2016 --module es2015 --jsx preserve --moduleResolution\
node --allowSyntheticDefaultImports --allowJs

1. Go to your React project's root folder and...

2. Edit your Webpack configuration

//...
    module: {
	rules: [{ 
            test: /\.tsx?/, 
            exclude: /node_modules/, 
            use: ['babel-loader', 'ts-loader'] 
        }]
    },
    resolve: {
	extensions: ['.js', '.jsx', '.ts', '.tsx'],
    }
//...

3. Change your extensions from .js(x) to .ts(x)

.tsx is required to use the JSX syntax

That's about it

👍

Thank you!

Powering Up ReactJS With Typescript

By Gian Marco Toso

Powering Up ReactJS With Typescript

Slides for my talk at the 2017 WebAppConf in Torino

  • 2,582