Skip to content

TypeScript 开发笔记

TypeScript 类型系统和最佳实践。

类型基础

基本类型

ts
// 基础类型
let str: string = 'hello'
let num: number = 123
let bool: boolean = true
let arr: number[] = [1, 2, 3]
let tuple: [string, number] = ['a', 1]
let obj: { name: string; age: number } = { name: 'saqqdy', age: 18 }

// 联合类型
let id: string | number

// 类型别名
type UserID = string | number
type User = {
  id: UserID
  name: string
  email?: string // 可选属性
}

泛型

ts
// 泛型函数
function identity<T>(arg: T): T {
  return arg
}

identity<string>('hello')
identity(123) // 类型推断

// 泛型约束
interface Lengthwise {
  length: number
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length)
  return arg
}

// 多类型参数
function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value]
}

高级类型

工具类型

基于 js-cool 的类型设计:

ts
// Partial - 可选
type PartialUser = Partial<User>

// Required - 必选
type RequiredUser = Required<User>

// Pick - 选取
type UserName = Pick<User, 'name'>

// Omit - 排除
type UserWithoutEmail = Omit<User, 'email'>

// Record - 记录
type UserMap = Record<string, User>

// DeepPartial - 深层可选
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

条件类型

ts
// 条件类型
type IsString<T> = T extends string ? true : false

type A = IsString<string> // true
type B = IsString<number> // false

// infer 推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function getName(): string { return 'saqqdy' }
type Name = ReturnType<typeof getName> // string

模板字面量类型

ts
type EventName = 'click' | 'focus' | 'blur'
type Handler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'

type PropName = `data-${string}`
// `data-${string}`

类型推断

类型守卫

ts
function isString(val: unknown): val is string {
  return typeof val === 'string'
}

function process(val: string | number) {
  if (isString(val)) {
    // val 是 string
    console.log(val.toUpperCase())
  } else {
    // val 是 number
    console.log(val.toFixed(2))
  }
}

// instanceof
class Dog { bark() {} }
class Cat { meow() {} }

function speak(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark()
  } else {
    animal.meow()
  }
}

类型断言

ts
// 尖括号
let value: any = 'hello'
let length: number = (<string>value).length

// as 语法 (推荐)
let length2: number = (value as string).length

// 非空断言
let element = document.getElementById('app')!
element.innerHTML = 'Hello'

库开发

类型声明

ts
// global.d.ts
declare module 'my-lib' {
  export function format(date: Date, pattern: string): string
  export function clone<T>(obj: T): T
  export const version: string
  
  export interface Options {
    timeout?: number
    retry?: number
  }
  
  export default function(options: Options): void
}

多格式输出

json
// package.json
{
  "name": "my-lib",
  "version": "1.0.0",
  "type": "module",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"]
}

类型导出

ts
// index.ts
export type { User, Options } from './types'
export { format, clone } from './utils'
export { default as MyLib } from './core'

实用技巧

递归类型

ts
// 深度只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? DeepReadonly<T[P]> 
    : T[P]
}

// 深度必填
type DeepRequired<T> = {
  [P in keyof T]-?: T[P] extends object 
    ? DeepRequired<T[P]> 
    : T[P]
}

类型组合

ts
// 合并类型
type Merge<A, B> = Omit<A, keyof B> & B

type Base = { id: string; name: string }
type Extra = { name: number; age: number }
type Merged = Merge<Base, Extra>
// { id: string; name: number; age: number }

函数重载

ts
function parse(input: string): Date
function parse(input: Date): string
function parse(input: string | Date) {
  if (typeof input === 'string') {
    return new Date(input)
  }
  return input.toISOString()
}

const date = parse('2024-01-01') // Date
const str = parse(new Date()) // string

相关项目

MIT Licensed