TypeScript 实战:项目配置与最佳实践
学会了 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 与前端框架的结合。
工具推荐:
Views: 0
