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

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!

Godfrey Chan

October 10, 2019
Tweet

More Decks by Godfrey Chan

Other Decks in Programming

Transcript

  1. Thinking in TypeScript
    @chancancode

    View Slide

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

    View Slide

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

    View Slide

  4. 01 Introduction
    A quick tour of TypeScript

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

  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

    View Slide

  12. 02 undefined
    Filling the void of null

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 …

    View Slide

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

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. Warning Signs
    undefined and unexpected
    03

    View Slide

  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

    View Slide

  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

    View Slide

  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?

    View Slide

  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?

    View Slide

  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?

    View Slide

  31. Thinking in TypeScript
    @chancancode

    View Slide