7 min read

TypeScript 完全指南:类型系统与实战技巧

Table of Contents

引言

TypeScript 已经成为了现代前端开发的标配,但很多开发者仅仅把它当作「带类型的 JavaScript」来用,实际上 TypeScript 的类型系统非常强大,善用它能让你的代码更健壮、团队协作更顺畅。这篇文章将带你深入理解 TypeScript 的核心概念,并分享真实项目中的实用技巧。


类型基础

原始类型

// 基础类型
const name: string = 'Sean';
const age: number = 25;
const isDeveloper: boolean = true;
const nullValue: null = null;
const undefinedValue: undefined = undefined;

// Symbol 和 BigInt
const symbolKey: symbol = Symbol('key');
const bigNumber: bigint = 100n;

数组和元组

// 数组
const numbers: number[] = [1, 2, 3];
const strings: Array<string> = ['a', 'b', 'c'];

// 元组 - 固定长度和类型的数组
const tuple: [string, number] = ['Sean', 25];
const optionalTuple: [string, number?] = ['Sean']; // 可选元素

对象类型

// 方式一:interface
interface Person {
  name: string;
  age: number;
  email?: string; // 可选属性
  readonly id: number; // 只读属性
}

// 方式二:type
type Person = {
  name: string;
  age: number;
  email?: string;
};

// 使用
const person: Person = {
  name: 'Sean',
  age: 25,
  id: 1, // 不能修改
};

高级类型

联合类型和交叉类型

// 联合类型:可以是多种类型之一
type ID = string | number;
const userId: ID = 'abc123';
const postId: ID = 123;

// 交叉类型:合并多个类型
interface Name {
  name: string;
}
interface Age {
  age: number;
}
type Person = Name & Age;
const person: Person = { name: 'Sean', age: 25 };

泛型

// 基础泛型
function identity<T>(arg: T): T {
  return arg;
}
const result = identity<string>('hello'); // 显式指定类型
const inferred = identity('hello'); // 类型推断

// 泛型约束
interface Lengthwise {
  length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
  console.log(arg.length);
}
logLength('hello'); // ✓
logLength([1, 2, 3]); // ✓
// logLength(123); // ✗ Error

// 泛型接口
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}
interface User {
  id: number;
  name: string;
}
const response: ApiResponse<User> = {
  data: { id: 1, name: 'Sean' },
  status: 200,
  message: 'success',
};

// 泛型类
class Container<T> {
  private value: T;
  constructor(value: T) {
    this.value = value;
  }
  get(): T {
    return this.value;
  }
}

映射类型

// 从现有类型创建新类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface User {
  id: number;
  name: string;
  age: number;
}
type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;

// 实用类型(TypeScript 内置)
type PickUser = Pick<User, 'id' | 'name'>;
type OmitUser = Omit<User, 'age'>;

实用技巧

1. 类型守卫

// typeof - 原始类型
function print(value: string | number) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}

// instanceof - 类
class Dog {
  bark() { console.log('Woof!'); }
}
class Cat {
  meow() { console.log('Meow!'); }
}
function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// 自定义类型守卫
interface Fish {
  swim(): void;
}
interface Bird {
  fly(): void;
}
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

2. 模板字面量类型

// 动态创建字符串类型
type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${EventName}`;
const handler: Handler = 'onClick'; // ✓
// const invalid: Handler = 'onHover'; // ✗

// 组合类型
type Path = '/home' | '/about' | '/user';
type Method = 'get' | 'post' | 'delete';
type Route = `${Method}${Path}`;
const route: Route = 'get/home'; // ✓

// 提取部分
type Props = 'marginTop' | 'marginBottom' | 'paddingLeft';
type CssProperty = `margin${'Top' | 'Bottom'}` | `padding${'Left' | 'Right'}`;

3. 条件类型

// 基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<123>; // false

// 提取元素类型
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Elem = ArrayElement<string[]>; // string
type Elem2 = ArrayElement<number[]>; // number

// 实用场景:API 响应类型
interface ApiSuccess<T> {
  success: true;
  data: T;
}
interface ApiError {
  success: false;
  error: string;
}
type ApiResponse<T> = ApiSuccess<T> | ApiError;

// 提取成功数据类型
type SuccessData<T> = T extends ApiSuccess<infer U> ? U : never;
type Result = SuccessData<ApiResponse<User>>; // User

4. satisfies 操作符

// 既类型推断,又保持字面量类型
const config = {
  port: 3000,
  host: 'localhost',
  timeout: 5000,
} satisfies Record<string, number | string>;

console.log(config.port); // 类型是 3000(字面量),不是 number
console.log(config.host); // 类型是 'localhost',不是 string

5. 类型断言

// as 断言
const value: unknown = 'hello';
const length = (value as string).length;

// 非空断言
const element = document.getElementById('app')!; // 确信元素存在

// 双重断言
const input: unknown = 'hello';
const str = input as string as number; // 危险!尽量避免

// const 断言
const colors = ['red', 'green', 'blue'] as const;
// 类型是 readonly ['red', 'green', 'blue']

最佳实践

1. 优先使用接口

// ✓ 推荐:接口可以合并
interface User {
  name: string;
}
interface User {
  age: number;
}
const user: User = { name: 'Sean', age: 25 }; // 合并后

// ✗ type 不支持声明合并
type User = { name: string };
type User = { age: number }; // Error

2. 使用 type 别名简化复杂类型

// ✗ 难以阅读
function process(
  items: Array<{ id: number; name: string; status: 'pending' | 'done' }>
): Array<{ id: number; name: string }> {}

// ✓ 使用 type 别名
type Item = {
  id: number;
  name: string;
  status: 'pending' | 'done';
};
type ProcessedItem = Pick<Item, 'id' | 'name'>;

function process(items: Item[]): ProcessedItem[] {
  return items
    .filter(item => item.status === 'done')
    .map(({ id, name }) => ({ id, name }));
}

3. strict 模式始终开启

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
// ✗ noImplicitAny: 会报错
function greet(message) {
  console.log(message);
}

// ✓
function greet(message: string) {
  console.log(message);
}

// ✗ strictNullChecks: 会报错
const element = document.getElementById('app');
element.innerHTML = 'Hello'; // element 可能是 null

// ✓
const element = document.getElementById('app');
if (element) {
  element.innerHTML = 'Hello';
}

4. 避免使用 any,使用 unknown

// ✗ any - 放弃所有类型检查
function parse(value: any): any {
  return value;
}
const result = parse('hello');
result.foo.bar.baz; // 不会报错,隐藏风险

// ✓ unknown - 强制类型检查
function parse(value: unknown): string {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  throw new Error('Invalid value');
}
const result = parse('hello');
// result.foo // Error - unknown 类型

5. 使用枚举还是常量对象

// 方式一:const object(推荐)
const Status = {
  Pending: 'pending',
  Done: 'done',
  Failed: 'failed',
} as const;
type Status = typeof Status[keyof typeof Status];

// 方式二:enum
enum StatusEnum {
  Pending = 'pending',
  Done = 'done',
  Failed = 'failed',
}

// 推荐 const object 的原因:
// - 更易 tree-shake
// - 兼容性好(enum 在某些场景有坑)
// - 符合函数式思维

常见错误解析

错误 1:找不到模块

// ✗
import { something } from './module';

// ✓ 检查路径和扩展名
import { something } from './module.ts';
import { something } from './module/index.ts';

错误 2:类型不匹配

// ✗
const arr = [1, '2', 3];
const num: number = arr[1]; // string 无法赋值给 number

// ✓ 使用联合类型
const arr: (number | string)[] = [1, '2', 3];
const item = arr[1]; // number | string

错误 3:泛型实例化

// ✗
class Container<T> {
  value: T;
}
const container: Container = new Container(); // 需要指定 T

// ✓
const container: Container<string> = new Container();
container.value = 'hello';

总结

TypeScript 的学习曲线并不陡峭,但深入理解其类型系统能让你:

  • 更早发现 bug:编译时而非运行时
  • 更好的代码提示:IDE 自动补全
  • 更安全的重构:修改类型,全局检查

记住这些核心概念:

  • 泛型是 TypeScript 的灵魂
  • 类型守卫让运行时检查更优雅
  • strict 模式是代码质量的保障
  • 优先使用 interface,复杂类型用 type

Happy TypeScripting! 🚀