Building Scalable, Maintainable Apps Using TypeScript and React

Kamran Ayub

patterns and practices

Watch the talk:

bit.ly/ndcmn-react-ts-video

👨‍👩‍👦 🐶

🎲🎮👨‍💻📚🎸

Work at Target

What do I want?

To make my apps easier to maintain

To make my apps easier to scale

Scalable

?

Scalable

Easy to extend

 

Easy to collaborate on

 

Easy to reuse code

Maintainable

Easy to refactor

 

Easy to understand

 

Easy to test

TypeScript

JavaScript that scales

  • Static type checking
  • Fantastic editor tooling
  • Refactor-friendly code

React

Component-based presentation

  • Small surface area
  • Reusable components
  • Functional characteristics
  • Great performance

ktomg.com

  • Built in .NET
  • Hybrid app
  • Originally JavaScript & Knockout.js
  • Migrated (90%) to TypeScript & React
  • Heavily interactive UI

Based on a true story

(work in progress)

Concepts

  • Store - Single source of truth for app
  • State - The data representing the app at a point-in-time
  • Actions - Object with a "type" property
  • Action Creators - Small functions that create actions
  • Reducers - Take actions and update Redux state

Folder Layout

State & Reducers

Actions

Props & State

Action Creators

Folder layout

(previous)

  • Files are spread out
  • Components folder doesn't scale well
  • Fine for a small site

Folder Layout

Folder layout

(new)

  • Proposed by Ryan Florence
  • Screen organization
  • Scalable nesting
  • Better collaboration
  • Colocate related files
  • Closer imports

Folder Layout

Module Resolution

Not scalable as screens nest further

Folder Layout

Module Resolution

Emulate Node resolution, search each directory upwards

 

Maintains F12 Go to Definition behavior

Folder Layout

Module Resolution

There's an open issue for this

Folder Layout

Module Resolution

Using tsconfig-paths-webpack-plugin package

 

Embrace the chaos

const path = require("path");
const fs = require("fs");
const klaw = require("klaw-sync");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const tsconfig = require("./tsconfig.json");

// Find shared folders
const searchPath = path.join(__dirname, "new/src/");
const paths = klaw(searchPath, {
  nofile: true
})
  .filter(({ path: p }) => path.basename(p) === "shared")
  .map(({ path: p }) => path.join(path.relative(searchPath, p), "*"));

// Override tsconfig paths
tsconfig.compilerOptions.paths = {
  ...tsconfig.compilerOptions.paths,
  "shared/*": paths
};

// Write temp tsconfig
fs.writeFileSync(
  path.join(__dirname, "tsconfig.temp.json"),
  JSON.stringify(tsconfig)
);

module.exports = {
  resolve: {
    extensions: [".ts", ".tsx", ".js"],

    // Use temp config for resolving paths
    plugins: [new TsconfigPathsPlugin({ configFile: "tsconfig.temp.json" })]
  },
  mode: "development",
  entry: "./index.tsx",
  context: path.resolve(__dirname, "new/src"),
  output: {
    filename: "build/bundle.js"
  },
  module: {
    rules: [
      // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
      { test: /\.tsx?$/, loader: "ts-loader" }
    ]
  }
};

Folder Layout

Folder Layout

  • ✅ Scalable folder structure
  • ✅ Shorter import paths
  • ✅ "shared" component resolution
  • ✅ Supports TypeScript and dev tools

 

  • 😐 Currently forced to provide shared paths

Folder Layout

Redux State

"Bottom up" state

State & Reducers

No need to define an entire representative "State" interface. Just export what it will end up being (typeof)

Folder Layout

Redux State

"Bottom up" state

Each screen will define its state shape

State & Reducers

Folder Layout

Redux State

"Bottom up" state

State & Reducers

Folder Layout

Reducers

Combining reducers

State & Reducers

Folder Layout

Reducers

Combining reducers

State & Reducers

Folder Layout

Reducers

Combining reducers

State & Reducers

Folder Layout

Reducers

Combining reducers

State & Reducers

Folder Layout

State & Reducers

State & Reducers

  • ✅ Maximizes type inference
  • ✅ "Bottom-up" composable state
  • Screens manage their own state
  • Using "shared" folder, you can share state

Folder Layout

Actions

Using constants and typeof

A bit wordy, but pays off at scale

Actions

Folder Layout

State & Reducers

Actions

Action type helpers

Actions

Folder Layout

State & Reducers

Actions (Discriminated)

Actions

Folder Layout

State & Reducers

Actions (Mapped)

Actions

Folder Layout

State & Reducers

Actions (Mapped)

Creating reducer action map helpers

Actions

Folder Layout

State & Reducers

Actions (Mapped)

Actions

Folder Layout

State & Reducers

Actions

Actions

  • ✅ Strongly-typed actions
  • ✅ Reducer action handlers are typed
  • Reusable type utilities
  • If actions or state change, reducers break

Folder Layout

State & Reducers

Action Creators

Action Creators

Folder Layout

Actions

State & Reducers

Action Creators

Dispatching actions

Properly types dispatch function

Action Creators

Folder Layout

Actions

State & Reducers

Action Creators

All type-safe with intellisense

Action Creators

Folder Layout

Actions

State & Reducers

Action Creators

Action Creators

  • ✅ Action creator utility
  • ✅ Dispatch is strongly-typed
  • Ensure actions invoked with correct args

 

  • 😐 Dispatch is on props. Global dispatch? 😈

Folder Layout

Actions

State & Reducers

Props & State

(stateful, non-redux)

Props & State

Folder Layout

Actions

Action Creators

State & Reducers

Props & State

(stateless, non-redux)

Folder Layout

Actions

Props & State

Action Creators

State & Reducers

Props & State

(render prop pattern)

Can pull into type alias and share

Folder Layout

Actions

Props & State

Action Creators

State & Reducers

Props & State

(redux state props)

Folder Layout

Actions

Props & State

Action Creators

State & Reducers

Need a way to get "dispatch" and separate Redux state props vs. "own" props

Extract stateless type alias to encapsulate our State and reduce DispatchProp usage

Props & State

(redux state props)

Folder Layout

Actions

Props & State

Action Creators

State & Reducers

Same as stateless, create encapsulated Redux component type

Props & State

(redux class component)

Folder Layout

Actions

Props & State

Action Creators

State & Reducers

Props & State

Redux type utilities

Folder Layout

Actions

Props & State

Action Creators

State & Reducers

Props & State

Folder Layout

Actions

Props & State

  • ✅ Splitting OwnProps vs. StateProps
  • ✅ Reduce excessive type intersections
  • Reusable type utilities
  • Render prop pattern typing

Action Creators

State & Reducers

All Together Now!

There's always room for improvement

Thanks for listening!

@kamranayub

kamranicus.com

 

Introduction to TypeScript Course

bit.ly/introts

 

Midwest.js 2017 Version (Performance Section)

https://youtu.be/owcuEwn-pSM

Maintainable Apps with TypeScript & React (NDCMN)

By Kamran Ayub

Maintainable Apps with TypeScript & React (NDCMN)

Given at NDC Minnesota 2018

  • 1,186