Introduction to TypeScript

TypeScript @ Netlify
Tue Sep 03 2019 ( External link )

Description: TypeScript is a megatrend in JavaScript, going from 46% of npm survey respondents in 2018 to 62% in 2019. This is an introduction to TypeScript for those familiar with JavaScript with a Q&A at the end. A 1hr internal Netlify lunch & learn.

Table of Contents

Twitter Comments

Basic Questions

Conclusion: if you're not interested now you may not stay that way.

Primary Motivations

https://www.monkeyuser.com/assets/images/2017/70-runtime-vs-compile-time-errors.png

Increased Benefits

Lower Costs

  • Refactoring
    • Tooling as a first class focus aka "Compiler as a Service", not a black box
  • Benefit from Types written by
  • Easy interop with JavaScript

Wat

Here's Gary Bernhardt's famous Wat talk in TypeScript:

[] + [] // Error: Operator '+' cannot be applied to types 'never[]' and 'never[]'.
[] + {}  // Error: Operator '+' cannot be applied to types 'never[]' and '{}'.
Array(16).join('wat' - 1) + " Batman!" // Error: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

{} + []  // legal!
{} + {}  // legal!

TypeScript Type System Attributes

  • TS Type System Attributes:
    • Erasable
    • Inferable
    • Gradual
    • Structural
    • Generic
    • Expressive
    • Both Object Oriented and Functional

Erasable

The first and most important attribute of the TS Type System to note is that it gets compiled away. This TS code:

type Person = {
  firstName: string
  lastName: string
}
function greeting(person: Person) {
  return 'Hello, ' + person.firstName + ' ' + person.lastName
}
let user: Person = { firstName: 'Jane', lastName: 'Doe' }
document.body.innerHTML = greeting(user)

compiles to perfectly readable JS to be run:

function greeting(person) {
  return 'Hello, ' + person.firstName + ' ' + person.lastName
}
let user = { firstName: 'Jane', lastName: 'Doe' }
document.body.innerHTML = greeting(user)

This is why criticism from people who claim you should not adopt TypeScript because ECMAScript may someday have types, mostly fall flat.

In particular, note that Types do not get sent to the browser because they are compiled away.

Inferable

The same JS code is also valid (though not necessarily strict) TS. However even then we can still infer types based on usage:

function greeting(person) {
  // person: any <= not infered bc TS doesn't do non-local type inference
  return 'Hello, ' + person.firstName + ' ' + person.lastName
}
let user = { firstName: 'Jane', lastName: 'Doe' }
// user: {firstName: string, lastName: string }
document.body.innerHTML = greeting(user)

Gradual

TypeScript has a spectrum of strictness. At the loose end it will infer everything to the maximally permissive any type, which will let you run all JS code, but offers no type safety. At the other end is strict mode, which includes:

  • noImplicitAny
  • noImplicitThis
  • strictNullChecks
  • strictFunctionTypes
  • strictPropertyInitialization
  • strictBindCallApply

You can also opt in to type checking on a file by file basis, or even use TypeScript tooling inside JS with JSDoc as Webpack does.

Structural

By default, TypeScript compares type structure instead of type name (aka duck typed). We do this to increase interoperability with JavaScript itself. This can cause confusion, for example, this doesn't raise an error when it would in other type systems:

type UserID = string
type FoodID = string
function getUserID(): UserID {
  return readFromFile('/userid')
}
let foodId: FoodID = getUserID() // this is a mistake, but TS doesn't raise an error

Instead, TS chooses to compare shapes. This is most useful in complex data types like objects:

function greet(person: { name: string }) {
  return 'Hello, ' + person.name + '!'
}
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
}
greet(new Person('John')) // no error
greet({ name: 'Jane' }) // no error

Many people wish for nominal types, which C# and Java have. We opt in to it with Type Branding and, in future, the unique keyword.

Generic

TS allows you to declare generic interfaces for reusable code where you don't know the types til much later:

type Node<P> = {
  data: string
  parent: P
  children: Node<Node<P>>[] // can recurse
}

You can use limitations and keywords to offer even more type checks with generics, for example "stringly typed" return values:

function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}
const obj = { text: 'hello', count: 42 }
getProp(obj, 'text') // string
getProp(obj, 'count') // number
getProp(obj, 'doesntexist') // Error

In fact, the dynamic expressiveness of JavaScript leads to TypeScript's type system, not the other way round. This is why there are so many generic type patterns that may look scary at first:

{ x: T, y: U }     // Object
T | U              // Union
T & U              // Intersection
keyof T            // Index
T[K]               // Lookup
{ [P in K]: X }    // Mapped Types
T extends U ? X: Y // Conditional Types

The Generic Type system is very powerful (even Turing Complete) and deserve much fuller treatment in a separate intro. But more important for beginners to know that it is an opt in system to provide better type safety, not a required part of using TypeScript.

Expressive

Union Types and Control Flow Analysis allow smart localization of the type system. For example here we can make sure to perform null checks and proceed the rest of the code with the type system assuring us that we've done it:

function foo(x: string | undefined) {
  if (typeof x === 'undefined') {
    return 'no x parameter given to foo!'
  }
  x // string
  // etc...
}

We can use string and number literals in Union Types which basically act like enums. This is used, for example, in the Discriminated Union design pattern:

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; size: number }
  | { kind: 'rectangle'; w: number; h: number }

function area(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.size ** 2
    case 'rectangle':
      return shape.w * shape.h
  }
}

For those who know Haskell/ML, this looks like Algebraic Data Types.

Tooling

Official Tooling

  • TypeScript Server/Language Service
  • Plugins to VSCode, IntelliJ, vim, emacs, etc
  • Babel 7 released with TypeScript Support
  • Webpack Typescript Loader maintainer on the TS team
  • TS Playground
  • Focus on 250ms response time

Community Tooling

Criticisms of TypeScript

Migrating to TypeScript

I keep a guide here. The technical part is easy, the hard part is getting organization buy-in. Do not ever force it on people.

TSconfig for loose method:

{
  "compilerOptions": {
    "allowJs": true, // level 0
    "checkJs": true, // level 2
    "noImplicitAny": true // level 3
  }
}

Then keep adding noImplicitThis, strictNullChecks, until you have all the strict mode flags.

People often use a type TODO_TYPEME = any; while they do the conversions.

In converting the Netlify codebase, I found some other compiler options handy.

{
  "compilerOptions": {
    "allowJs": true,
    "esModuleInterop": true,
    "incremental": true,
    "lib": ["esnext", "dom"],
    "moduleResolution": "node",
    "jsx": "react",
    "outDir": "tsdist",
    "skipDefaultLibCheck": true, // opt out of entire file type checks with /// <reference no-default-lib="true"/>
    "skipLibCheck": true // really ignore node_modules
    "rootDir": "src"
  },
  "include": ["src/pages/*"], // one folder at a time
  "exclude": ["node_modules", "build", "scripts"] // ignore node_modules
}

Call To Action

Copy and Paste your JS code to TypeScript Playground, play with the compiler options, see what it finds!

Resources

More of My Talks

Other People

{
  "title": "Introduction to TypeScript",
  "slug": "intro-to-typescript",
  "topic": "TypeScript",
  "venues": "Netlify",
  "date": "2019-09-03T00:00:00.000Z",
  "url": "https://mobile.twitter.com/swyx/status/1166487148331184128",
  "video": "https://www.youtube.com/watch?v=YDK8sYCgbDw",
  "desc": "a 1hr netlify workshop introducing people to TS",
  "description": "TypeScript is a megatrend in JavaScript, going from 46% of npm survey respondents in 2018 to 62% in 2019. This is an introduction to TypeScript for those familiar with JavaScript with a Q&A at the end. A 1hr internal Netlify lunch & learn.",
  "pubdate": "2019-09-03T00:00:00.000Z",
  "dateString": "Tue Sep 03 2019"
}