Upgrade to Pro — share decks privately, control downloads, hide ads and more …

You're doing TypeScript wrong

You're doing TypeScript wrong

TypeScript is a great language with a ton of features to support application development in JavaScript. By allowing optional type annotations, TypeScript can tell you at write-time or compile-time when something could break at run-time, reducing the number of bugs in your code. What could be wrong with that?

We will look at a few examples where you may have inadvertently prevented TypeScript from being able to help you, how you can avoid them in the future, and (maybe) how we all got in this mess in the first place.

Resources:
https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#new-unknown-top-type
https://www.typescriptlang.org/docs/handbook/advanced-types.html
https://www.typescriptlang.org/docs/handbook/compiler-options.html

https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
https://github.com/Microsoft/TypeScript/wiki/Roadmap
https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Type Compatibility.md
https://github.com/Microsoft/TypeScript/wiki/FAQ#common-bugs-that-arent-bugs

Benjamin Lowry

August 04, 2018
Tweet

More Decks by Benjamin Lowry

Other Decks in Programming

Transcript

  1. # About me * Ben Lowry * Developer at Readify

    >> * Using TypeScript for 2 years
  2. # Discussion * Three things you're doing wrong * What

    is it * What to do instead * Tweet me! * @benjaminlowry * #DDDPerth * #noAny
  3. # 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
  4. # 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
  5. The first thing you're doing wrong Using `any` when you

    mean something else #DDDPerth @benjaminlowry #noAny
  6. # 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
  7. let myVariable: any = 'hello' myVariable = false myVariable =

    function () { console.log('hello') } myVariable = 5 #DDDPerth @benjaminlowry #noAny
  8. 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
  9. 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
  10. # 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
  11. 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
  12. 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
  13. `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
  14. The second thing you're doing wrong Duplicating interfaces when they

    are related #DDDPerth @benjaminlowry #noAny
  15. # 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
  16. interface LabeledInputProps { label: string value: string onChange(ev: Event): void

    } interface InputProps { value: string onChange(ev: Event): void } #DDDPerth @benjaminlowry #noAny
  17. function LabeledInput(props: LabeledInputProps) { const { label, ...inputProps } =

    props return ( <label>{label} <Input {...inputProps} /> </label> ) } function Input({ value, onChange }: InputProps) { return <input value={value} onChange={onChange} /> } #DDDPerth @benjaminlowry #noAny
  18. 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
  19. function LabeledInput(props: LabeledInputProps) { const { label, ...inputProps } =

    props return ( <label>{label} <Input {...inputProps} /> </label> ) } function Input({ value, onChange }: InputProps) { return <input value={value} onChange={onChange} /> } #DDDPerth @benjaminlowry #noAny
  20. 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
  21. function LabeledInput(props: LabeledInputProps) { const { label, ...inputProps } =

    props return ( <label>{label} <Input {...inputProps} /> </label> ) } function Input({ value, onChange }: InputProps) { return <input value={value} onChange={onChange} /> } #DDDPerth @benjaminlowry #noAny
  22. # What to do instead * Extend the interface interface

    LabeledInputProps extends InputProps { label: string } * Use TypeScript mapped types type InputProps = Pick<LabeledInputProps, 'value' | 'onChange'> * Type changes will flow through automatically * Errors for _implementation_, not for types #DDDPerth @benjaminlowry #noAny
  23. # What is Pick<T, keyof T> * Pick some properties

    (with the same types) from another type type InputProps = Pick<LabeledInputProps, 'value' | // use `|` to separate keys 'onChange' > #DDDPerth @benjaminlowry #noAny
  24. # What other mapped types * Readonly<T> * TypeScript compiler

    will error when you change a property * Partial<T> * Every property becomes optional * Exclude<T, U> * Only include things in T that aren't in U #DDDPerth @benjaminlowry #noAny
  25. The third thing you're doing wrong Not taking advantage of

    TypeScript compiler options #DDDPerth @benjaminlowry #noAny
  26. # 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
  27. # 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
  28. function add(first, second) { return first + second } interface

    Foo { bar // [ts] Member 'bar' implicitly has an 'any' type baz } #DDDPerth @benjaminlowry #noAny
  29. # 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
  30. # 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
  31. # 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
  32. # 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
  33. # Anything else * What you've seen people doing wrong

    * What _I_ got wrong * Tweet me! * @benjaminlowry * #DDDPerth * #noAny
  34. # Resources * `unknown` type * Mapped types * Compiler

    options * TypeScript design goals * TypeScript Roadmap * Type compatibility * Bugs that aren't bugs #DDDPerth @benjaminlowry #noAny