Tại sao cần tips TypeScript?
Nhiều người dùng TypeScript nhưng thực ra chỉ đang viết JavaScript với any và // @ts-ignore. TypeScript mạnh nhất khi bạn để compiler làm việc cho bạn — không phải chống lại bạn. Dưới đây là 10 tips mình thường xuyên áp dụng trong dự án thực tế.
1. Dùng Union Types thay vì boolean flags
typescript
// ❌ Khó đọc, dễ có state không hợp lệ
interface Post {
isLoading: boolean
isError: boolean
data: Post | null
}
// ✅ Rõ ràng, không thể có trạng thái mâu thuẫn
type PostState =
| { status: "loading" }
| { status: "error"; message: string }
| { status: "success"; data: Post }2. Utility Types: Partial, Required, Pick, Omit
typescript
interface User {
id: string
name: string
email: string
password: string
createdAt: Date
}
// Chỉ cần một số fields
type UserResponse = Omit<User, "password">
// Update request — mọi field đều optional
type UpdateUserDto = Partial<Pick<User, "name" | "email">>
// Tạo type mới từ type cũ
type CreateUserDto = Omit<User, "id" | "createdAt">3. Type Narrowing với Type Guards
typescript
interface Cat { type: "cat"; meow(): void }
interface Dog { type: "dog"; bark(): void }
type Animal = Cat | Dog
function makeSound(animal: Animal) {
// TypeScript biết chính xác loại trong mỗi branch
if (animal.type === "cat") {
animal.meow() // ✅ TypeScript biết đây là Cat
} else {
animal.bark() // ✅ TypeScript biết đây là Dog
}
}4. Const Assertion để giữ literal types
typescript
// ❌ TypeScript infer là string[]
const colors = ["red", "green", "blue"]
// ✅ TypeScript giữ nguyên literal types: readonly ["red", "green", "blue"]
const colors = ["red", "green", "blue"] as const
type Color = typeof colors[number] // "red" | "green" | "blue"5. Template Literal Types
typescript
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"
type Endpoint = "/users" | "/posts" | "/comments"
// Tự động tạo combinations
type ApiRoute = `${HttpMethod} ${Endpoint}`
// = "GET /users" | "GET /posts" | "POST /users" | ...
type EventName = "click" | "focus" | "blur"
type Handler = `on${Capitalize<EventName>}`
// = "onClick" | "onFocus" | "onBlur"6. Satisfies operator (TypeScript 4.9+)
typescript
type Config = {
port: number
host: string
debug?: boolean
}
// ✅ Vừa check type, vừa giữ literal type inference
const config = {
port: 3000,
host: "localhost",
debug: true,
} satisfies Config
// config.port vẫn là type number (không bị widened)
// Nhưng được validate theo Config7. Tránh dùng any — dùng unknown thay thế
typescript
// ❌ any tắt hoàn toàn type checking
function parseData(data: any) {
return data.user.name // Không có lỗi dù có thể crash runtime
}
// ✅ unknown buộc bạn phải check type trước
function parseData(data: unknown) {
if (typeof data === "object" && data !== null && "user" in data) {
const user = (data as { user: { name: string } }).user
return user.name // An toàn
}
throw new Error("Invalid data")
}8. Mapped Types để transform types
typescript
type Nullable<T> = { [K in keyof T]: T[K] | null }
type Optional<T> = { [K in keyof T]?: T[K] }
type Readonly<T> = { readonly [K in keyof T]: T[K] }
// Thực tế: tạo form state từ data type
interface LoginForm {
email: string
password: string
}
type LoginFormErrors = Partial<Record<keyof LoginForm, string>>
// = { email?: string; password?: string }9. Infer trong Conditional Types
typescript
// Lấy return type của async function
type Awaited<T> = T extends Promise<infer R> ? R : T
// Lấy props type của React component
type ComponentProps<T> = T extends React.ComponentType<infer P> ? P : never
// Ví dụ thực tế
async function fetchUser() {
return { id: "1", name: "Khang" }
}
type UserType = Awaited<ReturnType<typeof fetchUser>>
// = { id: string; name: string }10. Discriminated Unions cho API responses
typescript
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string; code: number }
async function fetchPost(id: string): Promise<ApiResponse<Post>> {
try {
const post = await db.posts.findById(id)
return { success: true, data: post }
} catch (err) {
return { success: false, error: "Not found", code: 404 }
}
}
// Tại nơi gọi:
const result = await fetchPost("123")
if (result.success) {
console.log(result.data.title) // ✅ TypeScript biết data tồn tại
} else {
console.log(result.error) // ✅ TypeScript biết error tồn tại
}Kết luận
TypeScript mạnh nhất khi bạn để nó guide code của mình. Đừng cố "workaround" compiler — nếu TypeScript báo lỗi, thường đó là vì code bạn có vấn đề thực sự. Hãy bắt đầu với những tips đơn giản như Union Types và Utility Types, rồi dần dần khám phá Advanced Types khi cần.
