Skip to content

Latest commit

 

History

History
227 lines (167 loc) · 7.93 KB

avoid-non-ecma.md

File metadata and controls

227 lines (167 loc) · 7.93 KB

Item 72: Prefer ECMAScript Features to TypeScript Features

Things to Remember

  • By and large, you can convert TypeScript to JavaScript by removing all the types from your code.
  • Enums, parameter properties, triple-slash imports, experimental decorators, and member visibility modifiers are historical exceptions to this rule.
  • To keep TypeScript’s role in your codebase as clear as possible and to avoid future compatibility issues, avoid nonstandard features.

Code Samples

enum Flavor {
  Vanilla = 0,
  Chocolate = 1,
  Strawberry = 2,
}

let flavor = Flavor.Chocolate;
//  ^? let flavor: Flavor

Flavor  // Autocomplete shows: Vanilla, Chocolate, Strawberry
Flavor[0]  // Value is "Vanilla"

💻 playground


enum Flavor {
  Vanilla = 'vanilla',
  Chocolate = 'chocolate',
  Strawberry = 'strawberry',
}

let favoriteFlavor = Flavor.Chocolate;  // Type is Flavor
favoriteFlavor = 'strawberry';
// ~~~~~~~~~~~ Type '"strawberry"' is not assignable to type 'Flavor'

💻 playground


function scoop(flavor: Flavor) { /* ... */ }

💻 playground


scoop('vanilla');
//    ~~~~~~~~~ '"vanilla"' is not assignable to parameter of type 'Flavor'

import {Flavor} from 'ice-cream';
scoop(Flavor.Vanilla);  // OK

💻 playground


type Flavor = 'vanilla' | 'chocolate' | 'strawberry';

let favoriteFlavor: Flavor = 'chocolate';  // OK
favoriteFlavor = 'americone dream';
// ~~~~~~~~~~~ Type '"americone dream"' is not assignable to type 'Flavor'

💻 playground


class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

💻 playground


class Person {
  constructor(public name: string) {}
}

💻 playground


class Person {
  first: string;
  last: string;
  constructor(public name: string) {
    [this.first, this.last] = name.split(' ');
  }
}

💻 playground


class PersonClass {
  constructor(public name: string) {}
}
const p: PersonClass = { name: 'Jed Bartlet' };  // OK

interface Person {
  name: string;
}
const jed: Person = new PersonClass('Jed Bartlet');  // also OK

💻 playground


// other.ts
namespace foo {
  export function bar() {}
}

💻 playground


// index.ts
/// <reference path="other.ts"/>
foo.bar();

💻 playground


class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  @logged  // <-- this is the decorator
  greet() {
    return `Hello, ${this.greeting}`;
  }
}

function logged(originalFn: any, context: ClassMethodDecoratorContext) {
  return function(this: any, ...args: any[]) {
    console.log(`Calling ${String(context.name)}`);
    return originalFn.call(this, ...args);
  };
}

console.log(new Greeter('Dave').greet());
// Logs:
// Calling greet
// Hello, Dave

💻 playground


class Diary {
  private secret = 'cheated on my English test';
}

const diary = new Diary();
diary.secret
//    ~~~~~~ Property 'secret' is private and only accessible within ... 'Diary'

💻 playground


const diary = new Diary();
(diary as any).secret  // OK

console.log(Object.entries(diary));
// logs [["secret", "cheated on my English test"]]

💻 playground


class PasswordChecker {
  #passwordHash: number;

  constructor(passwordHash: number) {
    this.#passwordHash = passwordHash;
  }

  checkPassword(password: string) {
    return hash(password) === this.#passwordHash;
  }
}

const checker = new PasswordChecker(hash('s3cret'));
checker.#passwordHash
//      ~~~~~~~~~~~~~ Property '#passwordHash' is not accessible outside class
//                    'PasswordChecker' because it has a private identifier.
checker.checkPassword('secret');  // Returns false
checker.checkPassword('s3cret');  // Returns true

💻 playground