07. TypeScript实战:项目配置与最佳实践

TypeScript 实战:项目配置与最佳实践

TypeScript Config

学会了 TypeScript 的类型系统,现在我们来看看如何在实际项目中配置和使用 TypeScript。这一期,我们学习 tsconfig.json、模块系统、声明文件和工程化实践。

tsconfig.json 详解

基本结构

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

常用配置项

目标和模块

{
  "compilerOptions": {
    // 编译目标 ES 版本
    "target": "ES2020",

    // 模块系统
    "module": "ESNext",

    // 模块解析策略
    "moduleResolution": "node",

    // 生成 source map
    "sourceMap": true
  }
}

严格模式

{
  "compilerOptions": {
    // 启用所有严格选项
    "strict": true,

    // 允许从没有设置默认导出的模块中导入
    "allowSyntheticDefaultImports": true,

    // 启用 ES 模块互操作性
    "esModuleInterop": true,

    // 不允许隐式 any
    "noImplicitAny": true,

    // 严格的 null 检查
    "strictNullChecks": true
  }
}

路径映射

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

使用:

import { Button } from '@components/Button'
import { formatDate } from '@utils/date'

输出配置

{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

推荐配置

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "moduleResolution": "node",
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "moduleDetection": "force",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

模块系统

ES 模块

// utils.ts
export function add(a: number, b: number): number {
  return a + b
}

export const PI = 3.14

export default class Calculator {
  // ...
}
// main.ts
import Calculator, { add, PI } from './utils'

命名导出

// 导出多个
export { add, subtract, multiply }

// 重命名导出
export { add as sum }

// 全部导出
export * from './math'

动态导入

async function loadModule() {
  const { add } = await import('./utils')
  console.log(add(1, 2))
}

声明文件

什么是声明文件

声明文件(.d.ts)描述 JavaScript 代码的类型信息。

// math.d.ts
declare function add(a: number, b: number): number
declare const PI: number

编写声明文件

// jquery.d.ts
declare const $: {
  (selector: string): {
    html(content: string): void
    on(event: string, handler: (e: Event) => void): void
  }
  ajax(options: {
    url: string
    method?: string
    data?: any
    success?: (response: any) => void
  }): void
}

模块声明

// my-module.d.ts
declare module 'my-module' {
  export function doSomething(value: string): number
  export interface Options {
    debug: boolean
  }
  export default class MyClass {
    constructor(options: Options)
    run(): void
  }
}

全局声明

// global.d.ts
declare global {
  interface Window {
    myCustomProperty: string
  }

  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production'
      API_URL: string
    }
  }
}

export {}

DefinitelyTyped

大部分库都有社区维护的声明文件:

npm install --save-dev @types/lodash
npm install --save-dev @types/node
npm install --save-dev @types/react

项目结构

目录组织

src/
├── components/     # 组件
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.styles.ts
│   │   ├── Button.types.ts
│   │   └── index.ts
│   └── index.ts
├── hooks/          # 自定义 Hooks
├── utils/          # 工具函数
├── types/          # 全局类型
│   └── index.d.ts
├── api/            # API 接口
├── constants/      # 常量
└── App.tsx

类型文件组织

// types/user.ts
export interface User {
  id: number
  name: string
  email: string
}

export type UserRole = 'admin' | 'user' | 'guest'

export interface UserWithRole extends User {
  role: UserRole
}
// types/api.ts
export interface ApiResponse {
  code: number
  message: string
  data: T
}

export interface PaginatedResponse {
  items: T[]
  total: number
  page: number
  pageSize: number
}
// types/index.ts
export * from './user'
export * from './api'

工具链

ESLint

npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
// .eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended'
  ],
  rules: {
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/explicit-function-return-type': 'off'
  }
}

Prettier

npm install --save-dev prettier eslint-config-prettier
// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}

Husky + lint-staged

npm install --save-dev husky lint-staged
npx husky install
// package.json
{
  "scripts": {
    "lint": "eslint src --ext .ts,.tsx",
    "type-check": "tsc --noEmit"
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}
npx husky add .husky/pre-commit "npx lint-staged"

最佳实践

1. 严格模式

始终启用 strict: true

{
  "compilerOptions": {
    "strict": true
  }
}

2. 避免 any

// Bad
function parse(data: any) {
  return JSON.parse(data)
}

// Good
function parse(data: string): unknown {
  return JSON.parse(data)
}

3. 使用 unknown 代替 any

// Bad
let value: any = getValue()
value.foo()  // 不安全

// Good
let value: unknown = getValue()
if (typeof value === 'object' && value !== null && 'foo' in value) {
  (value as { foo: () => void }).foo()
}

4. 类型优先

// Bad
const user = {
  name: 'Alice',
  age: 30
}

// Good
interface User {
  name: string
  age: number
}

const user: User = {
  name: 'Alice',
  age: 30
}

5. 使用类型推断

// Bad
let x: number = 10
let arr: number[] = [1, 2, 3]

// Good(让 TS 推断)
let x = 10
let arr = [1, 2, 3]

6. 使用 const 断言

// 不使用 const 断言
let x = [1, 2]  // number[]
x = [3, 4]      // OK

// 使用 const 断言
let y = [1, 2] as const  // readonly [1, 2]
y = [3, 4]    // Error

7. 使用满足约束

type Colors = 'red' | 'green' | 'blue'

// Bad
const favoriteColor: Colors = 'red'

// Good(更好的类型推断)
const favoriteColor = 'red' as const satisfies Colors

8. 使用模板字面量类型

type EventName = 'click' | 'scroll' | 'mousemove'
type EventHandler = <code>on${Capitalize}
// "onClick" | "onScroll" | "onMousemove"

function addHandler(event: EventHandler, handler: () => void) {
  // ...
}

addHandler('onClick', () => {})  // OK
addHandler('click', () => {})    // Error

调试技巧

使用 tsc --noEmit

只做类型检查,不生成文件:

npx tsc --noEmit

使用 tsc --watch

监视模式:

npx tsc --watch

类型可视化

// 使用工具类型查看推断结果
type ShowType = { [K in keyof T]: T[K] }

type Result = ShowType

常见问题

模块导入问题

// 如果遇到 "Cannot find module" 错误
// 检查 tsconfig.json

{
  "compilerOptions": {
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  }
}

类型扩展

// 扩展第三方类型
declare module 'third-party-lib' {
  interface Options {
    customOption: string
  }
}

类型断言滥用

// Bad
const value = something as any as MyType

// Good
function isMyType(value: unknown): value is MyType {
  return typeof value === 'object' && 
         value !== null && 
         'requiredField' in value
}

if (isMyType(something)) {
  // something 是 MyType
}

总结

TypeScript 项目配置要点:

  • tsconfig.json:正确配置编译选项
  • 模块系统:使用 ES 模块
  • 声明文件:为 JS 库添加类型
  • 工具链:ESLint + Prettier + Husky
  • 最佳实践:严格模式、避免 any、类型优先

下一期,我们学习 TypeScript 与前端框架的结合。


工具推荐

  • ts-node - 直接运行 TypeScript
  • ts-loader - Webpack TypeScript loader
  • tsdx - TypeScript 库开发工具

Views: 0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Index