TypeScript 与类型系统

深入理解 TypeScript 类型系统、高级类型技巧、泛型编程、以及如何在大型项目中有效使用 TypeScript。


TypeScript 概述

为什么需要 TypeScript

┌──────────────────────────────────────────────────────────────┐
│                   JavaScript 类型问题                         │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  JavaScript: 动态类型,运行时才发现问题                        │
│                                                              │
│  function add(a, b) {                                        │
│    return a + b;                                             │
│  }                                                           │
│                                                              │
│  add(1, 2)      // ✅ 3                                     │
│  add("a", "b")   // ✅ "ab"                                 │
│  add(1, "2")     // ⚠️ "12" (隐式转换)                       │
│  add({}, [])     // ⚠️ "[object Object]"                    │
│                                                              │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  TypeScript: 静态类型,编译时就发现问题                        │
│                                                              │
│  function add(a: number, b: number): number {                │
│    return a + b;                                             │
│  }                                                           │
│                                                              │
│  add(1, 2)        // ✅                                     │
│  add("a", "b")     // ❌ 编译错误!                          │
│  add(1, "2")       // ❌ 编译错误!                          │
│                                                              │
└──────────────────────────────────────────────────────────────┘

TypeScript 配置

// tsconfig.json
{
  "compilerOptions": {
    // 目标版本
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],

    // 模块解析
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,

    // 严格模式(必须开启)
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,

    // 输出控制
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",

    // 其他
    "skipLibCheck": true,
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

基础类型

原始类型

// 原始类型
const name: string = 'John';
const age: number = 30;
const isActive: boolean = true;
const bigInt: bigint = 100n;
const symbol: symbol = Symbol('id');

// null 和 undefined
const nullValue: null = null;
const undefinedValue: undefined = undefined;

// void(用于函数无返回值)
function log(message: string): void {
  console.log(message);
}

// never(用于永不返回的函数)
function throwError(message: string): never {
  throw new Error(message);
}

// any(尽量避免使用)
const unknown: any = JSON.parse('{"name": "John"}');

数组和元组

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

// 只读数组
const readonly: ReadonlyArray<number> = [1, 2, 3];
const readonly2: readonly number[] = [1, 2, 3];

// 元组(固定长度和类型的数组)
const tuple: [string, number] = ['age', 30];
const [key, value] = tuple;  // 解构赋值

// 可选元素的元组
const optionalTuple: [string, number?] = ['hello'];

// 元组标签
const labeled: [name: string, age: number] = ['John', 30];

对象类型

// 对象类型
interface User {
  name: string;
  age: number;
  email?: string;  // 可选属性
  readonly id: number;  // 只读属性
}

// 类型别名
type User = {
  name: string;
  age: number;
};

// 索引签名
interface StringMap {
  [key: string]: string;
}

const map: StringMap = {
  key1: 'value1',
  key2: 'value2'
};

// 嵌套对象
interface Address {
  street: string;
  city: string;
  country: string;
  zipCode: string;
}

interface Company {
  name: string;
  address: Address;
}

联合类型与交叉类型

联合类型

// 联合类型:可以是多种类型之一
type StringOrNumber = string | number;
type Result = Success | Error;
type Status = 'pending' | 'fulfilled' | 'rejected';

// 字面量类型
type Direction = 'north' | 'south' | 'east' | 'west';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

// 使用联合类型
function format(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase();  // TypeScript 知道这是 string
  }
  return value.toFixed(2);  // TypeScript 知道这是 number
}

// 类型守卫
type Animal = Dog | Cat | Bird;

function speak(animal: Animal) {
  if (animal.type === 'dog') {
    animal.bark();  // TypeScript 知道是 Dog
  } else if (animal.type === 'cat') {
    animal.meow();  // TypeScript 知道是 Cat
  } else {
    animal.fly();  // TypeScript 知道是 Bird
  }
}

交叉类型

// 交叉类型:同时具有多种类型
interface Printable {
  print(): void;
}

interface Loggable {
  log(): void;
}

// 交叉类型
type PrintAndLog = Printable & Loggable;

const obj: PrintAndLog = {
  print() { console.log('print'); },
  log() { console.log('log'); }
};

// 混入模式
function withLogging<T extends object>(target: T): T & Loggable {
  return {
    ...target,
    log() { console.log('logged'); }
  };
}

泛型

基础泛型

// 泛型函数
function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42);
const str = identity('hello');  // TypeScript 自动推断

// 泛型接口
interface Container<T> {
  value: T;
  get(): T;
  set(value: T): void;
}

class Box<T> implements Container<T> {
  constructor(private _value: T) {}

  get(): T { return this._value; }
  set(value: T) { this._value = value; }
}

const box = new Box<number>(123);

泛型约束

// 使用 extends 约束类型
interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(value: T): void {
  console.log(value.length);
}

logLength('hello');     // ✅ string 有 length
logLength([1, 2, 3]);   // ✅ array 有 length
logLength({ length: 10 }); // ✅ 有 length 属性
// logLength(123);      // ❌ number 没有 length

// keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name');   // ✅ string
const age = getProperty(user, 'age');    // ✅ number
// const invalid = getProperty(user, 'email'); // ❌

// 多重约束
function combine<T extends HasLength & HasName, U extends HasName>(
  a: T, b: U
): T & U {
  return { ...a, ...b };
}

泛型工具类型

// Partial - 所有属性变为可选
interface User {
  name: string;
  age: number;
  email: string;
}

type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string; }

// Required - 所有属性变为必需
type RequiredUser = Required<User>;

// Readonly - 所有属性变为只读
type ReadonlyUser = Readonly<User>;

// Pick - 选择属性
type UserPreview = Pick<User, 'name' | 'email'>;
// { name: string; email: string; }

// Omit - 排除属性
type UserWithoutAge = Omit<User, 'age'>;
// { name: string; email: string; }

// Record - 创建键值对类型
type UserRoles = Record<string, Role>;
type Permissions = 'read' | 'write' | 'delete';
const permissions: Record<Permissions, boolean> = {
  read: true,
  write: false,
  delete: true
};

// Exclude 和 Extract
type AllStatuses = 'pending' | 'active' | 'inactive' | 'deleted';
type ActiveStatuses = Exclude<AllStatuses, 'pending' | 'deleted'>;
// 'active' | 'inactive'

type ExtractStatuses = Extract<AllStatuses, 'active' | 'pending'>;
// 'pending' | 'active'

// NonNullable - 移除 null 和 undefined
type NonNullEmail = NonNullable<User['email'] | null | undefined>;
// string

// ReturnType - 获取函数返回类型
function createUser() {
  return { name: 'John', age: 30 };
}
type UserType = ReturnType<typeof createUser>;
// { name: string; age: number; }

// Parameters - 获取函数参数类型
function updateUser(id: number, name: string, age: number) {}
type UpdateUserParams = Parameters<typeof updateUser>;
// [id: number, name: string, age: number]

高级类型

条件类型

// 基础条件类型
type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<string>;  // 'yes'
type B = IsString<number>;  // 'no'

// 分布式条件类型
type Flatten<T> = T extends Array<infer U> ? U : T;

type C = Flatten<string[]>;  // string
type D = Flatten<number>;   // number

// 推断类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type E = ReturnType<() => string>;  // string
type F = ReturnType<(x: number) => boolean>;  // boolean

// 工具类型的实现
type MyParameters<T extends (...args: any) => any> =
  T extends (...args: infer P) => any ? P : never;

type MyConstructorParameters<T extends new (...args: any) => any> =
  T extends new (...args: infer P) => any ? P : never;

type MyInstanceType<T extends new (...args: any) => any> =
  T extends new (...args: any) => infer R ? R : never;

映射类型

// 基础映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};

// 带条件映射
type Concrete<T> = {
  [P in keyof T]-?: T[P];  // -? 移除可选
};

type NonNullable<T> = {
  [P in keyof T]: NonNullable<T[P]>;
};

// 模板字面量映射
type EventName<T extends string> = `on${Capitalize<T>}`;

type ButtonEvents = EventName<'click' | 'focus' | 'blur'>;
// 'onClick' | 'onFocus' | 'onBlur'

// 递归映射类型
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

模板字面量类型

// 模板字面量
type World = 'world';
type Greeting = `hello ${World}`;  // 'hello world'

// 联合类型展开
type Direction = 'north' | 'south' | 'east' | 'west';
type EventName = `on${Direction}`;
type CSSProperty = `${Capitalize<Direction>}Margin`;

// 提取 URL 部分
type ExtractRoute<T extends string> =
  T extends `${infer Method} /api/${infer Route}` ? Route : never;

type Route = ExtractRoute<'GET /api/users'>;  // 'users'
type Route2 = ExtractRoute<'POST /api/users'>;  // 'users'
// type Route3 = ExtractRoute<'GET /other'>;  // never

// JSON 类型
type JsonPrimitive = string | number | boolean | null;
type JsonObject = { [key: string]: JsonValue };
type JsonArray = JsonValue[];
type JsonValue = JsonPrimitive | JsonObject | JsonArray;

类型守卫与类型窄缩

自定义类型守卫

interface Cat {
  meow(): void;
  type: 'cat';
}

interface Dog {
  bark(): void;
  type: 'dog';
}

type Animal = Cat | Dog;

// 类型守卫函数
function isCat(animal: Animal): animal is Cat {
  return animal.type === 'cat';
}

function isDog(animal: Animal): animal is Dog {
  return animal.type === 'dog';
}

// 使用
function speak(animal: Animal) {
  if (isCat(animal)) {
    animal.meow();
  } else if (isDog(animal)) {
    animal.bark();
  }
}

// in 操作符类型守卫
function isCat2(animal: Animal): animal is Cat {
  return 'meow' in animal;
}

typeof 类型守卫

function processValue(value: string | number | null) {
  if (typeof value === 'string') {
    // value 是 string 类型
    value.toUpperCase();
  } else if (typeof value === 'number') {
    // value 是 number 类型
    value.toFixed(2);
  }
  // value 是 null
}

断言函数

// 断言函数 - 确保类型
function assertIsString(val: unknown): asserts val is string {
  if (typeof val !== 'string') {
    throw new Error('Not a string!');
  }
}

function process(value: unknown) {
  assertIsString(value);
  // 这里 value 被断言为 string
  value.toUpperCase();
}

// 非空断言
function getLength(str: string | null): number {
  return str!.length;  // 确信 str 不为 null
}

// 确定赋值断言
let initialized: string;
// console.log(initialized);  // ❌ 可能未初始化
let definitelyInitialized!: string;
console.log(definitelyInitialized);  // ✅ 告诉 TS 我确定已赋值

装饰器与反射

装饰器基础(实验性)

// 启用 experimentalDecorators
// tsconfig.json: { "experimentalDecorators": true }

// 类装饰器
function sealed(target: Function) {
  Object.seal(target);
  Object.seal(target.prototype);
}

@sealed
class BugReport {
  id: number;
}

// 方法装饰器
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${key} with`, args);
    return original.apply(this, args);
  };
  return descriptor;
}

class Calculator {
  @log
  add(a: number, b: number) {
    return a + b;
  }
}

// 属性装饰器
function readonly(target: any, key: string) {
  Object.defineProperty(target, key, {
    writable: false,
    value: 42
  });
}

class Config {
  @readonly
  VERSION = '1.0.0';
}

TypeScript 与 React

React 组件类型

import React, { FC, useState, useCallback } from 'react';

// Props 类型定义
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
  disabled?: boolean;
}

// 函数组件
export const Button: FC<ButtonProps> = ({
  variant = 'primary',
  size = 'md',
  children,
  onClick,
  disabled = false
}) => {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
};

// Hooks 类型
interface UseCounterProps {
  initial?: number;
  min?: number;
  max?: number;
}

function useCounter({
  initial = 0,
  min = Number.MIN_SAFE_INTEGER,
  max = Number.MAX_SAFE_INTEGER
}: UseCounterProps = {}) {
  const [count, setCount] = useState(initial);

  const increment = useCallback(() => {
    setCount(c => Math.min(c + 1, max));
  }, [max]);

  const decrement = useCallback(() => {
    setCount(c => Math.max(c - 1, min));
  }, [min]);

  return { count, increment, decrement, setCount };
}

// 泛型组件
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

export function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map(item => (
        <li key={keyExtractor(item)}>
          {renderItem(item)}
        </li>
      ))}
    </ul>
  );
}

事件处理类型

// React 事件类型
function EventExamples() {
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    console.log(e.clientX, e.clientY);
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        onChange={handleChange}
        onKeyDown={handleKeyDown}
      />
      <button onClick={handleClick}>Click</button>
    </form>
  );
}

这一章想说的

TypeScript 是大型前端项目的必备工具:

  1. 类型安全:编译时发现类型错误,减少运行时 bug
  2. 类型推断:TypeScript 能自动推断类型,减少显式标注
  3. 泛型:编写可复用且类型安全的代码
  4. 高级类型:条件类型、映射类型、模板字面量类型
  5. 类型守卫:缩小类型范围,确保类型安全

合理使用 TypeScript 能显著提升代码质量和可维护性。


延展阅读