TypeScript is a powerhouse for modern JavaScript development, bringing type safety
and advanced features to the table.
While many developers know the basics, there are hidden gems and practical tricks that can make your code more efficient, clean, and maintainable.
Letβs dive into 20 TypeScript tricks that every developer should know, with examples and practical approaches! π»
1. Non-Nullable Types (NonNullable
) β
TypeScript provides the NonNullable
utility to eliminate null
and undefined
from a type. This can help you avoid unexpected null values.
type User = { name: string; age?: number | null };
const user: NonNullable<User["age"]> = 30; // β
No null or undefined allowed
2. Using Partial
for Flexibility π§
The Partial<T>
utility makes all properties in a type optional, which is great when you're updating only a subset of object fields.
interface User {
name: string;
age: number;
email: string;
}
const updateUser = (user: Partial<User>) => {
// You can pass only the fields you want to update
return { ...user, updatedAt: new Date() };
};
updateUser({ name: 'John' }); // No need to provide the entire object
3. Leverage Readonly
for Immutable Data π
When you need immutability in TypeScript, Readonly<T>
makes all properties of a type immutable, preventing reassignment.
const config: Readonly<{ apiUrl: string; retries: number }> = {
apiUrl: 'https://api.example.com',
retries: 5
};
config.apiUrl = 'https://newapi.com'; // β Error: Cannot assign to 'apiUrl' because it is a read-only property
4. Mapped Types for Dynamic Property Typing π οΈ
Mapped types let you create new types by transforming existing ones. This is handy for creating variations of an object type.
type Status = 'loading' | 'success' | 'error';
type ApiResponse<T> = {
[K in Status]: T;
};
const response: ApiResponse<string> = {
loading: 'Fetching...',
success: 'Data loaded',
error: 'Something went wrong'
};
5. Tuple Types with Optional Elements π’
Did you know TypeScript allows optional elements in tuples? This is great when dealing with variadic function arguments.
type UserTuple = [string, number?, boolean?];
const user1: UserTuple = ['Alice']; // β
Just the name
const user2: UserTuple = ['Bob', 30]; // β
Name and age
const user3: UserTuple = ['Charlie', 25, true]; // β
Full tuple
6. Union Types with Exhaustive Checks π
Ensure you're handling all possible cases with union types and exhaustive checks in switch statements.
type Status = 'open' | 'closed' | 'pending';
function handleStatus(status: Status) {
switch (status) {
case 'open':
return 'Opened';
case 'closed':
return 'Closed';
case 'pending':
return 'Pending';
default:
const exhaustiveCheck: never = status; // β Error if a new status type is added but not handled
return exhaustiveCheck;
}
}
7. Utility Type Omit
for Excluding Keys ποΈ
Sometimes you need to create an object type that excludes certain keys. Omit
is your friend here!
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, 'description'>;
const todo: TodoPreview = {
title: 'Learn TypeScript',
completed: false
};
8. Type Narrowing with in
and instanceof
π΅οΈ
Use in
and instanceof
to narrow down types at runtime.
function processInput(input: string | number | { title: string }) {
if (typeof input === 'string') {
return input.toUpperCase(); // Narrowed to string
} else if (typeof input === 'number') {
return input * 2; // Narrowed to number
} else if ('title' in input) {
return input.title; // Narrowed to object with title property
}
}
9. Conditional Types for Advanced Type Logic π
Conditional types give you incredible flexibility for transforming types based on conditions.
type IsString<T> = T extends string ? true : false;
type CheckString = IsString<'Hello'>; // true
type CheckNumber = IsString<42>; // false
10. Use as const
for Immutable Literal Types π
as const
is great for freezing values and ensuring that TypeScript treats them as literal types, not mutable values.
const COLORS = ['red', 'green', 'blue'] as const;
type Color = typeof COLORS[number]; // 'red' | 'green' | 'blue'
Follow me on github:
11. Extract and Exclude to Refine Types π§Ή
Use Extract
and Exclude
to filter out or pick specific types from a union.
type T = 'a' | 'b' | 'c';
type OnlyAOrB = Extract<T, 'a' | 'b'>; // 'a' | 'b'
type ExcludeC = Exclude<T, 'c'>; // 'a' | 'b'
12. Type Guards for Custom Validation β
Create your own type guards to refine types dynamically at runtime.
function isString(input: any): input is string {
return typeof input === 'string';
}
const value: any = 'Hello';
if (isString(value)) {
console.log(value.toUpperCase()); // Safe: value is a string here
}
13. Use Record
for Dynamic Object Types π
When you need a type for an object with dynamic keys, Record<K, V>
is the perfect fit.
type Role = 'admin' | 'user' | 'guest';
const permissions: Record<Role, string[]> = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read']
};
14. Dynamic Class Properties with Index Signatures ποΈ
Index signatures allow you to create objects or classes with dynamically named properties.
class DynamicObject {
[key: string]: any;
}
const obj = new DynamicObject();
obj.name = 'Alice';
obj.age = 30;
15. never
Type for Impossible States π«
The never
type represents values that should never occur. It's commonly used in exhaustive checks.
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
16. Optional Chaining for Safe Property Access π
Use optional chaining (?.
) to safely access deeply nested properties without worrying about undefined
errors.
const user = { profile: { name: 'John' } };
const userName = user?.profile?.name; // 'John'
const age = user?.profile?.age ?? 'Not provided'; // Fallback to default
17. Default Values with Nullish Coalescing (??
) π
Use the nullish coalescing operator to provide a fallback value only if the original value is null
or undefined
.
const input: string | null = null;
const defaultValue = input ?? 'Default'; // 'Default'
18. Inferring Return Types with ReturnType
π
The ReturnType<T>
utility extracts the return type of a function, which can be helpful when you're dealing with complex types.
function getUser() {
return { name: 'John', age: 30 };
}
type UserReturn = ReturnType<typeof getUser>; // { name: string; age: number; }
19. Type Parameters in Functions π§βπ»
Generic type parameters make your functions flexible and reusable across different types.
function identity<T>(value: T): T {
return value;
}
identity<string>('Hello'); // 'Hello'
identity<number>(42); // 42
20. Intersection Types to Combine Structures β
Intersection types let you combine multiple types into one.
type Admin = { privileges: string[] };
type User = { name: string };
type AdminUser = Admin & User;
const adminUser: AdminUser = {
privileges: ['admin', 'editor'],
name: 'Alice'
};
These tricks will help you take your TypeScript skills to the next level! π₯ Keep experimenting and integrating these patterns into your projects for cleaner, more efficient code. Happy coding! ππ¨βπ»