Loading...
Please wait while we prepare your content
Please wait while we prepare your content

TypeScript has become an essential tool in modern web development. Let's explore the best practices that will help you write better TypeScript code in 2024.
Always enable strict mode in your tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}
Let TypeScript infer types when possible:
// Good
const numbers = [1, 2, 3]; // Type: number[]
// Bad
const numbers: number[] = [1, 2, 3];
interface User {
id: number;
name: string;
email: string;
}
// Extending interfaces
interface AdminUser extends User {
role: "admin";
permissions: string[];
}
type Status = "pending" | "approved" | "rejected";
type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
// Partial
type PartialUser = Partial<User>;
// Pick
type UserCredentials = Pick<User, "email" | "password">;
// Omit
type PublicUser = Omit<User, "password">;
// Record
type UserRoles = Record<string, string[]>;
function isAdmin(user: User): user is AdminUser {
return "role" in user && user.role === "admin";
}
function processUser(user: User) {
if (isAdmin(user)) {
// TypeScript knows user is AdminUser here
console.log(user.permissions);
}
}
class ValidationError extends Error {
constructor(
public field: string,
message: string,
) {
super(message);
this.name = "ValidationError";
}
}
function validateUser(user: User): void {
if (!user.email.includes("@")) {
throw new ValidationError("email", "Invalid email format");
}
}
type Result<T, E = Error> =
| {
success: true;
data: T;
}
| {
success: false;
error: E;
};
async function fetchUser(id: number): Promise<Result<User>> {
try {
const response = await api.get(`/users/${id}`);
return { success: true, data: response.data };
} catch (error) {
return { success: false, error: error as Error };
}
}
interface ButtonProps {
variant: "primary" | "secondary";
size: "small" | "medium" | "large";
onClick: () => void;
children: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({
variant,
size,
onClick,
children,
}) => {
// Component implementation
};
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue] as const;
}
describe("UserService", () => {
it("should create a new user", async () => {
const user: User = {
id: 1,
name: "John Doe",
email: "john@example.com",
};
const result = await createUser(user);
expect(result).toEqual(user);
});
});
const mockApi = {
get: jest.fn<Promise<User>, [string]>(),
post: jest.fn<Promise<User>, [string, Partial<User>]>(),
};
Following these TypeScript best practices will help you write more maintainable, type-safe, and robust applications. Remember that TypeScript is a tool to help you catch errors early and provide better developer experience, so use it wisely and consistently throughout your project.