最后更新:2026-02-06
说实话,我刚开始接触 TypeScript 的时候是抵触的。觉得 JavaScript 写着挺顺,加了类型约束之后各种报错,到处要加类型标注,很烦。
后来接手了一个老项目,代码里有个函数被七八个地方调用,没有任何注释,参数传的什么鬼全靠猜。改了一个地方,不知道其他地方有没有受影响,提心吊胆。那次之后开始认真对待 TypeScript 了。
最基础的类型注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| let name: string = 'Eric' let age: number = 28 let isActive: boolean = true
let tags: string[] = ['react', 'vue'] let scores: Array<number> = [98, 85, 72]
function greet(name: string): string { return `Hello, ${name}` }
function log(msg: string): void { console.log(msg) }
|
这些是最基础的,用起来跟写注释差不多,但机器能读懂。
对象类型和接口
定义对象结构用 interface 或 type:
1 2 3 4 5 6 7 8 9 10
| interface User { id: number name: string email: string avatar?: string }
function getUserName(user: User): string { return user.name }
|
两者很像,有争议。我的习惯是:描述对象结构用 interface,其他情况用 type。
1 2 3 4 5 6
| type Status = 'pending' | 'active' | 'disabled' type ID = string | number
type Username = string
|
联合类型和类型收窄
联合类型是 TypeScript 里非常常用的特性:
1 2 3 4 5 6 7
| type ButtonVariant = 'primary' | 'secondary' | 'danger'
interface ButtonProps { variant: ButtonVariant label: string disabled?: boolean }
|
类型收窄(Type Narrowing)是 TypeScript 自动根据判断条件推断类型:
1 2 3 4 5 6 7 8
| function format(value: string | number): string { if (typeof value === 'string') { return value.toUpperCase() } return value.toFixed(2) }
|
泛型:让函数更通用
泛型是 TypeScript 里稍微难一点的概念,但用好了非常有用。
1 2 3 4 5 6 7 8 9 10 11 12
| function first(arr: number[]): number { return arr[0] }
function first<T>(arr: T[]): T { return arr[0] }
first([1, 2, 3]) first(['a', 'b', 'c'])
|
接口里的泛型,在 React 里很常用:
1 2 3 4 5 6 7 8 9 10 11
| interface ApiResponse<T> { code: number message: string data: T }
type UserListResponse = ApiResponse<User[]>
type UserDetailResponse = ApiResponse<User>
|
在 React 里怎么用
函数组件的 Props 类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| interface ButtonProps { label: string variant?: 'primary' | 'secondary' onClick: () => void children?: React.ReactNode }
const Button: React.FC<ButtonProps> = ({ label, variant = 'primary', onClick }) => { return ( <button className={`btn btn-${variant}`} onClick={onClick}> {label} </button> ) }
|
useState 的类型:
1 2 3 4 5 6 7 8
| const [count, setCount] = useState(0)
const [user, setUser] = useState<User | null>(null)
const [list, setList] = useState<User[]>([])
|
事件处理函数类型:
1 2 3 4 5 6 7 8 9
| const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setValue(e.target.value) }
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault() }
|
几个常用的工具类型
TypeScript 内置了一堆工具类型,用好了省很多事:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| interface User { id: number name: string email: string password: string }
type UpdateUserDto = Partial<User>
type UserProfile = Pick<User, 'id' | 'name' | 'email'>
type PublicUser = Omit<User, 'password'>
type StrictUser = Required<User>
type RoleMap = Record<string, string[]>
|
这几个我项目里几乎每天都在用,Partial 和 Omit 尤其多。
类型断言和非空断言
有时候 TS 类型推断比你保守,你比它更清楚某个值的类型:
1 2 3 4 5 6 7
| const canvas = document.getElementById('canvas') as HTMLCanvasElement const ctx = canvas.getContext('2d')
const input = document.querySelector<HTMLInputElement>('#search')! input.focus()
|
非空断言用的时候要小心,如果你判断错了,运行时还是会报错,只是 TS 不警告你了。
tsconfig 几个重要配置
1 2 3 4 5 6 7 8 9 10 11 12
| { "compilerOptions": { "strict": true, "target": "ES2020", "module": "ESNext", "jsx": "react-jsx", "baseUrl": "src", "paths": { "@/*": ["./*"] } } }
|
strict: true 一定要开。虽然一开始会有很多报错要改,但能帮你发现很多潜在问题。
说实话 TypeScript 的学习曲线比我想象的平缓,基础部分很快就能上手。真正复杂的是条件类型、infer、mapped types 这些高级用法,但日常开发用到的机会不多,遇到了再查也来得及。