30 TypeScript Tips

Type Inference
Basics
TypeScript can infer types automatically in many cases.
let message = "Hello, TypeScript!";
console.log(typeof message); // Output: string
Union Types
Basics
Union types allow a value to be one of several types.
function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
printId(101); // OK
printId("202"); // OK
Type Aliases
Basics
Create a new name for a type using type aliases.
type Point = {
  x: number;
  y: number;
};

const point: Point = { x: 10, y: 20 };
console.log(point);
Interfaces
Basics
Interfaces define the structure of objects.
interface Person {
  name: string;
  age: number;
}

const user: Person = { name: "Alice", age: 30 };
console.log(user);
Optional Properties
Intermediate
Make properties optional using the ? operator.
interface Car {
  make: string;
  model: string;
  year?: number;
}

const car: Car = { make: "Toyota", model: "Corolla" };
console.log(car);
Readonly Properties
Intermediate
Use readonly to prevent properties from being changed.
interface Point {
  readonly x: number;
  readonly y: number;
}

const point: Point = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.
console.log(point);
Function Types
Intermediate
Define types for functions.
type MathFunc = (a: number, b: number) => number;

const add: MathFunc = (a, b) => a + b;
console.log(add(5, 10)); // Output: 15
Generics
Advanced
Create reusable components that can work with a variety of types.
function identity<T>(arg: T): T {
  return arg;
}

console.log(identity<string>("Hello")); // Output: Hello
console.log(identity<number>(42)); // Output: 42
Enum
Intermediate
Enums allow us to define a set of named constants.
enum Direction {
  Up,
  Down,
  Left,
  Right
}

console.log(Direction.Up); // Output: 0
console.log(Direction[2]); // Output: Left
Tuple Types
Intermediate
Tuples let you express an array with a fixed number of elements whose types are known.
let tuple: [string, number] = ["hello", 10];
console.log(tuple[0].substring(1)); // OK
// console.log(tuple[1].substring(1)); // Error, 'number' does not have 'substring'
Null and Undefined
Basics
TypeScript has two special types, null and undefined, for values that aren't there.
let u: undefined = undefined;
let n: null = null;

console.log(typeof u); // Output: undefined
console.log(typeof n); // Output: object
Never Type
Advanced
The never type represents the type of values that never occur.
function error(message: string): never {
  throw new Error(message);
}

// Inferred return type is never
function fail() {
  return error("Something failed");
}

// Uncomment to see the error
// fail();
Type Assertions
Intermediate
Type assertions are a way to tell the compiler 'trust me, I know what I'm doing.'
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
console.log(strLength); // Output: 16
Intersection Types
Advanced
Intersection types combine multiple types into one.
interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}

type ColorfulCircle = Colorful & Circle;

const cc: ColorfulCircle = {
  color: "red",
  radius: 42
};
console.log(cc);
Literal Types
Intermediate
A literal is a more concrete sub-type of a collective type.
type Easing = "ease-in" | "ease-out" | "ease-in-out";

function animate(dx: number, dy: number, easing: Easing) {
  console.log(`Moving ${dx}px, ${dy}px with ${easing}`);
}

animate(0, 0, "ease-in");
// animate(0, 0, "linear"); // Error: Argument of type '"linear"' is not assignable to parameter of type 'Easing'.
keyof Operator
Advanced
The keyof operator takes an object type and produces a string or numeric literal union of its keys.
interface Person {
  name: string;
  age: number;
}

type PersonKeys = keyof Person; // "name" | "age"

function getProperty(obj: Person, key: PersonKeys) {
  return obj[key];
}

let person = { name: "Alice", age: 30 };
console.log(getProperty(person, "name")); // Output: Alice
// console.log(getProperty(person, "location")); // Error: Argument of type '"location"' is not assignable to parameter of type 'keyof Person'.
Mapped Types
Advanced
A mapped type is a generic type which uses a union of PropertyKeys to iterate through keys to create a type.
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

interface Person {
  name: string;
  age: number;
}

const person: Readonly<Person> = {
  name: "Alice",
  age: 30
};

// person.name = "Bob"; // Error: Cannot assign to 'name' because it is a read-only property.
console.log(person);
Conditional Types
Advanced
Conditional types select one of two possible types based on a condition expressed as a type relationship test.
type NonNullable<T> = T extends null | undefined ? never : T;

type ResultType = NonNullable<string | number | undefined>;
// ResultType is string | number

let result: ResultType = "Hello";
console.log(typeof result); // Output: string
Utility Types: Partial
Utility Types
Partial<T> constructs a type with all properties of T set to optional.
interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
  title: "organize desk",
  description: "clear clutter"
};

const todo2 = updateTodo(todo1, {
  description: "throw out trash"
});

console.log(todo2);
Utility Types: Required
Utility Types
Required<T> constructs a type consisting of all properties of T set to required.
interface Props {
  a?: number;
  b?: string;
}

const obj: Props = { a: 5 };

const obj2: Required<Props> = { a: 5, b: "test" };

console.log(obj);
console.log(obj2);
Utility Types: Pick
Utility Types
Pick<T, K> constructs a type by picking the set of properties K from T.
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false
};

console.log(todo);
Utility Types: Omit
Utility Types
Omit<T, K> constructs a type by picking all properties from T and then removing K.
interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}

type TodoInfo = Omit<Todo, "completed" | "createdAt">;

const todoInfo: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm"
};

console.log(todoInfo);
Utility Types: ReturnType
Utility Types
ReturnType<T> constructs a type consisting of the return type of function T.
function f1(): { a: number; b: string } {
  return { a: 1, b: "test" };
}

type T0 = ReturnType<typeof f1>;
// type T0 = { a: number; b: string; }

const result: T0 = { a: 2, b: "example" };
console.log(result);
Type Guards
Advanced
Type guards are some expression that performs a runtime check that guarantees the type in some scope.
function isString(test: any): test is string {
  return typeof test === "string";
}

function example(x: unknown) {
  if (isString(x)) {
    console.log(x.toUpperCase());
  } else {
    console.log("Not a string");
  }
}

example("hello"); // Output: HELLO
example(123); // Output: Not a string
Index Signatures
Intermediate
Index signatures are used for objects without a defined list of properties.
interface StringArray {
  [index: number]: string;
}

const myArray: StringArray = ["Bob", "Fred"];
console.log(myArray[0]); // Output: Bob
Decorators
Advanced
Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members.
function logged(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with `, args);
    return originalMethod.apply(this, args);
  };
  return descriptor;
}

class Calculator {
  @logged
  add(a: number, b: number) {
    return a + b;
  }
}

const calc = new Calculator();
console.log(calc.add(1, 2)); // Output: Calling add with [1, 2] 
 3
Mixins
Advanced
Mixins are a way to create classes from reusable components.
// Disposable Mixin
class Disposable {
  isDisposed: boolean = false;
  dispose() {
    this.isDisposed = true;
  }
}

// Activatable Mixin
class Activatable {
  isActive: boolean = false;
  activate() {
    this.isActive = true;
  }
  deactivate() {
    this.isActive = false;
  }
}

class SmartObject implements Disposable, Activatable {
  constructor() {
    setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
  }

  interact() {
    this.activate();
  }

  // Disposable
  isDisposed: boolean = false;
  dispose: () => void;
  // Activatable
  isActive: boolean = false;
  activate: () => void;
  deactivate: () => void;
}

applyMixins(SmartObject, [Disposable, Activatable]);

let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);

////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////

function applyMixins(derivedCtor: any, baseCtors: any[]) {
  baseCtors.forEach(baseCtor => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name)!
      );
    });
  });
}

// Uncomment to see the output
// smartObj;
Namespace
Advanced
Namespaces are used to organize code into logical groups and to prevent name collisions in your code.
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = /^[0-9]+$/;

  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }

  export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
      return s.length === 5 && numberRegexp.test(s);
    }
  }
}

// Some samples to try
let strings = ["Hello", "98052", "101"];

// Validators to use
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// Show whether each string passed each validator
strings.forEach(s => {
  for (let name in validators) {
    console.log(
      `"${s}" - ${
        validators[name].isAcceptable(s) ? "matches" : "does not match"
      } ${name}`
    );
  }
});
Symbols
Advanced
Symbols are primitive values guaranteed to be unique, even if they have the same description.
const sym1 = Symbol("key");
const sym2 = Symbol("key");

console.log(sym1 === sym2); // Output: false

const obj = {
  [sym1]: "Value for sym1",
  [sym2]: "Value for sym2"
};

console.log(obj[sym1]); // Output: Value for sym1
console.log(obj[sym2]); // Output: Value for sym2
Template Literal Types
Advanced
Template literal types build on string literal types, and have the ability to expand into many strings via unions.
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

const id: AllLocaleIDs = "welcome_email_id";
console.log(id);