前言
TypeScript 已经成为现代前端开发的标配。但是,仅仅使用 TypeScript 并不能保证代码质量。本文将分享一些实用的 TypeScript 最佳实践,帮助你写出更安全、更优雅的代码。
类型定义最佳实践
1. 优先使用 interface 而非 type
虽然 interface
和 type
在很多场景下可以互换,但 interface
有更好的扩展性:
// ✅ 推荐:使用 interface
interface User {
id: string
name: string
email: string
}
// 可以轻松扩展
interface AdminUser extends User {
permissions: string[]
}
// ❌ 避免:使用 type
type User = {
id: string
name: string
email: string
}
// 扩展需要使用交叉类型
type AdminUser = User & {
permissions: string[]
}
2. 使用字面量类型提高类型精确度
// ❌ 过于宽泛
interface Button {
type: string
size: string
}
// ✅ 精确的字面量类型
interface Button {
type: 'primary' | 'secondary' | 'ghost'
size: 'small' | 'medium' | 'large'
}
// 使用 const 断言创建只读对象
const BUTTON_TYPES = {
PRIMARY: 'primary',
SECONDARY: 'secondary',
GHOST: 'ghost'
} as const
type ButtonType = typeof BUTTON_TYPES[keyof typeof BUTTON_TYPES]
3. 善用泛型约束
// 基础泛型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
// 条件类型
type IsArray<T> = T extends any[] ? true : false
// 实用的泛型工具类型
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}
// 高级示例:提取 Promise 的返回类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
// 使用示例
async function fetchUser(): Promise<User> {
// ...
}
type FetchedUser = UnwrapPromise<ReturnType<typeof fetchUser>> // User
代码组织最佳实践
1. 模块化类型定义
// types/user.ts
export interface User {
id: string
name: string
email: string
}
export interface UserProfile extends User {
avatar?: string
bio?: string
createdAt: Date
}
// types/api.ts
export interface ApiResponse<T> {
data: T
status: number
message?: string
}
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
page: number
pageSize: number
total: number
}
// 使用
import type { User } from './types/user'
import type { ApiResponse } from './types/api'
async function getUser(id: string): Promise<ApiResponse<User>> {
// ...
}
2. 使用命名空间组织相关类型
// 将相关类型组织在一起
namespace UserAPI {
export interface User {
id: string
name: string
}
export interface CreateUserRequest {
name: string
email: string
password: string
}
export interface UpdateUserRequest {
name?: string
email?: string
}
export type UserResponse = ApiResponse<User>
}
// 使用
function createUser(data: UserAPI.CreateUserRequest): Promise<UserAPI.UserResponse> {
// ...
}
3. 分离业务逻辑和类型定义
// models/user.model.ts - 纯类型定义
export interface IUser {
id: string
name: string
email: string
}
// services/user.service.ts - 业务逻辑
import type { IUser } from '../models/user.model'
export class UserService {
async getUser(id: string): Promise<IUser> {
// 业务逻辑
}
async createUser(data: Omit<IUser, 'id'>): Promise<IUser> {
// 业务逻辑
}
}
类型安全最佳实践
1. 避免使用 any
// ❌ 避免
function processData(data: any) {
return data.value // 没有类型检查
}
// ✅ 使用 unknown
function processData(data: unknown) {
// 需要类型守卫
if (isValidData(data)) {
return data.value
}
throw new Error('Invalid data')
}
// 类型守卫
function isValidData(data: unknown): data is { value: string } {
return (
typeof data === 'object' &&
data !== null &&
'value' in data &&
typeof (data as any).value === 'string'
)
}
2. 使用严格的配置
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
3. 使用类型守卫和断言函数
// 类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string'
}
// 断言函数
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value must be a string')
}
}
// 使用示例
function processValue(value: unknown) {
if (isString(value)) {
// value 的类型是 string
console.log(value.toUpperCase())
}
// 或者使用断言
assertIsString(value)
// 之后 value 的类型就是 string
console.log(value.toUpperCase())
}
高级类型技巧
1. 映射类型和条件类型
// 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P]
}
// 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
// 排除某些属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
// 条件类型示例
type NonNullable<T> = T extends null | undefined ? never : T
// 递归类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}
2. 模板字面量类型
// 创建特定格式的字符串类型
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type APIEndpoint = '/users' | '/posts' | '/comments'
type APIRoute = `${HTTPMethod} ${APIEndpoint}`
// 类型为: "GET /users" | "GET /posts" | ... | "DELETE /comments"
// 实用示例:CSS 单位
type CSSUnit = 'px' | 'em' | 'rem' | '%'
type CSSValue = `${number}${CSSUnit}`
function setWidth(value: CSSValue) {
// ...
}
setWidth('100px') // ✅
setWidth('2em') // ✅
setWidth('100') // ❌ 类型错误
3. 使用 infer 进行类型推断
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
// 提取 Promise 中的类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
// 提取数组元素类型
type ArrayElement<T> = T extends (infer U)[] ? U : never
// 实用示例:提取函数参数类型
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never
function greet(name: string, age: number) {
return `Hello ${name}, you are ${age} years old`
}
type GreetParams = Parameters<typeof greet> // [string, number]
React + TypeScript 最佳实践
1. 组件类型定义
// 函数组件
interface ButtonProps {
variant: 'primary' | 'secondary'
size?: 'small' | 'medium' | 'large'
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
children: React.ReactNode
}
const Button: React.FC<ButtonProps> = ({
variant,
size = 'medium',
onClick,
children
}) => {
return (
<button
className={`btn btn-${variant} btn-${size}`}
onClick={onClick}
>
{children}
</button>
)
}
// 使用泛型的组件
interface ListProps<T> {
items: T[]
renderItem: (item: T) => React.ReactNode
keyExtractor: (item: T) => string
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
)
}
2. Hook 类型定义
// 自定义 Hook
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
return initialValue
}
})
const setValue = (value: T | ((prev: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.error(error)
}
}
return [storedValue, setValue]
}
// 使用
const [user, setUser] = useLocalStorage<User | null>('user', null)
性能优化技巧
1. 使用 const enum 减少运行时开销
// const enum 在编译时会被内联
const enum Direction {
Up,
Down,
Left,
Right
}
// 编译后直接替换为数字
let direction = Direction.Up // 编译为: let direction = 0
2. 条件导入类型
// 只导入类型,不会影响运行时
import type { User } from './types'
// 条件导入
if (process.env.NODE_ENV === 'development') {
import('./dev-tools').then(({ setupDevTools }) => {
setupDevTools()
})
}
总结
TypeScript 不仅仅是给 JavaScript 加上类型,更是一种思维方式的转变。通过遵循这些最佳实践,你可以:
- 提高代码质量:减少运行时错误
- 改善开发体验:更好的 IDE 支持和自动补全
- 增强代码可维护性:类型即文档
- 提升团队协作:统一的代码规范
记住,TypeScript 的目标是帮助我们写出更好的代码,而不是增加负担。合理使用这些技巧,让 TypeScript 成为你的得力助手!