Thinking in TypeScript

Thinking in TypeScript

In this super gentle introduction to TypeScript, we will explore the benefits it may bring to your development workflow. More importantly, you will be able to apply some of these lessons to your codebases, even if you are not quite ready to adopt TypeScript just yet!

22bb3e56828870ee9a0dd93aeadbe04a?s=128

Godfrey Chan

October 10, 2019
Tweet

Transcript

  1. Thinking in TypeScript @chancancode

  2. https://skylight.io/ Free for open source apps!

  3. OCTANE https://emberjs.com/editions/octane/ Try the new tutorial!

  4. 01 Introduction A quick tour of TypeScript

  5. function measure(target, options) { if (typeof target === 'string') {

    target = document.querySelector(target); } let width = target.offsetWidth; let height = target.offsetHeight; if (options.margin) { let style = getComputedStyle(target); height += parseFloat(style.getPropertyValue('margin-top')); width += parseFloat(style.getPropertyValue('margin-right')); height += parseFloat(style.getPropertyValue('margin-bottom')); width += parseFloat(style.getPropertyValue('margin-left')); } return { width, height }; }
  6. let i: number; i = 0; // Ok i =

    'not a number'; // Error i = false; // Error Type '"not a number"' is not assignable to type 'number'. Type 'false' is not assignable to type 'number'.
  7. let target: string | HTMLElement; target = '.sidebar'; // Ok

    target = document.body; // Ok target = false; // Error Type 'false' is not assignable to type 'string | HTMLElement'.
  8. interface Options { margin: boolean; } let options: Options; options

    = { margin: true }; // Ok options = { margin: false }; // Ok options = {}; // Error options = false; // Error Property 'margin' is missing in type '{}' but required in type 'Options'. Type 'false' is not assignable to type 'Options'.
  9. function greetingsFor(name: string): string { return `Hello, ${name}!`; } greetingsFor('Godfrey');

    // Ok greetingsFor(false); // Error greetingsFor('Godfrey', 'Chan'); // Error greetingsFor(); // Error let a: string = greetingsFor('Godfrey'); // Ok let b: number = greetingsFor('Godfrey'); // Error Argument of type 'false' is not assignable to … Type 'string' is not assignable to type 'number'. Expected 1 arguments, but got 2. Expected 1 arguments, but got 0.
  10. interface Options { margin: boolean; } interface Measurement { width:

    number; height: number; } function measure(target: string | HTMLElement, options: Options): Measurement { if (typeof target === 'string') { target = document.querySelector(target); } let width = target.offsetWidth; let height = target.offsetHeight; if (options.margin) { let style = getComputedStyle(target); height += parseFloat(style.getPropertyValue('margin-top')); width += parseFloat(style.getPropertyValue('margin-right')); height += parseFloat(style.getPropertyValue('margin-bottom')); width += parseFloat(style.getPropertyValue('margin-left')); } return { width, height }; }
  11. interface Options { margin: boolean; } interface Measurement { width:

    number; height: number; } function measure(target: string | HTMLElement, options: Options): Measurement { if (typeof target === 'string') { target = document.querySelector(target); } let width = target.offsetWidth; let height = target.offsetHeight; if (options.margin) { let style = getComputedStyle(target); height += parseFloat(style.getPropertyValue('margin-top')); width += parseFloat(style.getPropertyValue('margin-right')); height += parseFloat(style.getPropertyValue('margin-bottom')); width += parseFloat(style.getPropertyValue('margin-left')); } return { width, height }; } let width: number
  12. 02 undefined Filling the void of null

  13. let i: number; i = 0; // Ok i =

    'not a number'; // Error i = false; // Error i = undefined; // Ok i = null; // Ok Type '"not a number"' is not assignable to type 'number'. Type 'false' is not assignable to type 'number'.
  14. function firstMatch(candidates: string[], pattern: RegExp): string { return candidates.find(candidate =>

    pattern.test(candidate)); } /* Ok */ firstMatch(['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'], /^.+ve$/); // => 'Dave' /* Ok according to TypeScript 1, but doesn't work! */ firstMatch(undefined, /^.+ve$/); firstMatch(null, /^.+ve$/); firstMatch(['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'], undefined); firstMatch(['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'], null); firstMatch(undefined, undefined); firstMatch(null, null);
  15. function firstMatch(candidates: string[], pattern: RegExp): string { return candidates.find(candidate =>

    pattern.test(candidate)); } /* Ok according to TypeScript 1, but so many problems */ function rateNames(names: string[], ignoreCase = true): void { let candidates: string[]; if (ignoreCase) { candidates = names.map(name => name.toLowerCase()); } let bestName = firstMatch(candidates, /^.+ve$/); let prefix = bestName.slice(0, -2); console.log(`The best name starts with "${prefix}" and ends with "-ve"`); }
  16. rateNames(['Dave']); // The best name starts with "Da" and ends

    with "-ve". rateNames(['Eve']); // The best name starts with "E" and ends with "-ve". rateNames(['Alice', 'Bob', 'Charlie', 'Dave', 'Eve']); // The best name starts with "Da" and ends with "-ve".
  17. let i: number; console.log(i); // Error i = 1; //

    Ok i = 'not a number'; // Error i = false; // Error i = undefined; // Error i = null;// Error let j: number | undefined; console.log(j); // Ok j = 1; // Ok j = undefined; // Ok j = null;// Error let k: number | undefined | null; k = 1; // Ok k = undefined; // Ok k = null;// Ok Variable 'i' is used before being assigned. Type '"not a number"' is not assignable to type 'number'. Type 'false' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number | undefined'.
  18. function firstMatch(candidates: string[], pattern: RegExp): string { return candidates.find(candidate =>

    pattern.test(candidate)); } /* Ok */ firstMatch(['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'], /^.+ve$/); // => 'Dave' /* Error */ firstMatch(undefined, /^.+ve$/); firstMatch(null, /^.+ve$/); firstMatch(['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'], undefined); firstMatch(['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'], null); firstMatch(undefined, undefined); firstMatch(null, null); Argument of type 'undefined' is not assignable to parameter of type … Argument of type 'null' is not assignable to parameter of type … Argument of type … Argument of type … Argument of type 'undefined' is not assignable to parameter of type … Argument of type 'null' is not assignable to parameter of type …
  19. function firstMatch(candidates: string[], pattern: RegExp): string { return candidates.find(candidate =>

    pattern.test(candidate)); } function rateNames(names: string[], ignoreCase = true): void { let candidates: string[]; if (ignoreCase) { candidates = names.map(name => name.toLowerCase()); } let bestName = firstMatch(candidates, /^.+ve$/); let prefix = bestName.slice(0, -2); console.log(`The best name starts with "${prefix}" and ends with "-ve"`); } Type 'string | undefined' is not assignable to type 'string'.
  20. function firstMatch(candidates: string[], pattern: RegExp): string { return candidates.find(candidate =>

    pattern.test(candidate)); } function rateNames(names: string[], ignoreCase = true): void { let candidates: string[]; if (ignoreCase) { candidates = names.map(name => name.toLowerCase()); } let bestName = firstMatch(candidates, /^.+ve$/); let prefix = bestName.slice(0, -2); console.log(`The best name starts with "${prefix}" and ends with "-ve"`); } (method) Array<string>.find(predicate: PredicateFunction): string | undefined; Returns the value of the first element in the array where predicate is true, and undefined otherwise.
  21. function firstMatch(candidates: string[], pattern: RegExp): string | undefined { return

    candidates.find(candidate => pattern.test(candidate)); } function rateNames(names: string[], ignoreCase = true): void { let candidates: string[]; if (ignoreCase) { candidates = names.map(name => name.toLowerCase()); } let bestName = firstMatch(candidates, /^.+ve$/); let prefix = bestName.slice(0, -2); console.log(`The best name starts with "${prefix}" and ends with "-ve"`); } Variable 'candidates' is used before being assigned.
  22. function firstMatch(candidates: string[], pattern: RegExp): string | undefined { return

    candidates.find(candidate => pattern.test(candidate)); } function rateNames(names: string[], ignoreCase = true): void { let candidates: string[] = names; if (ignoreCase) { candidates = names.map(name => name.toLowerCase()); } let bestName = firstMatch(candidates, /^.+ve$/); let prefix = bestName.slice(0, -2); console.log(`The best name starts with "${prefix}" and ends with "-ve"`); }
  23. function firstMatch(candidates: string[], pattern: RegExp): string | undefined { return

    candidates.find(candidate => pattern.test(candidate)); } function rateNames(names: string[], ignoreCase = true): void { let candidates: string[] = names; if (ignoreCase) { candidates = names.map(name => name.toLowerCase()); } let bestName = firstMatch(candidates, /^.+ve$/); let prefix = bestName.slice(0, -2); console.log(`The best name starts with "${prefix}" and ends with "-ve"`); } Object is possibly 'undefined'.
  24. function firstMatch(candidates: string[], pattern: RegExp): string | undefined { return

    candidates.find(candidate => pattern.test(candidate)); } function rateNames(names: string[], ignoreCase = true): void { let candidates: string[] = names; if (ignoreCase) { candidates = names.map(name => name.toLowerCase()); } let bestName = firstMatch(candidates, /^.+ve$/); if (bestName) { let prefix = bestName.slice(0, -2); console.log(`The best name starts with "${prefix}" and ends with "-ve"`); } else { console.log('These names are not that great to be honest'); } }
  25. Warning Signs undefined and unexpected 03

  26. function fullNameFor(firstName, lastName) { if (!firstName || !lastName) { return;

    } return `${firstName} ${lastName}`; } Does the caller expect us to return undefined here? Bare Returns Using the return keyword without a value
  27. function fullNameFor(firstName, lastName) { if (firstName && lastName) { return

    `${firstName} ${lastName}`; } } Not all branches return a value, is that expected? Missing Returns Reaching the end of a function without returning a value
  28. function sayHello(name) { console.log(`Hello, ${name}.`); } let user = {

    firstName: 'Godfrey', lastName: 'Chan' }; sayHello(user.name); Property Access Accessing nonexistent properties on objects This object does not have a property 'name', probably a typo?
  29. function sayHello(firstName, lastName) { console.log(`Hello, ${firstName} ${lastName.toUpperCase()}.`); } sayHello('Godfrey'); Missing

    Arguments Not passing enough arguments to a function The function takes two arguments, but we are only passing one. Probably a mistake?
  30. let names = ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve']; let upperCaseNames

    = names.forEach(name => name.toUpperCase()); Void Functions Consuming the return value of a function that does not return anything 'forEach' does not return a value, probably meant to use 'map' here?
  31. Thinking in TypeScript @chancancode