跳转到内容

TypeScript

一、TypeScript 概述

1.1 什么是 TypeScript

TypeScript 是微软开发的 JavaScript 的超集,在 JS 基础上添加了静态类型系统。TS 代码编译后生成纯 JS 代码,可运行在任何支持 JS 的环境中。

1.2 TypeScript vs JavaScript

特性

JavaScript

TypeScript

类型系统

动态类型

静态类型(可选)

编译检查

运行时才发现错误

编译时即可发现类型错误

IDE 支持

基础

强大的智能提示和自动补全

泛型

不支持

支持

接口/枚举

不支持

支持

装饰器

Stage 3 提案

原生支持(实验性)

学习成本

中等

1.3 为什么选择 TypeScript

  • 编译时捕获类型错误,减少线上 Bug
  • 更好的 IDE 智能提示,提升开发效率
  • 类型即文档,降低代码维护成本
  • 大型项目中优势更明显

二、环境搭建

2.1 安装与编译

# 全局安装
npm install -g typescript

# 查看版本
tsc --version

# 编译单个文件
tsc index.ts

# 编译并监听变化
tsc --watch index.ts

# 初始化 tsconfig.json
tsc --init

2.2 tsconfig.json 关键配置

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

三、基础类型系统

3.1 原始类型

// 布尔
let isDone: boolean = false;

// 数字(所有数字都是浮点数)
let decimal: number = 6;
let hex: number = 0xff;
let binary: number = 0b1010;

// 字符串
let color: string = 'blue';
let greeting: string = `Hello, ${color}`;

// 数组
let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];

// 元组(固定长度和类型的数组)
let tuple: [string, number] = ['hello', 10];

// 枚举
enum Color { Red, Green, Blue }
let c: Color = Color.Green;

// 枚举支持字符串值
enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE',
  Deleted = 'DELETED'
}

3.2 特殊类型

// any — 关闭类型检查(尽量少用)
let notSure: any = 4;
notSure = 'maybe a string';

// unknown — 安全的 any(必须先类型收窄才能使用)
let uncertain: unknown = 4;
if (typeof uncertain === 'string') {
  console.log(uncertain.toUpperCase());
}

// void — 函数无返回值
function warn(msg: string): void {
  console.warn(msg);
}

// never — 永远不会返回(抛异常或死循环)
function error(msg: string): never {
  throw new Error(msg);
}

// null / undefined
let u: undefined = undefined;
let n: null = null;

3.3 类型断言

// 告诉编译器"我知道这是什么类型"
let someValue: unknown = 'hello';

// 方式一:尖括号
let strLength: number = (<string>someValue).length;

// 方式二:as(JSX 中只能用这种)
let strLength2: number = (someValue as string).length;

// 非空断言(告诉编译器值一定不为 null/undefined)
let name: string | null = getName();
console.log(name!.length);

四、接口与类型别名

4.1 Interface(接口)

// 基本接口
interface User {
  id: number;
  name: string;
  email: string;
}

// 可选属性
interface Config {
  url: string;
  method?: string;
  timeout?: number;
}

// 只读属性
interface Point {
  readonly x: number;
  readonly y: number;
}

// 函数类型接口
interface SearchFunc {
  (source: string, keyword: string): boolean;
}

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

// 接口继承
interface Admin extends User {
  role: string;
  permissions: string[];
}

// 接口可以多次声明(自动合并)
interface Window {
  title: string;
}
interface Window {
  width: number;
}
// Window 现在有 title 和 width 两个属性

4.2 Type Alias(类型别名)

// 基本类型别名
type ID = string | number;

// 联合类型
type Status = 'active' | 'inactive' | 'deleted';

// 交叉类型
type Employee = User & { employeeId: string; department: string };

// 函数类型
type Callback = (data: string) => void;

// 元组
type Point2D = [number, number];

4.3 Interface vs Type

特性

Interface

Type

声明合并

支持

不支持

扩展方式

extends

交叉类型 &

联合类型

不支持

支持

元组

不支持

支持

映射类型

不支持

支持

使用建议

优先用 interface 描述对象结构

需要联合类型/映射类型时用 type


五、函数类型

5.1 函数声明与表达式

// 函数声明
function add(x: number, y: number): number {
  return x + y;
}

// 函数表达式
const multiply: (x: number, y: number) => number = function(x, y) {
  return x * y;
};

// 箭头函数
const divide = (x: number, y: number): number => x / y;

// 可选参数(必须在必选参数之后)
function greet(name: string, greeting?: string): string {
  return `${greeting || 'Hello'}, ${name}`;
}

// 默认参数
function greet2(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}`;
}

// 剩余参数
function sum(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}

5.2 函数重载

// 多个重载签名 + 1 个实现签名
function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;
function format(value: string | number | boolean): string {
  if (typeof value === 'string') return `"${value}"`;
  if (typeof value === 'number') return value.toFixed(2);
  return value ? 'true' : 'false';
}

5.3 this 类型

// 显式指定 this 类型(作为第一个参数,编译后会被移除)
interface Card {
  suit: string;
  card: number;
}

function pickCard(this: Card): string {
  return `${this.card} of ${this.suit}`;
}

六、类与面向对象

6.1 类的基本语法

class Animal {
  name: string;
  static species = 'unknown';
  readonly birthDate: Date;

  constructor(name: string) {
    this.name = name;
    this.birthDate = new Date();
  }

  move(distance: number): void {
    console.log(`${this.name} moved ${distance}m.`);
  }

  static create(name: string): Animal {
    return new Animal(name);
  }
}

6.2 访问修饰符

class Person {
  public name: string;
  private age: number;
  protected gender: string;
  readonly id: number;

  // 参数属性简写(构造函数参数前加修饰符,自动声明并赋值)
  constructor(
    name: string,
    private _age: number,
    protected _gender: string
  ) {
    this.name = name;
  }

  // getter / setter
  get age(): number {
    return this._age;
  }

  set age(value: number) {
    if (value < 0) throw new Error('年龄不能为负数');
    this._age = value;
  }
}

6.3 继承与抽象类

// 继承
class Dog extends Animal {
  bark(): void {
    console.log('Woof!');
  }

  move(distance: number): void {
    console.log('Dog running...');
    super.move(distance);
  }
}

// 抽象类(不能直接实例化)
abstract class Shape {
  abstract getArea(): number;

  describe(): string {
    return `Area: ${this.getArea()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  getArea(): number {
    return Math.PI * this.radius ** 2;
  }
}

6.4 接口实现

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();

  setTime(d: Date): void {
    this.currentTime = d;
  }
}

七、泛型

7.1 泛型函数

// 基本泛型
function identity<T>(arg: T): T {
  return arg;
}

const result = identity<string>('hello');
const result2 = identity(42);

// 泛型约束
function logLength<T extends { length: number }>(arg: T): T {
  console.log(arg.length);
  return arg;
}
logLength('hello');   // OK
logLength([1, 2, 3]); // OK

// 多泛型参数
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

7.2 泛型接口与泛型类

// 泛型接口
interface Repository<T> {
  getById(id: string): T;
  getAll(): T[];
  create(item: T): T;
  update(id: string, item: Partial<T>): T;
  delete(id: string): void;
}

// 泛型类
class GenericList<T> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  get(index: number): T {
    return this.items[index];
  }
}

7.3 泛型工具类型

// Partial — 所有属性变为可选
type PartialUser = Partial<User>;

// Required — 所有属性变为必填
type RequiredUser = Required<PartialUser>;

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

// Pick — 选取指定属性
type UserNameAndEmail = Pick<User, 'name' | 'email'>;

// Omit — 排除指定属性
type UserWithoutEmail = Omit<User, 'email'>;

// Record — 构造对象类型
type PageMap = Record<string, string>;

// Exclude — 从联合类型中排除
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'>;  // 'c'

// Extract — 从联合类型中提取
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'c'>;   // 'a' | 'c'

// NonNullable — 排除 null 和 undefined
type T3 = NonNullable<string | null | undefined>;  // string

// ReturnType — 获取函数返回值类型
type T4 = ReturnType<() => string>;  // string

// Parameters — 获取函数参数类型
type T5 = Parameters<(x: number, y: string) => void>;  // [number, string]

// Awaited — 获取 Promise 内部类型
type T6 = Awaited<Promise<string>>;  // string

八、高级类型

8.1 联合类型与交叉类型

// 联合类型 — 值可以是多种类型之一
type StringOrNumber = string | number;

function formatId(id: StringOrNumber): string {
  return `ID: ${id}`;
}

// 交叉类型 — 合并多个类型的所有属性
interface Nameable { name: string; }
interface Ageable { age: number; }
type Person = Nameable & Ageable;
// { name: string; age: number; }

// 类型守卫(type guard)
function printValue(value: string | number): void {
  if (typeof value === 'string') {
    console.log(value.toUpperCase());  // value 被收窄为 string
  } else {
    console.log(value.toFixed(2));     // value 被收窄为 number
  }
}

8.2 类型守卫

// typeof — 原始类型守卫
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

// instanceof — 类实例守卫
class Cat { meow() {} }
class Dog { bark() {} }
function handleAnimal(animal: Cat | Dog) {
  if (animal instanceof Cat) {
    animal.meow();
  }
}

// in — 属性存在守卫
interface Bird { fly(): void; }
interface Fish { swim(): void; }
function move(pet: Bird | Fish) {
  if ('fly' in pet) {
    pet.fly();
  }
}

// 自定义类型守卫(is)
function isUser(obj: any): obj is User {
  return obj && typeof obj.name === 'string' && typeof obj.age === 'number';
}

8.3 条件类型

// 基本条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>;  // true
type B = IsString<number>;  // false

// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>;  // string[] | number[]

// infer — 在条件类型中推断类型
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type D = Unwrap<Promise<string>>;  // string

type FirstArg<T> = T extends (first: infer F, ...args: any[]) => any ? F : never;

8.4 映射类型

// 遍历属性并转换
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

// 模板字面量类型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>;  // 'onClick'

type PropKey = 'name' | 'age' | 'location';
type ChangeEvent = `on${Capitalize<PropKey>}Change`;
// 'onNameChange' | 'onAgeChange' | 'onLocationChange'

8.5 keyof 与 typeof

// keyof — 获取对象类型的所有键
interface User { name: string; age: number; }
type UserKeys = keyof User;  // 'name' | 'age'

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

// typeof — 获取值的类型
const config = {
  host: 'localhost',
  port: 3000,
  debug: true
};
type Config = typeof config;

// as const — 将值收窄为字面量类型
const routes = ['home', 'about', 'contact'] as const;
type Route = typeof routes[number];  // 'home' | 'about' | 'contact'

九、模块与声明文件

9.1 ES Module 使用

// 具名导出
export function add(x: number, y: number): number { return x + y; }

// 默认导出
export default class Calculator { }

// 具名导入
import { add, type Point } from './math';

// 默认导入
import Calculator from './calculator';

// 仅导入类型(编译时被擦除)
import type { User } from './types';

9.2 声明文件(.d.ts)

// 为已有的 JS 库声明类型
declare module 'my-library' {
  export function doSomething(param: string): number;
  export const version: string;
}

// 声明全局变量
declare const APP_VERSION: string;

// 扩展全局命名空间
declare global {
  interface Window {
    __CUSTOM_DATA__: any;
  }
}

// 扩展已有模块
declare module 'express' {
  interface Request {
    user?: { id: string; name: string };
  }
}

十、工程化实践

10.1 代码质量工具

{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "warn",
    "@typescript-eslint/no-explicit-any": "warn"
  }
}

10.2 常用类型声明库

npm i -D @types/node        # Node.js 类型
npm i -D @types/express     # Express 类型
npm i -D @types/react @types/react-dom  # React 类型
npm i -D @types/lodash      # Lodash 类型
npm i -D @types/jest        # Jest 类型

十一、最佳实践

11.1 类型设计原则

  • 优先使用 interface 描述对象形状,用 type 处理联合/交叉/映射类型
  • 避免 any,不确定类型用 unknown
  • 善用泛型约束而不是断言
  • 利用字面量类型缩小类型范围
  • 善用 readonly 防止意外修改
  • 开启 strict 模式从项目初期就保持严格检查

11.2 常见模式

// 模式一:Discriminated Union(可辨识联合)
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':    return Math.PI * shape.radius ** 2;
    case 'rectangle': return shape.width * shape.height;
    case 'triangle':  return (shape.base * shape.height) / 2;
  }
}

// 模式二:Branded Types(品牌类型,防止类型混淆)
type UserId = string & { __brand: 'UserId' };
type OrderId = string & { __brand: 'OrderId' };

// 模式三:Builder Pattern
class RequestBuilder {
  private url: string = '';
  private method: string = 'GET';

  setUrl(url: string): this { this.url = url; return this; }
  setMethod(method: string): this { this.method = method; return this; }
  build() { return { url: this.url, method: this.method }; }
}

11.3 TypeScript 资源

  • 官方文档:https://www.typescriptlang.org/docs/
  • TS Playground:https://www.typescriptlang.org/play
  • DefinitelyTyped:https://github.com/DefinitelyTyped/DefinitelyTyped
  • Type Challenges:https://github.com/type-challenges/type-challenges