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
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!
Maintainable Apps with TypeScript & React (NDCMN)
By Kamran Ayub
Maintainable Apps with TypeScript & React (NDCMN)
Given at NDC Minnesota 2018
- 1,304