Skip to main content

generic-tips

Tip 1: Generics on the Type Level

Generics allow the creation of type functions, making code more reusable.

type MyGenericType<T> = {
data: T;
};

const example1: MyGenericType<string> = {
data: "Some string",
};

Tip 2: Passing Type Arguments to Functions

Functions can have type arguments to ensure return types are as expected.

function makeFetch<T>(url: string): Promise<T> {
return fetch(url).then((res) => res.json());
}

const result = makeFetch<{ firstName: string; lastName: string }>("api/user");

Tip 3: Passing Type Arguments to Set

You can specify type arguments in built-in JavaScript collections.

const mySet = new Set<number>();
mySet.add(1);
// mySet.add("string"); // This will cause an error

Tip 4: Inferring the Types

TypeScript can infer type arguments from the provided values.

function addIdToObject<T>(obj: T): T & { id: string } {
return {
...obj,
id: "unique-id",
};
}

const result = addIdToObject({ firstName: "John", lastName: "Doe" });

Tip 5: Constraints on Type Arguments

Constraints can be used to restrict the type of arguments a generic type can accept.

type GetPromiseReturnType<T extends Promise<any>> = T extends Promise<infer U> ? U : never;

async function fetchUserData(): Promise<{ name: string }> {
// ...fetch logic
return { name: "Alice" };
}

type UserData = GetPromiseReturnType<ReturnType<typeof fetchUserData>>;

Tip 6: Constraints in Functions

Type constraints in functions ensure that you pass the correct types.

function getKeyWithHighestValue<T extends Record<string, number>>(obj: T) {
return {
key: "highestValueKey",
value: 123,
};
}

const result = getKeyWithHighestValue({ a: 1, b: 2, c: 3 });

Tip 7: Sometimes 'as' is Fine

Type assertions with as can be used when you are confident about the type more than TypeScript can infer.

function typedObjectKeys<T>(obj: T): Array<keyof T> {
return Object.keys(obj) as Array<keyof T>;
}

const keys = typedObjectKeys({ a: 1, b: 2 });

Tip 8: Multiple Type Arguments

You can use multiple type arguments for more complex generics.

function getValue<TObj, TKey extends keyof TObj>(obj: TObj, key: TKey): TObj[TKey] {
return obj[key];
}

const value = getValue({ a: 1, b: "text" }, "b");

Tip 9: Defaults in Type Arguments

You can provide default type arguments in generics.

function createSet<T = string>() {
return new Set<T>();
}

const mySet = createSet<number>(); // Set<number>
const defaultSet = createSet(); // Set<string>

Tip 10: Integrating with Third-Party Libraries

Generics can help with integrating TypeScript types from third-party libraries like Zod.

import { z } from 'zod';

async function makeZodSafeFetch<T>(url: string, schema: z.ZodType<T>): Promise<T> {
const res = await fetch(url);
const result = await res.json();
return schema.parse(result);
}

const userSchema = z.object({
firstName: z.string(),
lastName: z.string(),
});

const userData = makeZodSafeFetch('api/user', userSchema);

These tips and examples demonstrate the power and versatility of TypeScript generics in developing type-safe and reusable code.