TypeScriptJavaScriptProgramming
10 TypeScript Tips That Will Make You a Better Developer
Level up your TypeScript skills with these practical tips and patterns that I use every day.
December 10, 20243 min read
10 TypeScript Tips That Will Make You a Better Developer
After years of working with TypeScript, I've collected a set of patterns and tips that have significantly improved my code quality. Here are my favorites.
1. Use const Assertions for Literal Types
// Without const assertion
const config = { theme: "dark", version: 1 };
// type: { theme: string; version: number }
// With const assertion
const config = { theme: "dark", version: 1 } as const;
// type: { readonly theme: "dark"; readonly version: 1 }2. Discriminated Unions for State Management
type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: User[] };
type ErrorState = { status: "error"; error: string };
type State = LoadingState | SuccessState | ErrorState;
function handleState(state: State) {
switch (state.status) {
case "loading":
return <Spinner />;
case "success":
return <UserList users={state.data} />;
case "error":
return <Error message={state.error} />;
}
}3. Template Literal Types
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// type Handler = "onClick" | "onFocus" | "onBlur"4. The satisfies Operator
type Colors = Record<string, [number, number, number]>;
const palette = {
red: [255, 0, 0],
green: [0, 255, 0],
blue: [0, 0, 255],
} satisfies Colors;
// palette.red is [number, number, number], not (number | string)[]5. Branded Types for Type Safety
type UserId = string & { readonly brand: unique symbol };
type OrderId = string & { readonly brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId) { /* ... */ }
const userId = createUserId("123");
const orderId = "456" as OrderId;
getUser(userId); // ✓ OK
getUser(orderId); // ✗ Error: Argument of type 'OrderId' is not assignable6. Utility Types You Should Know
// Pick specific properties
type UserPreview = Pick<User, "id" | "name">;
// Omit specific properties
type UserWithoutPassword = Omit<User, "password">;
// Make all properties optional
type PartialUser = Partial<User>;
// Make all properties required
type RequiredUser = Required<User>;
// Extract return type
type FetchResult = ReturnType<typeof fetchUser>;7. Generic Constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "John", age: 30 };
getProperty(user, "name"); // ✓ OK
getProperty(user, "email"); // ✗ Error8. Conditional Types
type ApiResponse<T> = T extends Array<infer U>
? { items: U[]; total: number }
: { data: T };
type UserResponse = ApiResponse<User>;
// { data: User }
type UsersResponse = ApiResponse<User[]>;
// { items: User[]; total: number }9. Type Guards
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value
);
}
function processData(data: unknown) {
if (isUser(data)) {
// data is now typed as User
console.log(data.name);
}
}10. Mapped Types
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// {
// getName: () => string;
// getAge: () => number;
// }Conclusion
TypeScript's type system is incredibly powerful. Master these patterns and you'll write safer, more maintainable code.