TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

Compiler options

Strict mode

"The strict flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness."

Strict mode

  • strictNullChecks
  • strictBindCallApply
  • strictFunctionTypes
  • strictPropertyInitialization
  • noImplicitAny
  • noImplicitThis
  • useUnknownInCatchVariables
  • ...

strictNullChecks

  • Enforce edge case implementation
  • Make assumptions about optionals visible
  • Easy to catch optionals during PR review
// Types for array.find

interface Array<T> {
    find(...): T | undefined;
}

// Example

const item2 = collection
  .find(item => item.id === '2');

console.log(item2.name);

// More robust code

if(item2 !== undefined) {
  console.log(item2.name);
} else {
  // Implement edge case
}

// More transparant code

console.log(item2!.name);

noImplicitAny

  • Use TS to full potential
  • Make code refactorable 
interface Person {
  id: string;
  name: string;
  age?: number;
}

function logName(person) {
  console.log(person.name);
}

function logName(person: Person) {
  console.log(person.name);
}

interface Identity {
  name: string;
}

interface Person extends Identity {
  id: string;
  age?: number;
}

function logName(identity: Identity) {
  console.log(identity.name);
}

Coupled

Built-in utilities

// Decoupled from Person
function logName(identity: Record<"name", string>) {
  console.log(identity.name);
}

// Equivalent to Record
function logName(identity: { name: string }) {
  console.log(identity.name);
}

Record

Record

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

let incomplete: Partial<Person> = {};
incomplete = { id: '1' };

const complete: Required<Person> = {
  id: '1',
  name: 'John Doe',
  age: 64
};

Partial/Required

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

// Whitelist
const personWithOnlyAge: Pick<Person, "age"> = {
  age: 60
};

// Blacklist
const personWithoutAge: Omit<Person, "age"> = {
  id: '1',
  name: 'John Doe'
};

const completePerson: Person = {
  ...personWithOnlyAge,
  ...personWithoutAge
};

Pick/Omit

// Omit before it was added to TS
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// Example of filtering object values on type 
type FilterFlags<Base, Type> = {
  [Key in keyof Base]: Base[Key] extends Type ? Key : never;
};

type AllowedNames<Base, Type> = FilterFlags<Base, Type>[keyof Base];

export type SubType<Base, Type> = Pick<Base, AllowedNames<Base, Type>>;

const personStrings: SubType<Person, string> = {
  id: '1',
  name: 'John Doe'
};

// Example of handling nested structures
export type RecursivePartial<T> = {
  [Key in keyof T]?: T[Key] extends (infer U)[]
    ? RecursivePartial<U>[]
    : T[Key] extends object
    ? RecursivePartial<T[Key]>
    : T[Key];
};

Advanced

String literal

interface Order {
  status: string;
}

enum OrderStatus {
  ...
}

interface Order {
  // Coupled to enum
  status: OrderStatus;
}

interface Order {
  // Typed with string literal
  status: "open" | "closed";
}

Magic strings

interface OpenOrder {
  status: 'open';
  deadline: Date;
}

interface ClosedOrder {
  status: 'closed';
  closedBy: Employee;
}

type Order = OpenOrder | ClosedOrder;

const order: Order = {...};

if(order.status === 'open') {
  order.


} else {
  order.
}

String literal

Generics<T>

// Decoupled from Person
interface Identity {
  name: string;
}

function logName<T extends Identity>(identity: T) {
  console.log(identity.name);
}

extends

Data normalization

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

const people: Person[] = [{...}, {...}, {...}];

// O(n)
people.find(({id}) => id === '1');

const normalizedPeople = {
  byId: {
    '1': {...},
    '2': {...},
    '3': {...}
  },
  allIds: ['1', '2', '3']
};
          
// O(1) 
normalizedPeople.byId['1']; 

    

Data normalization

type Identity = { id: string };

interface Normalized<T extends Identity> {
  byId: Record<string, T>;
  allIds: string[];
}
  
function normalize<T extends Identity>(collection: T[]): Normalized<T> {
  return collection.reduce(
    (acc, value) => ({
      byId: {
        ...acc.byId,
        [value.id]: value,
      },
      allIds: [...acc.allIds, value.id],
    }),
    {
      byId: {},
      allIds: [],
    } as Normalized<T>
  );
}

const people: Person[] = [...];
                          
const peopleNormalized = normalize(people);

// O(1)
peopleNormalized.byId['1'];

Data normalization

function toCollection<T extends Identity>(normalized: Normalized<T>): T[] {
  return normalized.allIds.map(id => normalized.byId[id]);
}
                          
const peopleNormalized: Normalized<Person> = {...};
                                              
const people = toCollection(peopleNormalized);

Questions?

TypeScript

By rachnerd

TypeScript

  • 164