一、什么是 TypeScript

TypeScript(简称 TS)是 JavaScript 的超集,在 JS 的基础上添加了静态类型检查。TS 代码最终会被编译成普通 JS 代码在浏览器或 Node.js 中运行。

# 安装 TypeScript 编译器
npm install -g typescript

# 编译 TS 文件为 JS
tsc hello.ts
// tsconfig.json — 项目配置文件,用 tsc --init 生成(支持注释)
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

二、基础类型

2.1 原始类型

let name: string = "张三";
let age: number = 25;
let isStudent: boolean = true;
let empty: null = null;
let notDefined: undefined = undefined;

// bigint 和 symbol
let big: bigint = 100n;
let sym: symbol = Symbol("id");

2.2 数组与元组

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

// 元组 — 固定长度和类型的数组
let person: [string, number] = ["张三", 25];
// person[0] 是 string,person[1] 是 number

2.3 特殊类型

// any — 跳过类型检查(尽量避免使用)
let data: any = "hello";
data = 42; // 不报错

// unknown — 安全版的 any,使用前必须进行类型检查
let input: unknown = "hello";
// console.log(input.toUpperCase()); // 报错!
if (typeof input === "string") {
  console.log(input.toUpperCase()); // 正确
}

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

// never — 永远不会有返回值
function throwError(msg: string): never {
  throw new Error(msg);
}

// 字面量类型 — 限定为某个具体值
let direction: "left" | "right" | "up" | "down" = "left";
let level: 1 | 2 | 3 = 2;

三、对象类型

3.1 接口(interface)

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

const user: User = {
  name: "张三",
  age: 25,
  id: 1,
};
// user.id = 2; // 报错,readonly 不允许修改

3.2 类型别名(type)

type ID = string | number;

type Point = {
  x: number;
  y: number;
};

// 联合类型
type Status = "active" | "inactive" | "pending";

// 交叉类型
type Person = { name: string };
type Employee = { company: string };
type Worker = Person & Employee;
// Worker 包含 name 和 company

3.3 interface vs type

特性 interface type
扩展 extends & 交叉
声明合并 支持 不支持
基本类型别名 不支持 支持
适用场景 定义对象形状 联合、交叉、工具类型
// interface 继承
interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}

四、函数

4.1 函数类型定义

// 普通函数
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const multiply = (a: number, b: number): number => a * b;

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

// 默认参数
function createUser(name: string, role: string = "user") {
  return { name, role };
}

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

4.2 函数类型表达式

type MathFn = (a: number, b: number) => number;

const subtract: MathFn = (a, b) => a - b;

// 用接口定义函数
interface SearchFunc {
  (source: string, sub: string): boolean;
}
const contains: SearchFunc = (src, sub) => src.includes(sub);

五、联合类型与类型守卫

5.1 联合类型

function printId(id: number | string) {
  console.log(`ID: ${id}`);
}

5.2 类型守卫 — 缩小类型范围

// typeof 守卫
function double(value: number | string) {
  if (typeof value === "number") {
    return value * 2;  // 此处 value 确定为 number
  }
  return value.repeat(2); // 此处 value 确定为 string
}

// instanceof 守卫
function getDate(value: Date | string): string {
  if (value instanceof Date) {
    return value.toISOString();
  }
  return value;
}

// 字面量类型守卫
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number };

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;
  }
  return shape.width * shape.height;
}

六、泛型(Generics)

泛型让函数、类型可以处理多种类型,同时保持类型安全。

6.1 基本用法

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

const result1 = identity<string>("hello"); // 类型为 string
const result2 = identity(42);              // 自动推断为 number

6.2 常见场景

// 泛型接口
interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

// 泛型约束
interface HasLength {
  length: number;
}

function getLength<T extends HasLength>(arg: T): number {
  return arg.length;
}

getLength("hello");     // 正确,string 有 length
getLength([1, 2, 3]);   // 正确,数组有 length
// getLength(42);       // 报错,number 没有 length

// 注意:以下工具类型(Partial、Required 等)都是 TypeScript 内置的,无需自己定义。
// 这里展示它们的底层实现原理,帮助你理解映射类型的写法:
// type Partial<T>    = { [K in keyof T]?: T[K] };
// type Required<T>   = { [K in keyof T]-?: T[K] };
// type Pick<T, K>    = { [P in K]: T[P] };
// type Readonly<T>   = { readonly [K in keyof T]: T[K] };
// type Record<K, T>  = { [P in K]: T };

6.3 实际示例

// 通用 API 响应类型
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

interface User {
  id: number;
  name: string;
}

// 使用时 data 自动推导为 User[]
async function getUsers(): Promise<ApiResponse<User[]>> {
  const res = await fetch("/api/users");
  return res.json();
}

七、类(Class)

7.1 基本定义

class Person {
  // 属性声明(TS 要求先声明再使用)
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): string {
    return `我是 ${this.name},今年 ${this.age}`;
  }
}

const p = new Person("张三", 25);

7.2 修饰符

class Animal {
  // public — 默认,随处可访问
  public name: string;

  // private — 仅类内部可访问
  private _age: number;

  // protected — 类内部及子类可访问
  protected type: string;

  // readonly — 只能在构造函数中赋值
  readonly id: number;

  constructor(name: string, age: number, type: string, id: number) {
    this.name = name;
    this._age = age;
    this.type = type;
    this.id = id;
  }
}

7.3 简写形式(参数属性)

class Student {
  constructor(
    public name: string,
    private age: number,
    readonly id: number
  ) {}
}
// 等同于手动声明 + constructor 赋值

7.4 抽象类

abstract class Shape {
  abstract getArea(): number; // 子类必须实现

  describe(): string {
    return `面积: ${this.getArea().toFixed(2)}`;
  }
}

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

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

7.5 实现接口

interface Printable {
  print(): void;
}

class Document implements Printable {
  constructor(public content: string) {}

  print(): void {
    console.log(this.content);
  }
}

八、枚举(Enum)

// 数字枚举(默认从 0 开始)
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right,   // 3
}

// 字符串枚举
enum Role {
  Admin = "ADMIN",
  User = "USER",
  Guest = "GUEST",
}

// 使用
let dir: Direction = Direction.Up;
if (dir === Direction.Up) {
  // ...
}

提示:优先使用联合字面量类型代替枚举,更轻量:

type Direction = "up" | "down" | "left" | "right";

九、类型断言

类型断言告诉编译器"我比你更了解这个类型",不会进行运行时检查。

// as 语法(推荐)
const canvas = document.getElementById("canvas") as HTMLCanvasElement;

// 尖括号语法(JSX 中不可用)
const input = <HTMLInputElement>document.getElementById("input");

// 非空断言 — 告诉编译器值不是 null/undefined
const el = document.querySelector(".box")!; // 断言元素一定存在

// 双重断言(极不推荐,通常意味着设计有问题)
const value = "hello" as unknown as number;

十、模块与命名空间

10.1 模块

每个包含 importexport 的文件都是一个模块。

// 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 from "./utils";
import { add, PI } from "./utils";

// 全部导入
import * as utils from "./utils";

10.2 类型导入(type-only import)

// 仅导入类型,编译后会被移除
import type { User } from "./types";
import { type User, formatName } from "./utils";

十一、实用工具类型

TypeScript 内置了许多实用类型,定义在 lib.es5.d.ts 中。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

// Partial — 所有属性变可选
type PartialTodo = Partial<Todo>;
// { title?: string; description?: string; completed?: boolean }

// Required — 所有属性变必填
type RequiredTodo = Required<PartialTodo>;

// Pick — 选取部分属性
type TodoPreview = Pick<Todo, "title" | "completed">;
// { title: string; completed: boolean }

// Omit — 排除部分属性
type TodoInfo = Omit<Todo, "completed">;
// { title: string; description: string }

// Record — 构造键值对类型
type PageInfo = Record<string, { title: string }>;
// { [url: string]: { title: string } }

// Readonly — 所有属性只读
type ReadonlyTodo = Readonly<Todo>;

// ReturnType — 获取函数返回值类型
function createUser() {
  return { name: "张三", age: 25 };
}
type User = ReturnType<typeof createUser>;

// keyof — 获取对象所有键的联合类型
type TodoKeys = keyof Todo; // "title" | "description" | "completed"

十二、声明文件(.d.ts)

用于描述已有 JavaScript 库的类型。

// global.d.ts — 全局类型声明
declare function formatDate(date: Date): string;

declare namespace MyLib {
  function greet(name: string): string;
  const version: string;
}
// 使用第三方库的类型包
// npm install @types/lodash
import _ from "lodash";

十三、配置要点

tsconfig.json 常用配置:

{
  "compilerOptions": {
    "target": "ES2020",          // 编译目标
    "module": "ESNext",          // 模块系统
    "strict": true,              // 开启所有严格检查(推荐)
    "noImplicitAny": true,       // 禁止隐式 any
    "strictNullChecks": true,    // 严格空值检查
    "noUnusedLocals": true,      // 未使用变量报错
    "moduleResolution": "node",  // 模块解析策略
    "esModuleInterop": true,     // 兼容 CommonJS 模块
    "outDir": "./dist",          // 输出目录
    "sourceMap": true            // 生成 sourcemap
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

十四、学习建议

  1. 先学 JS 再学 TS — TS 是 JS 的超集,JS 基础是前提
  2. 开启 strict: true — 从一开始就养成严格类型的习惯
  3. 少用 any,多用 unknownany 会绕过所有类型检查
  4. 优先用 type — 大多数场景下 typeinterface 互换,type 更灵活
  5. 善用编辑器提示 — VS Code 的 TS 支持非常强大,悬停查看类型,错误提示很详细
  6. 渐进式迁移 — 如果现有 JS 项目迁移,可以逐步将 .js 改为 .ts,允许 any 过渡

更多推荐