TypeScript 类型体操:从入门到"真香"の完全指南

摘要:TypeScript 的类型系统,表面上是"给 JavaScript 加个类型",实际上是一门图灵完备的编程语言。本文将带你从"any 大法好"进化到"类型体操运动员",用 15 个实战案例,让你的代码从"能跑就行"升级到"类型安全の艺术品"。警告:学完可能会对 any 产生生理性不适。


引言:一个关于类型的"真香"故事

三个月前的我:

typescript 复制代码
// 管他什么类型,any 一把梭!
const data: any = await fetchData()
const result: any = processData(data)
// 能跑就行,类型是什么?能吃吗?

三个月后的我:

typescript 复制代码
// 这类型推导,这自动补全,这编译时检查...真香!
const data = await fetchData<UserResponse>()
const result = processData(data) // 完美的类型推导
// IDE 告诉我 result.user.name 是 string,爽!

转变的契机?

一个深夜,线上报错:Cannot read property 'name' of undefined

排查了 3 小时,发现是后端改了接口字段名,前端没同步。

如果当时用了正确的类型定义,TypeScript 编译时就会告诉我这个问题。

从那以后,我开始认真学习 TypeScript 的类型系统。

今天,我要把这些"类型体操"的技巧分享给你。


第一章:热身运动 ------ 内置工具类型

TypeScript 内置了很多实用的工具类型,先来热热身。

1.1 Partial ------ 让所有属性变成可选

场景: 更新用户信息时,不需要传所有字段。

typescript 复制代码
interface User {
  id: number
  name: string
  email: string
  age: number
  avatar: string
}

// ❌ 不用 Partial:要传所有字段
function updateUser(id: number, user: User) {
  // ...
}
updateUser(1, { id: 1, name: "张三", email: "...", age: 25, avatar: "..." })

// ✅ 用 Partial:只传需要更新的字段
function updateUserBetter(id: number, updates: Partial<User>) {
  // ...
}
updateUserBetter(1, { name: "李四" }) // 只更新名字,完美!

// Partial 的实现原理(面试常考)
type MyPartial<T> = {
  [P in keyof T]?: T[P]
}

1.2 Required ------ Partial 的反义词

场景: 确保配置对象的所有字段都被填写。

typescript 复制代码
interface Config {
  apiUrl?: string
  timeout?: number
  retries?: number
}

// 用户可以只传部分配置
const userConfig: Config = { apiUrl: "https://api.example.com" }

// 但内部处理时,需要确保所有字段都有值
function initApp(config: Required<Config>) {
  console.log(config.timeout) // 类型是 number,不是 number | undefined
}

// 合并默认配置后调用
const defaultConfig: Required<Config> = {
  apiUrl: "https://default.api.com",
  timeout: 5000,
  retries: 3,
}

initApp({ ...defaultConfig, ...userConfig })

// Required 的实现原理
type MyRequired<T> = {
  [P in keyof T]-?: T[P] // -? 移除可选标记
}

1.3 Pick<T, K> ------ 从类型中挑选部分属性

场景: 列表页只需要显示用户的部分信息。

typescript 复制代码
interface User {
  id: number
  name: string
  email: string
  password: string // 敏感信息
  createdAt: Date
  updatedAt: Date
}

// 列表页只需要这些字段
type UserListItem = Pick<User, "id" | "name" | "email">

// 等价于:
// type UserListItem = {
//   id: number;
//   name: string;
//   email: string;
// }

const users: UserListItem[] = [
  { id: 1, name: "张三", email: "zhang@example.com" },
  // password 不会出现,类型安全!
]

// Pick 的实现原理
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

1.4 Omit<T, K> ------ Pick 的反义词

场景: 创建用户时,不需要传 id(由数据库生成)。

typescript 复制代码
interface User {
  id: number
  name: string
  email: string
  createdAt: Date
}

// 创建用户时排除 id 和 createdAt
type CreateUserInput = Omit<User, "id" | "createdAt">

// 等价于:
// type CreateUserInput = {
//   name: string;
//   email: string;
// }

function createUser(input: CreateUserInput): User {
  return {
    ...input,
    id: generateId(),
    createdAt: new Date(),
  }
}

// Omit 的实现原理
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

1.5 Record<K, V> ------ 快速创建对象类型

场景: 创建一个以字符串为键、特定类型为值的对象。

typescript 复制代码
// 状态映射
type Status = "pending" | "success" | "error"

interface StatusInfo {
  label: string
  color: string
  icon: string
}

const statusMap: Record<Status, StatusInfo> = {
  pending: { label: "处理中", color: "orange", icon: "⏳" },
  success: { label: "成功", color: "green", icon: "✅" },
  error: { label: "失败", color: "red", icon: "❌" },
}

// 如果漏写了某个状态,TypeScript 会报错!
// const badStatusMap: Record<Status, StatusInfo> = {
//   pending: { ... },
//   success: { ... },
//   // 缺少 error,报错!
// };

// Record 的实现原理
type MyRecord<K extends keyof any, V> = {
  [P in K]: V
}

第二章:进阶体操 ------ 条件类型与推断

2.1 条件类型基础

条件类型就像类型系统里的 if-else

typescript 复制代码
// 基本语法:T extends U ? X : Y
// 如果 T 可以赋值给 U,则结果是 X,否则是 Y

type IsString<T> = T extends string ? true : false

type A = IsString<string> // true
type B = IsString<number> // false
type C = IsString<"hello"> // true(字面量类型也是 string)

// 实际应用:根据输入类型返回不同的输出类型
type ApiResponse<T> = T extends "user"
  ? { id: number; name: string }
  : T extends "product"
  ? { id: number; price: number }
  : never

type UserResponse = ApiResponse<"user"> // { id: number; name: string }
type ProductResponse = ApiResponse<"product"> // { id: number; price: number }

2.2 infer 关键字 ------ 类型推断の魔法

infer 可以在条件类型中"捕获"类型:

typescript 复制代码
// 获取函数的返回类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function getUser() {
  return { id: 1, name: "张三" }
}

type UserType = MyReturnType<typeof getUser>
// { id: number; name: string }

// 获取函数的参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never

function login(username: string, password: string, remember?: boolean) {
  // ...
}

type LoginParams = MyParameters<typeof login>
// [username: string, password: string, remember?: boolean]

// 获取 Promise 的解析类型
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T

type A = MyAwaited<Promise<string>> // string
type B = MyAwaited<Promise<Promise<number>>> // number(递归解析)

// 获取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never

type C = ElementType<string[]> // string
type D = ElementType<number[]> // number

2.3 分布式条件类型

当条件类型作用于联合类型时,会自动"分发":

typescript 复制代码
type ToArray<T> = T extends any ? T[] : never

// 联合类型会被分发处理
type Result = ToArray<string | number>
// 等价于 ToArray<string> | ToArray<number>
// 结果是 string[] | number[]

// 如果不想分发,用方括号包裹
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never

type Result2 = ToArrayNoDistribute<string | number>
// 结果是 (string | number)[]

// 实际应用:过滤联合类型
type Exclude<T, U> = T extends U ? never : T

type Numbers = 1 | 2 | 3 | 4 | 5
type BigNumbers = Exclude<Numbers, 1 | 2> // 3 | 4 | 5

type Extract<T, U> = T extends U ? T : never

type OnlyStrings = Extract<string | number | boolean, string> // string

第三章:高级体操 ------ 模板字面量类型

TypeScript 4.1 引入的模板字面量类型,让类型操作更加灵活。

3.1 基础用法

typescript 复制代码
// 字符串拼接
type Greeting = `Hello, ${string}!`

const a: Greeting = "Hello, World!" // ✅
const b: Greeting = "Hello, TypeScript!" // ✅
// const c: Greeting = 'Hi, World!';  // ❌ 不以 Hello 开头

// 联合类型展开
type Color = "red" | "green" | "blue"
type Size = "small" | "medium" | "large"

type ColorSize = `${Color}-${Size}`
// 'red-small' | 'red-medium' | 'red-large' |
// 'green-small' | 'green-medium' | 'green-large' |
// 'blue-small' | 'blue-medium' | 'blue-large'

// CSS 类名生成
type ButtonVariant = `btn-${Color}` | `btn-${Size}`
// 'btn-red' | 'btn-green' | 'btn-blue' | 'btn-small' | 'btn-medium' | 'btn-large'

3.2 字符串操作类型

typescript 复制代码
// 内置的字符串操作类型
type Upper = Uppercase<"hello"> // 'HELLO'
type Lower = Lowercase<"HELLO"> // 'hello'
type Cap = Capitalize<"hello"> // 'Hello'
type Uncap = Uncapitalize<"Hello"> // 'hello'

// 实际应用:事件处理器命名
type EventName = "click" | "focus" | "blur"
type EventHandler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'

// 生成 getter/setter 方法名
type PropName = "name" | "age" | "email"
type Getter = `get${Capitalize<PropName>}` // 'getName' | 'getAge' | 'getEmail'
type Setter = `set${Capitalize<PropName>}` // 'setName' | 'setAge' | 'setEmail'

3.3 模板字面量 + infer = 字符串解析

typescript 复制代码
// 解析路由参数
type ExtractRouteParams<T extends string> =
  T extends `${infer _Start}:${infer Param}/${infer Rest}`
    ? Param | ExtractRouteParams<`/${Rest}`>
    : T extends `${infer _Start}:${infer Param}`
    ? Param
    : never

type Params = ExtractRouteParams<"/users/:userId/posts/:postId">
// 'userId' | 'postId'

// 解析查询字符串
type ParseQueryString<T extends string> =
  T extends `${infer Key}=${infer Value}&${infer Rest}`
    ? { [K in Key]: Value } & ParseQueryString<Rest>
    : T extends `${infer Key}=${infer Value}`
    ? { [K in Key]: Value }
    : {}

type Query = ParseQueryString<"name=张三&age=25&city=北京">
// { name: '张三' } & { age: '25' } & { city: '北京' }

// 驼峰转短横线
type CamelToKebab<S extends string> = S extends `${infer First}${infer Rest}`
  ? First extends Uppercase<First>
    ? `-${Lowercase<First>}${CamelToKebab<Rest>}`
    : `${First}${CamelToKebab<Rest>}`
  : S

type Kebab = CamelToKebab<"backgroundColor"> // 'background-color'
type Kebab2 = CamelToKebab<"fontSize"> // 'font-size'

第四章:实战体操 ------ 真实场景应用

4.1 类型安全的事件系统

typescript 复制代码
// 定义事件映射
interface EventMap {
  "user:login": { userId: string; timestamp: number }
  "user:logout": { userId: string }
  "cart:add": { productId: string; quantity: number }
  "cart:remove": { productId: string }
}

// 类型安全的事件发射器
class TypedEventEmitter<T extends Record<string, any>> {
  private listeners: Map<keyof T, Set<Function>> = new Map()

  on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set())
    }
    this.listeners.get(event)!.add(callback)
  }

  off<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
    this.listeners.get(event)?.delete(callback)
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    this.listeners.get(event)?.forEach((cb) => cb(data))
  }
}

// 使用
const emitter = new TypedEventEmitter<EventMap>()

// ✅ 类型安全:参数类型自动推导
emitter.on("user:login", (data) => {
  console.log(data.userId) // string
  console.log(data.timestamp) // number
})

// ✅ 类型安全:emit 时参数类型检查
emitter.emit("user:login", { userId: "123", timestamp: Date.now() })

// ❌ 类型错误:缺少 timestamp
// emitter.emit('user:login', { userId: '123' });

// ❌ 类型错误:事件名不存在
// emitter.emit('user:unknown', {});

4.2 类型安全的 API 客户端

typescript 复制代码
// API 路由定义
interface ApiRoutes {
  "GET /users": {
    response: { id: number; name: string }[]
  }
  "GET /users/:id": {
    params: { id: string }
    response: { id: number; name: string; email: string }
  }
  "POST /users": {
    body: { name: string; email: string }
    response: { id: number; name: string; email: string }
  }
  "PUT /users/:id": {
    params: { id: string }
    body: { name?: string; email?: string }
    response: { id: number; name: string; email: string }
  }
  "DELETE /users/:id": {
    params: { id: string }
    response: { success: boolean }
  }
}

// 提取路由信息的工具类型
type ExtractMethod<T extends string> = T extends `${infer M} ${string}`
  ? M
  : never
type ExtractPath<T extends string> = T extends `${string} ${infer P}`
  ? P
  : never

// API 客户端类型
type ApiClient = {
  [K in keyof ApiRoutes as Lowercase<ExtractMethod<K & string>>]: (
    path: ExtractPath<K & string>,
    options?: {
      params?: ApiRoutes[K] extends { params: infer P } ? P : never
      body?: ApiRoutes[K] extends { body: infer B } ? B : never
    }
  ) => Promise<ApiRoutes[K]["response"]>
}

// 简化版实现
function createApiClient(baseUrl: string) {
  const request = async (method: string, path: string, options?: any) => {
    let url = `${baseUrl}${path}`

    // 替换路径参数
    if (options?.params) {
      Object.entries(options.params).forEach(([key, value]) => {
        url = url.replace(`:${key}`, String(value))
      })
    }

    const response = await fetch(url, {
      method,
      headers: { "Content-Type": "application/json" },
      body: options?.body ? JSON.stringify(options.body) : undefined,
    })

    return response.json()
  }

  return {
    get: (path: string, options?: any) => request("GET", path, options),
    post: (path: string, options?: any) => request("POST", path, options),
    put: (path: string, options?: any) => request("PUT", path, options),
    delete: (path: string, options?: any) => request("DELETE", path, options),
  }
}

// 使用示例
const api = createApiClient("https://api.example.com")

// 类型推导正常工作
async function demo() {
  const users = await api.get("/users")
  // users 类型:{ id: number; name: string }[]

  const user = await api.get("/users/:id", { params: { id: "123" } })
  // user 类型:{ id: number; name: string; email: string }

  const newUser = await api.post("/users", {
    body: { name: "张三", email: "zhang@example.com" },
  })
  // newUser 类型:{ id: number; name: string; email: string }
}

4.3 深度只读类型

typescript 复制代码
// 递归地将所有属性变为只读
type DeepReadonly<T> = T extends Function
  ? T
  : T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T

interface Config {
  api: {
    baseUrl: string
    timeout: number
    headers: {
      authorization: string
    }
  }
  features: {
    darkMode: boolean
    notifications: boolean
  }
}

type ReadonlyConfig = DeepReadonly<Config>

const config: ReadonlyConfig = {
  api: {
    baseUrl: "https://api.example.com",
    timeout: 5000,
    headers: {
      authorization: "Bearer xxx",
    },
  },
  features: {
    darkMode: true,
    notifications: false,
  },
}

// ❌ 所有层级都不能修改
// config.api.baseUrl = 'xxx';           // 错误
// config.api.headers.authorization = 'yyy'; // 错误
// config.features.darkMode = false;     // 错误

4.4 路径类型(获取对象的所有路径)

typescript 复制代码
// 获取对象所有可能的路径
type Paths<T, Prefix extends string = ""> = T extends object
  ? {
      [K in keyof T]: K extends string
        ? Prefix extends ""
          ? K | Paths<T[K], K>
          : `${Prefix}.${K}` | Paths<T[K], `${Prefix}.${K}`>
        : never
    }[keyof T]
  : never

interface User {
  id: number
  profile: {
    name: string
    address: {
      city: string
      street: string
    }
  }
  settings: {
    theme: string
  }
}

type UserPaths = Paths<User>
// 'id' | 'profile' | 'profile.name' | 'profile.address' |
// 'profile.address.city' | 'profile.address.street' |
// 'settings' | 'settings.theme'

// 根据路径获取值类型
type PathValue<T, P extends string> = P extends `${infer Key}.${infer Rest}`
  ? Key extends keyof T
    ? PathValue<T[Key], Rest>
    : never
  : P extends keyof T
  ? T[P]
  : never

type CityType = PathValue<User, "profile.address.city"> // string
type ProfileType = PathValue<User, "profile"> // { name: string; address: { city: string; street: string } }

// 类型安全的 get 函数
function get<T, P extends Paths<T>>(obj: T, path: P): PathValue<T, P> {
  return path.split(".").reduce((acc: any, key) => acc?.[key], obj)
}

const user: User = {
  id: 1,
  profile: {
    name: "张三",
    address: { city: "北京", street: "长安街" },
  },
  settings: { theme: "dark" },
}

const city = get(user, "profile.address.city") // 类型是 string
const name = get(user, "profile.name") // 类型是 string
// const invalid = get(user, 'profile.invalid'); // ❌ 类型错误

第五章:类型体操の实用工具箱

5.1 NonNullable ------ 排除 null 和 undefined

typescript 复制代码
type MaybeString = string | null | undefined
type DefinitelyString = NonNullable<MaybeString> // string

// 实现原理
type MyNonNullable<T> = T extends null | undefined ? never : T

// 实际应用:处理可选链的结果
interface User {
  profile?: {
    avatar?: string
  }
}

function getAvatar(user: User): string {
  const avatar = user.profile?.avatar
  // avatar 类型是 string | undefined

  if (avatar) {
    // 这里 avatar 类型是 string
    return avatar
  }
  return "/default-avatar.png"
}

5.2 类型守卫工具

typescript 复制代码
// 创建类型守卫函数
function isString(value: unknown): value is string {
  return typeof value === "string"
}

function isNumber(value: unknown): value is number {
  return typeof value === "number"
}

function isNotNull<T>(value: T | null | undefined): value is T {
  return value !== null && value !== undefined
}

// 数组过滤时保持类型
const mixedArray: (string | null | undefined)[] = [
  "a",
  null,
  "b",
  undefined,
  "c",
]

// ❌ filter 后类型还是 (string | null | undefined)[]
const filtered1 = mixedArray.filter(
  (item) => item !== null && item !== undefined
)

// ✅ 使用类型守卫,类型变成 string[]
const filtered2 = mixedArray.filter(isNotNull)
// filtered2 类型是 string[]

// 通用的类型守卫生成器
function createTypeGuard<T>(check: (value: unknown) => boolean) {
  return (value: unknown): value is T => check(value)
}

interface User {
  id: number
  name: string
}

const isUser = createTypeGuard<User>(
  (value): value is User =>
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value
)

function processData(data: unknown) {
  if (isUser(data)) {
    // data 类型是 User
    console.log(data.name)
  }
}

5.3 函数重载の类型体操

typescript 复制代码
// 根据参数类型返回不同的结果类型
function process(input: string): string[]
function process(input: number): number
function process(input: string | number): string[] | number {
  if (typeof input === "string") {
    return input.split("")
  }
  return input * 2
}

const a = process("hello") // string[]
const b = process(42) // number

// 使用条件类型实现类似效果
type ProcessResult<T> = T extends string
  ? string[]
  : T extends number
  ? number
  : never

function processGeneric<T extends string | number>(input: T): ProcessResult<T> {
  if (typeof input === "string") {
    return input.split("") as ProcessResult<T>
  }
  return (input * 2) as ProcessResult<T>
}

const c = processGeneric("hello") // string[]
const d = processGeneric(42) // number

5.4 Builder 模式の类型安全

typescript 复制代码
// 类型安全的 Builder 模式
interface UserBuilder<T extends Partial<User> = {}> {
  setName<N extends string>(name: N): UserBuilder<T & { name: N }>
  setAge<A extends number>(age: A): UserBuilder<T & { age: A }>
  setEmail<E extends string>(email: E): UserBuilder<T & { email: E }>
  build(): T extends User ? User : never
}

interface User {
  name: string
  age: number
  email: string
}

function createUserBuilder(): UserBuilder {
  const data: Partial<User> = {}

  return {
    setName(name) {
      data.name = name
      return this as any
    },
    setAge(age) {
      data.age = age
      return this as any
    },
    setEmail(email) {
      data.email = email
      return this as any
    },
    build() {
      if (!data.name || !data.age || !data.email) {
        throw new Error("Missing required fields")
      }
      return data as User
    },
  }
}

// 使用
const user = createUserBuilder()
  .setName("张三")
  .setAge(25)
  .setEmail("zhang@example.com")
  .build()

// 如果漏掉某个字段,build() 返回 never,使用时会报错

5.5 状态机の类型安全

typescript 复制代码
// 定义状态和转换
type OrderState = "pending" | "paid" | "shipped" | "delivered" | "cancelled"

// 定义合法的状态转换
type StateTransitions = {
  pending: "paid" | "cancelled"
  paid: "shipped" | "cancelled"
  shipped: "delivered"
  delivered: never
  cancelled: never
}

// 类型安全的状态机
class OrderStateMachine<S extends OrderState> {
  constructor(private state: S) {}

  getState(): S {
    return this.state
  }

  // 只允许合法的状态转换
  transition<N extends StateTransitions[S]>(newState: N): OrderStateMachine<N> {
    console.log(`状态从 ${this.state} 转换到 ${newState}`)
    return new OrderStateMachine(newState)
  }
}

// 使用
let order = new OrderStateMachine("pending")

// ✅ 合法转换
order = order.transition("paid")
order = order.transition("shipped")
order = order.transition("delivered")

// ❌ 非法转换(类型错误)
// order.transition('pending'); // delivered 不能转换到 pending

// 从头开始
const newOrder = new OrderStateMachine("pending")
// newOrder.transition('shipped'); // ❌ pending 不能直接到 shipped
newOrder.transition("paid") // ✅ pending -> paid

第六章:避坑指南

6.1 any vs unknown

typescript 复制代码
// ❌ any:放弃类型检查
function badProcess(data: any) {
  data.foo.bar.baz() // 不报错,但运行时可能崩溃
}

// ✅ unknown:安全的"任意类型"
function goodProcess(data: unknown) {
  // data.foo; // ❌ 报错:unknown 类型不能直接访问属性

  if (typeof data === "object" && data !== null && "foo" in data) {
    // 现在可以安全访问
    console.log((data as { foo: unknown }).foo)
  }
}

6.2 类型断言の正确姿势

typescript 复制代码
// ❌ 危险:强制断言可能导致运行时错误
const data = JSON.parse('{"name": "张三"}') as { name: string; age: number }
console.log(data.age.toFixed(2)) // 运行时错误!age 是 undefined

// ✅ 安全:使用类型守卫验证
interface User {
  name: string
  age: number
}

function isUser(data: unknown): data is User {
  return (
    typeof data === "object" &&
    data !== null &&
    "name" in data &&
    "age" in data &&
    typeof (data as User).name === "string" &&
    typeof (data as User).age === "number"
  )
}

const parsed = JSON.parse('{"name": "张三"}')
if (isUser(parsed)) {
  console.log(parsed.age.toFixed(2)) // 类型安全
} else {
  console.log("数据格式不正确")
}

6.3 泛型约束の常见错误

typescript 复制代码
// ❌ 错误:T 可能没有 length 属性
function getLength<T>(item: T): number {
  // return item.length; // 报错
  return 0
}

// ✅ 正确:添加约束
function getLengthCorrect<T extends { length: number }>(item: T): number {
  return item.length
}

getLengthCorrect("hello") // ✅ string 有 length
getLengthCorrect([1, 2, 3]) // ✅ array 有 length
// getLengthCorrect(123);      // ❌ number 没有 length

6.4 索引签名の陷阱

typescript 复制代码
interface StringMap {
  [key: string]: string
}

const map: StringMap = {
  name: "张三",
  age: "25", // 必须是 string,不能是 number
}

// 陷阱:访问不存在的键不会报错
const value = map.nonExistent // 类型是 string,但实际是 undefined!

// 解决方案:使用 Record 或更精确的类型
type SafeMap = Record<"name" | "age", string>

const safeMap: SafeMap = {
  name: "张三",
  age: "25",
}

// safeMap.nonExistent; // ❌ 报错:属性不存在

写在最后:类型体操の哲学

学习 TypeScript 类型体操,不是为了炫技,而是为了:

1. 编译时发现错误

  • 类型错误在编译时就能发现,不用等到线上报错
  • 重构时 IDE 会告诉你哪些地方需要修改

2. 更好的开发体验

  • 自动补全更智能
  • 文档就在类型里,不用翻文档

3. 代码即文档

  • 类型定义本身就是最好的文档
  • 新人看类型就知道怎么用

最后,送你一句话:

"类型不是枷锁,而是翅膀。"

当你的类型系统足够强大时,很多 bug 在你写代码的时候就已经被消灭了。


💬 互动时间:你在项目中用过哪些类型体操技巧?遇到过什么类型难题?评论区分享一下,一起探讨!

觉得这篇文章有用?点赞 + 在看 + 转发,让更多 TypeScript 开发者告别 any!


本文作者是一个从 "any 大法好" 进化到 "类型体操运动员" 的前端开发。关注我,一起在类型的世界里优雅地编程。

相关推荐
小高0071 天前
Elips:领域模型与 DSL 设计实践:从配置到站点的优雅映射
前端·javascript·后端
远瞻。1 天前
【博客】前端新手如何创建自己的个人网站相册
前端·docker·博客·反向代理
青莲8431 天前
Java并发编程基础与进阶(线程·锁·原子类·通信)
android·前端·面试
祎直向前1 天前
linuxshell测试题
前端·chrome
嫂子的姐夫1 天前
012-AES加解密:某勾网(参数data和响应密文)
javascript·爬虫·python·逆向·加密算法
irises1 天前
开源项目next-ai-draw-io核心能力拆解
前端·后端·llm
irises1 天前
通过`ai.js`与`@ai-sdk`实现前后端tool注入与交互
前端·后端·llm
德育处主任1 天前
『NAS』部署轻量级开源图片水印工具-ImageWatermarkTool
前端·javascript·docker
pas1361 天前
28-mini-vue customRender
前端·javascript·vue.js