TypeScript Tips and Tricks for Better Code
TypeScript Tips and Tricks for Better Code
TypeScript has become the de facto standard for building large-scale JavaScript applications. In this article, we'll explore advanced TypeScript patterns and techniques that will help you write more type-safe, maintainable code.
Utility Types You Should Know
TypeScript provides several built-in utility types that can save you time and make your code more expressive.
Partial<T>
Makes all properties of a type optional:
interface User {
name: string
email: string
age: number
}
type PartialUser = Partial<User>
// { name?: string; email?: string; age?: number }
Pick<T, K>
Select specific properties from a type:
type UserEmail = Pick<User, 'email'>
// { email: string }
Omit<T, K>
Remove specific properties from a type:
type UserWithoutEmail = Omit<User, 'email'>
// { name: string; age: number }
Record<K, V>
Create an object type with specific keys and values:
type UserRoles = Record<string, boolean>
// { [key: string]: boolean }
Advanced Type Patterns
Discriminated Unions
Use discriminated unions for type-safe state management:
type LoadingState = {
status: 'loading'
}
type SuccessState = {
status: 'success'
data: string[]
}
type ErrorState = {
status: 'error'
error: string
}
type AsyncState = LoadingState | SuccessState | ErrorState
function handleState(state: AsyncState) {
switch (state.status) {
case 'loading':
return 'Loading...'
case 'success':
return state.data.join(', ')
case 'error':
return state.error
}
}
Template Literal Types
Create types from string templates:
type EventName<T extends string> = `on${Capitalize<T>}`
type ClickEvent = EventName<'click'> // 'onClick'
type ChangeEvent = EventName<'change'> // 'onChange'
Conditional Types
Create types that depend on conditions:
type NonNullable<T> = T extends null | undefined ? never : T
type ApiResponse<T> = T extends string
? { message: T }
: { data: T }
Type Guards
Type guards help TypeScript narrow types:
function isString(value: unknown): value is string {
return typeof value === 'string'
}
function processValue(value: unknown) {
if (isString(value)) {
// TypeScript knows value is string here
console.log(value.toUpperCase())
}
}
Generic Constraints
Use constraints to limit what types can be used with generics:
interface Lengthwise {
length: number
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
logLength('hello') // OK
logLength([1, 2, 3]) // OK
logLength(123) // Error: number doesn't have length
Mapped Types
Create new types by transforming existing ones:
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
type Optional<T> = {
[P in keyof T]?: T[P]
}
Real-World Examples
API Response Types
type ApiResponse<T> = {
success: true
data: T
} | {
success: false
error: string
}
async function fetchUser(id: string): Promise<ApiResponse<User>> {
try {
const user = await api.getUser(id)
return { success: true, data: user }
} catch (error) {
return { success: false, error: error.message }
}
}
Form State Management
type FormField<T> = {
value: T
error?: string
touched: boolean
}
type FormState<T> = {
[K in keyof T]: FormField<T[K]>
}
interface LoginForm {
email: string
password: string
}
type LoginFormState = FormState<LoginForm>
Common Pitfalls and How to Avoid Them
1. Using any Too Liberally
Instead of any, use unknown:
// Bad
function process(data: any) {
return data.value
}
// Good
function process(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value
}
throw new Error('Invalid data')
}
2. Overusing Type Assertions
Prefer type guards over assertions:
// Bad
const value = data as string
// Good
if (typeof data === 'string') {
const value = data
}
Conclusion
TypeScript's type system is powerful and expressive. By mastering these patterns and techniques, you can write code that's not only type-safe but also more maintainable and self-documenting.
Remember: good TypeScript code is about finding the right balance between type safety and practicality. Don't over-engineer your types, but don't shy away from using advanced features when they add value.