Slide 1

Slide 1 text

Thinking in TypeScript @chancancode

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

01 Introduction A quick tour of TypeScript

Slide 5

Slide 5 text

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 }; }

Slide 6

Slide 6 text

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'.

Slide 7

Slide 7 text

let target: string | HTMLElement; target = '.sidebar'; // Ok target = document.body; // Ok target = false; // Error Type 'false' is not assignable to type 'string | HTMLElement'.

Slide 8

Slide 8 text

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'.

Slide 9

Slide 9 text

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.

Slide 10

Slide 10 text

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 }; }

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

02 undefined Filling the void of null

Slide 13

Slide 13 text

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'.

Slide 14

Slide 14 text

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);

Slide 15

Slide 15 text

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"`); }

Slide 16

Slide 16 text

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".

Slide 17

Slide 17 text

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'.

Slide 18

Slide 18 text

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 …

Slide 19

Slide 19 text

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'.

Slide 20

Slide 20 text

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.find(predicate: PredicateFunction): string | undefined; Returns the value of the first element in the array where predicate is true, and undefined otherwise.

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

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"`); }

Slide 23

Slide 23 text

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'.

Slide 24

Slide 24 text

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'); } }

Slide 25

Slide 25 text

Warning Signs undefined and unexpected 03

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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?

Slide 29

Slide 29 text

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?

Slide 30

Slide 30 text

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?

Slide 31

Slide 31 text

Thinking in TypeScript @chancancode