TypeScript 5.4 高级实战指南:从入门到进阶
本文从实际开发角度出发,深入讲解 TypeScript 的高级特性和实战技巧。每个概念都配有详细的代码示例和最佳实践建议。
目录
基础配置优化
tsconfig.json 最佳实践
{
"compilerOptions": {
// 目标 ECMAScript 版本
"target": "ES2022",
// 模块系统
"module": "ESNext",
// 模块解析策略
"moduleResolution": "bundler",
// 严格模式
"strict": true,
// 类型检查
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
// 额外检查
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
// 模块导入
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
// JSX 支持
"jsx": "preserve",
// 装饰器支持
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// 路径别名
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
项目结构最佳实践
src/
├── types/ # 类型声明文件
│ ├── global.d.ts # 全局类型声明
│ └── module.d.ts # 模块类型声明
├── utils/ # 工具函数
├── services/ # API 服务
├── components/ # 组件
└── pages/ # 页面
高级类型系统
1. 高级类型推断
// 函数参数推断
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
// 使用示例
const numbers = [1, 2, 3];
const strings = map(numbers, n => n.toString()); // string[]
// 条件类型推断
type ElementType<T> = T extends (infer U)[] ? U : never;
type StringType = ElementType<string[]>; // string
2. 映射类型高级用法
// 深度 Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// 可选深度 Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
// 类型过滤
type FilterByValueType<T, ValueType> = {
[P in keyof T as T[P] extends ValueType ? P : never]: T[P];
};
// 使用示例
interface User {
id: number;
name: string;
settings: {
theme: string;
notifications: boolean;
};
}
type ReadonlyUser = DeepReadonly<User>;
type PartialUser = DeepPartial<User>;
type StringPropsOnly = FilterByValueType<User, string>;
3. 高级泛型约束
// 泛型约束
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): number {
return arg.length;
}
// 泛型默认值
interface DefaultGeneric<T = string> {
value: T;
}
// 多重泛型约束
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
// 泛型工厂
interface GenericFactory<T> {
create(): T;
update(item: Partial<T>): void;
delete(id: string): void;
}
类型体操实战
1. 递归类型
// 递归数组类型
type NestedArray<T> = Array<T | NestedArray<T>>;
// 递归对象类型
type NestedObject<T> = {
[key: string]: T | NestedObject<T>;
};
// JSON 类型
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// 使用示例
const nestedArr: NestedArray<number> = [1, [2, [3, 4]], 5];
const nestedObj: NestedObject<string> = {
a: "value",
b: {
c: "nested value"
}
};
2. 条件类型进阶
// 联合类型转交叉类型
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends
((k: infer I) => void) ? I : never;
// 提取函数返回类型
type ReturnTypeDeep<T> = T extends (...args: any[]) => infer R
? R extends Promise<infer P>
? P
: R
: never;
// 字符串操作类型
type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
? `${P1}${Uppercase<P2>}${CamelCase<P3>}`
: S;
// 使用示例
type Union = { a: string } | { b: number };
type Intersection = UnionToIntersection<Union>; // { a: string } & { b: number }
async function fetchUser() {
return { id: 1, name: "John" };
}
type UserType = ReturnTypeDeep<typeof fetchUser>; // { id: number, name: string }
type CamelCaseExample = CamelCase<"user_first_name">; // "userFirstName"
3. 实用工具类型
// 移除索引签名
type RemoveIndexSignature<T> = {
[K in keyof T as string extends K
? never
: number extends K
? never
: K]: T[K];
};
// 必选属性
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
// 可选属性
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
// 使用示例
interface Test {
[key: string]: unknown;
id: number;
name?: string;
age: number;
}
type CleanTest = RemoveIndexSignature<Test>;
type Required = RequiredKeys<Test>; // "id" | "age"
type Optional = OptionalKeys<Test>; // "name"
工程化实践
1. 模块声明
// 声明全局变量
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION__: any;
}
}
// 声明模块
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 声明 JSON 模块
declare module "*.json" {
const value: any;
export default value;
}
2. 类型声明文件组织
// types/api.d.ts
declare namespace API {
interface Response<T = any> {
code: number;
data: T;
message: string;
}
interface UserInfo {
id: number;
name: string;
email: string;
}
}
// types/global.d.ts
declare global {
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Recordable<T = any> = Record<string, T>;
}
3. 装饰器使用
// 方法装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with:`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
// 类装饰器
function controller(path: string) {
return function(target: Function) {
Reflect.defineMetadata('path', path, target);
};
}
// 使用示例
@controller('/api')
class UserController {
@log
getUser(id: number) {
return { id, name: 'John' };
}
}
性能优化
1. 类型缓存
// 使用接口合并而不是交叉类型
// 好的做法
interface MergedType extends Type1, Type2 {}
// 避免的做法
type MergedType = Type1 & Type2;
// 使用类型参数约束
function getValue<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
2. 条件类型优化
// 避免过深的条件类型嵌套
type Flatten<T> = T extends Array<infer U> ? U : T;
// 使用 never 过滤类型
type NonNullable<T> = T extends null | undefined ? never : T;
// 使用 unknown 而不是 any
function processValue(value: unknown) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return String(value);
}
实战案例
1. Vue 3 + TypeScript
// 组件 Props 类型定义
interface Props {
title: string;
items?: string[];
onSelect?: (item: string) => void;
}
// 组件实现
import { defineComponent, PropType } from 'vue';
export default defineComponent({
name: 'MyComponent',
props: {
title: {
type: String,
required: true
},
items: {
type: Array as PropType<string[]>,
default: () => []
},
onSelect: {
type: Function as PropType<(item: string) => void>,
default: undefined
}
},
setup(props) {
// 组件逻辑
}
});
2. React + TypeScript
// 组件 Props 类型定义
interface Props {
title: string;
count: number;
children?: React.ReactNode;
onIncrement?: () => void;
}
// 组件实现
const Counter: React.FC<Props> = ({
title,
count,
children,
onIncrement
}) => {
return (
<div>
<h2>{title}</h2>
<p>Count: {count}</p>
{children}
<button onClick={onIncrement}>Increment</button>
</div>
);
};
// 自定义 Hook
function useCounter(initialValue: number = 0) {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return { count, increment };
}
3. Express + TypeScript
import express from 'express';
import { Request, Response, NextFunction } from 'express';
// 自定义请求类型
interface AuthRequest extends Request {
user?: {
id: string;
role: string;
};
}
// 中间件类型
type Middleware = (
req: AuthRequest,
res: Response,
next: NextFunction
) => Promise<void> | void;
// 错误处理
interface AppError extends Error {
statusCode?: number;
status?: string;
isOperational?: boolean;
}
// 错误处理中间件
const errorHandler = (
err: AppError,
req: Request,
res: Response,
next: NextFunction
) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
};
// 路由处理
const app = express();
app.get('/api/users/:id', async (req: AuthRequest, res: Response) => {
try {
const userId = req.params.id;
// 处理逻辑
res.json({ /* user data */ });
} catch (error) {
next(error);
}
});
app.use(errorHandler);
调试技巧
1. 类型检查
// 类型检查工具
type TypeEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
// 检查是否是 never 类型
type IsNever<T> = [T] extends [never] ? true : false;
// 类型断言
function assertIsString(val: unknown): asserts val is string {
if (typeof val !== "string") {
throw new Error("Not a string!");
}
}
2. VS Code 调试配置
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug TypeScript",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
]
}
最佳实践总结
-
类型定义原则
- 优先使用接口定义对象类型
- 使用类型别名定义函数和联合类型
- 合理使用泛型提高代码复用性
-
代码组织
- 按功能模块组织类型定义
- 使用命名空间避免命名冲突
- 合理使用类型导入导出
-
性能优化
- 避免过度使用条件类型
- 合理使用类型缓存
- 控制类型递归深度
常见问题解决方案
- 类型扩展
// 扩展已有类型
interface Window {
__REDUX_DEVTOOLS_EXTENSION__: any;
}
// 扩展第三方模块
declare module 'vue' {
interface ComponentCustomProperties {
$http: typeof axios;
}
}
- 类型保护
// 自定义类型保护
function isError(error: unknown): error is Error {
return error instanceof Error;
}
// 使用类型谓词
function isString(value: unknown): value is string {
return typeof value === 'string';
}
- 类型兼容性
// 结构类型系统
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
let point2D: Point2D = { x: 0, y: 0 };
let point3D: Point3D = { x: 0, y: 0, z: 0 };
// Point2D 兼容 Point3D
point2D = point3D; // OK
结语
TypeScript 是一个强大的工具,掌握它的高级特性可以帮助我们写出更加健壮和可维护的代码。希望这篇指南能帮助你更好地理解和使用 TypeScript。
记住:类型系统是用来服务我们的,而不是限制我们。在实际开发中,要根据具体场景选择合适的类型策略。