Skip to main content

More on Typescript

Type Narrowing

  • Type narrowing is just what it sounds like narrowing down a general type into something more precise.

Typeof for Primitives

if(typeof input  === 'string'){
console.log(input.toUpperCase());
}

instanceOf for Objects

if(input instanceOf Array){
//do sth
}

if(input instanceOf MyClass){
//do sth
}

Property in Object

const output = getResult(req);
if("error" in output){
consol.error(output.error)
}

Utility Types

Partial

  • Constructs a type with all properties of Type set to optional.
  • This utility will return a type that represents all subsets of a given type.
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",
});

Required

  • Constructs a type consisting of all properties of Type set to required.
  • The opposite of Partial.
interface Props {
a?: number;
b?: string;
}

const obj: Props = { a: 5 };

const obj2: Required<Props> = { a: 5 }; //error => b is required

Readonly

  • Constructs a type with all properties of Type set to readonly, meaning the properties of the constructed type cannot be reassigned.
interface Todo {
title: string;
}

const todo: Readonly<Todo> = {
title: "Delete inactive users",
};

todo.title = "Hello"; //error cannot reassign

readonly can be useful when working with react -> hooks, to inform states being immutable

export function EditEvent() {
const [event, setEvent] = useState<Event>()

event.title = 'Sth' //not error, but bad practice
setEvent(event);
}
export function EditEvent() {
const [event, setEvent] = useState<Readonly<Event>>()

event.title = 'Sth' //error, cannot assign to readonly

//better, do
setEvent({...event, title: event.target.value})
}

DeepReadonly

  • consider the following code where readonly doesn't meet our need.
type Event = {
title: string,
body: string,
members: string[]
}

const readonlyEvent: Readonly<Event> = {
title: 'event title',
body: 'event body',
members: ['m1', 'm2']
}

//since readonly we can't edit title and body of readonlyEvent
readonlyEvent.title = 'event title 2' //error

//but we can update, members
readonlyEvent.memebers.push('m3');
  • This is because
    • Readonly only applies to top level properties of an object. We can still mutate nested properties and arrays without errors.
    • To prevent this use DeepReadonly, which is a custom snippet not found in typescript utility types
export type DeepReadonly<T> =
T extends Primitive ? T :
T extends Array<infer U> ? DeepReadonlyArray<U> :
DeepReadonlyObject<T>

type Primitive =
string | number | boolean | undefined | null

interface DeepReadonlyArray<T>
extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>
}
const readonlyEvent: DeepReadonly<Event> = {
title: 'event title',
body: 'event body',
members: ['m1', 'm2']
}

//since readonly we can't edit title and body of readonlyEvent
readonlyEvent.title = 'event title 2' //error

//but we can update, members
readonlyEvent.memebers.push('m3'); //error

Record<Keys, Type>

  • Constructs an object type whose property keys are Keys and whose property values are Type.
  • This utility can be used to map the properties of a type to another type.
interface CatInfo {
age: number;
breed: string;
}

type CatName = "miffy" | "boris" | "mordred";

const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};

console.log(cats.boris);

Pick<Type, Keys>

  • Constructs a type by picking the set of properties Keys (string literal or union of string literals) from Type.
interface Todo {
title: string;
description: string;
completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">; //equivalent to => i want just these

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

Omit<Type, Keys>

  • Constructs a type by picking all properties from Type and then removing Keys (string literal or union of string literals).
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}

type TodoInfo = Omit<Todo, "completed" | "createdAt">; //all except this

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

todoInfo;

ReturnType

  • To grab the type returned from a function, we can use the ReturnType utility
    • this is useful when the library/package we're using doesn't provide typescript types
function add(a: number, b: number): number {
return a + b;
}

type AddReturnType = ReturnType<typeof add>;
// type AddReturnType = number;
async function featchData(a: number, b: number): Promise<number> {
return //...;
}

type AddReturnType = ReturnType<typeof featchData>;
// type AddReturnType = Promise<number>;

Awaited

  • now in the above second ReturnType example the featchData type was an async function.
    • the return type will be promise, which we don't want
    • to fix this we can use the Awaited type to unwrap the promise and get the type of what the promise resolves to:
function featchData(a: number, b: number): Promise<number> {
return //...;
}

type AddReturnType = Awaited<ReturnType<typeof featchData>>
// type AddReturnType = number;

Parameters

  • Same as ReturnType, we can use Parameters to get the parameter types of a function.
  • Parameters gives you a tuple of the argument types, and you can pull out a specific parameter type by index like so:
function sum(a: number, b?:number){
//...
}

type SumParams = Parameters<typeof sum>
[number, number | undefined]

type param1 = SumParams[0]; //param1 is now type number
type params2 = SumParams[1]; //param2 is now type number | undefined

NonNullable

  • in the above example Parameters, what if we want params2 to be type number not number | undefined
  • we can use the NonNullable utility type, to exclude any null or undefined values from a union type.
type params2 = NonNullable<SumParams[1]>; //param2 is now type number

//or
type params2 = NonNullable<Parameters<typeof Sum>[1]>
type NullableString = string | null | undefined;

type NonNullableString = NonNullable<NullableString>;
// type NonNullableString = string;