Slide 1

Slide 1 text

You're doing TypeScript wrong

Slide 2

Slide 2 text

# About me * Ben Lowry * Developer at Readify >> * Using TypeScript for 2 years

Slide 3

Slide 3 text

# Discussion * Three things you're doing wrong * What is it * What to do instead * Tweet me! * @benjaminlowry * #DDDPerth * #noAny

Slide 4

Slide 4 text

# Why TypeScript * Add type annotations to describe your intent * Show inconsistencies in your code * Catch bugs before it's in production * All valid JavaScript is valid TypeScript #DDDPerth @benjaminlowry #noAny

Slide 5

Slide 5 text

# What does TypeScript look like const description: string = 'static types in JS' // ^^^^^^^^^ // type annotation function add(first: number, second: number): number { return first + second } #DDDPerth @benjaminlowry #noAny

Slide 6

Slide 6 text

The first thing you're doing wrong Using `any` when you mean something else #DDDPerth @benjaminlowry #noAny

Slide 7

Slide 7 text

# What is `any` * Most flexible type in TypeScript * Bail out of TypeScript * Any value is assignable to a variable marked as `any` * A variable marked as `any` is _assignable to any other variable_ #DDDPerth @benjaminlowry #noAny

Slide 8

Slide 8 text

let myVariable: any = 'hello' myVariable = false myVariable = function () { console.log('hello') } myVariable = 5 #DDDPerth @benjaminlowry #noAny

Slide 9

Slide 9 text

Why is `any` good? * Easier to prototype something without TypeScript getting in the way * 'Pure' JavaScript * Dynamic typing Why is `any` bad? * Prevents the TypeScript compiler from catching inconsistencies in your code * You need to do all the work to ensure the object is what you think it is #DDDPerth @benjaminlowry #noAny

Slide 10

Slide 10 text

function add(first: number, second: number) { return first + second } add('1', 2) // Error, string is not assignable to number let x: any x = '1' add(x, 1) // OK, any is assignable to number #DDDPerth @benjaminlowry #noAny

Slide 11

Slide 11 text

# Using `any` means you’re accepting the risk * How are you handling that risk? * `typeof` checks if (typeof thing === 'string') { … } * "Shape" of the object if (foo && foo.bar && foo.bar.baz) { … } * Type narrowing function isFoo(obj: any): obj is Foo { … } #DDDPerth @benjaminlowry #noAny

Slide 12

Slide 12 text

const thing: any = {} if (typeof thing === 'string') { thing.toUpperCase() // OK, we know it's a string } // TypeScript knows it's _not_ a string #DDDPerth @benjaminlowry #noAny

Slide 13

Slide 13 text

What if I _do_ have `any` *Consider using union types let variable: string | number | boolean | object | Function | Symbol | undefined | null const myString: string = variable // [ts] 'string | number ...' is not assignable to type string' #DDDPerth @benjaminlowry #noAny

Slide 14

Slide 14 text

`unknown` type *"typesafe `any`" *Anything is assignable to `unknown` *`unknown` is not assignable to anything let variable: unknown const myString: string = variable // [ts] Type 'unknown' is not assignable to type string' #DDDPerth @benjaminlowry #noAny

Slide 15

Slide 15 text

The second thing you're doing wrong Duplicating interfaces when they are related #DDDPerth @benjaminlowry #noAny

Slide 16

Slide 16 text

# Duplicating interfaces * When you change a type or property name, you need to change all places it's used * Errors because of changes to types will hide errors with your implementation * Could have errors that are _only_ because of your types * Twice as much typing #DDDPerth @benjaminlowry #noAny

Slide 17

Slide 17 text

interface LabeledInputProps { label: string value: string onChange(ev: Event): void } interface InputProps { value: string onChange(ev: Event): void } #DDDPerth @benjaminlowry #noAny

Slide 18

Slide 18 text

function LabeledInput(props: LabeledInputProps) { const { label, ...inputProps } = props return ( {label} ) } function Input({ value, onChange }: InputProps) { return } #DDDPerth @benjaminlowry #noAny

Slide 19

Slide 19 text

interface LabeledInputProps { label: string value: string // onChange(ev: Event): void onChange(value: string): void } interface InputProps { value: string onChange(ev: Event): void } #DDDPerth @benjaminlowry #noAny

Slide 20

Slide 20 text

function LabeledInput(props: LabeledInputProps) { const { label, ...inputProps } = props return ( {label} ) } function Input({ value, onChange }: InputProps) { return } #DDDPerth @benjaminlowry #noAny

Slide 21

Slide 21 text

interface LabeledInputProps { label: string value: string onChange(value: string): void } interface InputProps { value: string // onChange(ev: Event): void onChange(value: string): void } #DDDPerth @benjaminlowry #noAny

Slide 22

Slide 22 text

function LabeledInput(props: LabeledInputProps) { const { label, ...inputProps } = props return ( {label} ) } function Input({ value, onChange }: InputProps) { return } #DDDPerth @benjaminlowry #noAny

Slide 23

Slide 23 text

# What to do instead * Extend the interface interface LabeledInputProps extends InputProps { label: string } * Use TypeScript mapped types type InputProps = Pick * Type changes will flow through automatically * Errors for _implementation_, not for types #DDDPerth @benjaminlowry #noAny

Slide 24

Slide 24 text

# What is Pick * Pick some properties (with the same types) from another type type InputProps = Pick #DDDPerth @benjaminlowry #noAny

Slide 25

Slide 25 text

# What other mapped types * Readonly * TypeScript compiler will error when you change a property * Partial * Every property becomes optional * Exclude * Only include things in T that aren't in U #DDDPerth @benjaminlowry #noAny

Slide 26

Slide 26 text

The third thing you're doing wrong Not taking advantage of TypeScript compiler options #DDDPerth @benjaminlowry #noAny

Slide 27

Slide 27 text

# tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, } } #DDDPerth @benjaminlowry #noAny

Slide 28

Slide 28 text

# noImplicitAny * `any` means you accept the risk * Sometimes TypeScript can't infer types, leading to an implicit `any` * You don’t realise you're accepting the risk #DDDPerth @benjaminlowry #noAny

Slide 29

Slide 29 text

function add(first, second) { return first + second } interface Foo { bar // [ts] Member 'bar' implicitly has an 'any' type baz } #DDDPerth @benjaminlowry #noAny

Slide 30

Slide 30 text

# strictNullChecks #DDDPerth @benjaminlowry #noAny

Slide 31

Slide 31 text

# The billion dollar mistake > [the null reference] has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. > - Tony Hoare #DDDPerth @benjaminlowry #noAny

Slide 32

Slide 32 text

null undefined #DDDPerth @benjaminlowry #noAny

Slide 33

Slide 33 text

# strictNullChecks * Forces you to annotate when something could be null or undefined let string1: string = null // [ts] type 'null' is not assignable to type 'string’ let string2: string | null = null // OK * Based on that, forces you to validate that the thing isn’t null or undefined let nullableString: string | null = null nullableString.toUpperCase() // [ts] object is possibly null #DDDPerth @benjaminlowry #noAny

Slide 34

Slide 34 text

How did we get here #DDDPerth @benjaminlowry #noAny

Slide 35

Slide 35 text

# Remember you're still writing JavaScript > The compiler will handle it for me > That will never happen * No runtime type checking – just because you said it's a number, does that mean it really _is_ a number? #DDDPerth @benjaminlowry #noAny

Slide 36

Slide 36 text

# Recap * Remember that it's risky to use `any`, try and use the specific type(s), or `unknown` * Use mapped types when you have two types that are closely related * Turn on `strict` mode! * It's still JavaScript! #DDDPerth @benjaminlowry #noAny

Slide 37

Slide 37 text

# Anything else * What you've seen people doing wrong * What _I_ got wrong * Tweet me! * @benjaminlowry * #DDDPerth * #noAny

Slide 38

Slide 38 text

# Resources * `unknown` type * Mapped types * Compiler options * TypeScript design goals * TypeScript Roadmap * Type compatibility * Bugs that aren't bugs #DDDPerth @benjaminlowry #noAny