Skip to main content

Typescript

1. Enum as const

How to create an enum without using Enum keyword:

export const ROUTES = {
Home: '/',
Admin: '/admin',
Users: '/users',
} as const;

type TypeOfRoutes = typeof ROUTES;
export type KeysOfRoutes = keyof TypeOfRoutes; // <--- Use when need the key on the enum that's used to retrieve the value.
export type RoutesEnumType = TypeOfRoutes[KeysOfRoutes]; // <--- Use when in need of enum value

The as const makes objects readonly. It is telling the IDE that this object cannot be changed. Meaning that trying to modify a key's value later would result in a typescript error. You'll might notice that we could have used Object.freeze(). It does the same thing, and it works on the type level too. There are 2 main differences though. One is that it also prevents modifications at runtime as well, and two, that it only prevents modifications of values on the top-level keys.

Next up, we have TypeOfRouts. Since routes is a variable inited with the const keyword, it's in the javascript world. But we need its type, so we need to move it to the typescript world. For that we use the typeof keyword.


2. Prettify

You can create a type prettifier yourself:

// Define Prettify: a utility type that makes the type more readable
export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};

// ---------------------

// Example usage:

type ComplexType = {
name: string;
age: number;
location: string;
} & Omit<
{
c: string;
hobbies: string[];
isActive: boolean;
},
'c'
> &
Record<'metadata', string[]>;

export type PrettyComplexType = Prettify<ComplexType>;

3. Extract, Exclude, Pick & Omit

Extract and exclude are for Unions, Pick & Omit are for objects.

type User = {
id: number;
name: string;
age: number;
};

export type UserWithoutId = Omit<User, 'id'>;
export type UserWithId = Pick<User, 'id'>;

// -----------------------------------

type TypeA = {
type: 'aaa';
body: {
id: number;
name: string;
};
};

type TypeB = {
type: 'bbb';
body: {
scriptId: number;
};
};

type TypeC = {
type: 'ccc';
body: {
location: string;
};
};

type AllTypes = TypeA | TypeB | TypeC;

export type TwoTypes = Exclude<AllTypes, TypeA | TypeB>;
export type OneType = Exclude<AllTypes, TypeA | TypeB | TypeC>;

4. Key Optional V.S. Value Optional

Try switching the traceId of doThing & doAnotherThing.

/**
* Key Optional V.S. Value Optional
*/

type MainProps = {
traceId?: string;
};
function main(props: MainProps) {
const { traceId } = props;

doThing({ traceId });
doAnotherThing({ traceId });
}

type DoThingProps = {
traceId?: string;
// traceId: string | undefined;
};
function doThing(props: DoThingProps) {
const { traceId } = props;

console.log('traceId is:', traceId);
}

type DoAnotherThingProps = {
traceId?: string;
// traceId: string | undefined;
};
function doAnotherThing(props: DoAnotherThingProps) {
const { traceId } = props;

console.log('traceId is:', traceId);
}

main({ traceId: '12345' });

5. Mapped Types

type User = {
id: number;
name: string;
age: number;
};

// --------------------

export type TransformedUser1 = {
[K in keyof User]: K;
};

export type TransformedUser2 = {
[K in keyof User as K]: User[K];
};

export type TransformedUser3 = {
readonly [K in keyof User]?: User[K];
};

export type TransformedUser4 = {
readonly [K in keyof User as `get${Capitalize<K>}`]: () => User[K];
};

6. Immediately Indexed Mapped Types

import { Prettify } from './prettify';

// The example type:
type Actions = {
login: {
username: string;
password: string;
};
logout: {
reason: string;
};
updateProfile: {
userId: number;
profileData: {
name: string;
email: string;
age: number;
};
};
};

// ---------------------------------

export type ActionsAsUnion1 = {
[K in keyof Actions]: any;
};

export type ActionsAsUnion2 = {
[K in keyof Actions]: {
type: K;
};
};

export type ActionsAsUnion3 = {
[K in keyof Actions]: {
type: K;
} & Actions[K];
};

export type ActionsAsUnion4 = {
[K in keyof Actions]: Prettify<
{
type: K;
} & Actions[K]
>;
};

// And finally, here we're using the immediately indexed mapped types (IIMP)
export type ActionsAsUnion5 = {
[K in keyof Actions]: Prettify<
{
type: K;
} & Actions[K]
>;
}[keyof Actions];

// This is ALMOST the same as doing:
export type ActionsAsUnion6 = Actions['login' | 'logout' | 'updateProfile'];
// or just:
export type ActionsAsUnion7 = Actions[keyof Actions];

7. function isCat(props: Animal): props is Cat

type Cat = {
message: string;
age: number;
type: string;
};

type Dog = {
id: number;
age: number;
email: string;
};

type Animal = Cat | Dog;

function getIsCat(animal: Animal): animal is Cat {
return 'message' in animal;
}

const animal = { message: 'Hello, world!', type: 'doSomething' } as any;
const isCat = getIsCat(animal);

if (isCat) {
console.log(animal.message); // <--- would infer as Cat, showing only Cat fields.
} else {
// could be anything! Not necessarily a Dog! Inference does not work here.
}