Item 72: Prefer ECMAScript Features to TypeScript Features
May 10, 2024 Β· View on GitHub
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"
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'
function scoop(flavor: Flavor) { /* ... */ }
scoop('vanilla');
// ~~~~~~~~~ '"vanilla"' is not assignable to parameter of type 'Flavor'
import {Flavor} from 'ice-cream';
scoop(Flavor.Vanilla); // OK
type Flavor = 'vanilla' | 'chocolate' | 'strawberry';
let favoriteFlavor: Flavor = 'chocolate'; // OK
favoriteFlavor = 'americone dream';
// ~~~~~~~~~~~ Type '"americone dream"' is not assignable to type 'Flavor'
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Person {
constructor(public name: string) {}
}
class Person {
first: string;
last: string;
constructor(public name: string) {
[this.first, this.last] = name.split(' ');
}
}
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
// other.ts
namespace foo {
export function bar() {}
}
// index.ts
/// <reference path="other.ts"/>
foo.bar();
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
class Diary {
private secret = 'cheated on my English test';
}
const diary = new Diary();
diary.secret
// ~~~~~~ Property 'secret' is private and only accessible within ... 'Diary'
const diary = new Diary();
(diary as any).secret // OK
console.log(Object.entries(diary));
// logs [["secret", "cheated on my English test"]]
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