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 Config

7. 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.