Skip to content

TypeScript 介绍与环境搭建

什么是 TypeScript?

TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上添加了静态类型系统。由微软开发并维护,TypeScript 不仅兼容所有现有的 JavaScript 代码,还为开发者提供了强大的类型检查和现代语言特性。

TypeScript 的本质

可以这样理解 TypeScript:

  • JavaScript:就像是和朋友随口交谈,虽然表达自由,但容易产生误解
  • TypeScript:则像是签署正式合同,每个条款都明确定义,避免歧义和错误
javascript
// 纯JavaScript - 运行时才能发现错误
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// 错误:item没有price属性
const result = calculateTotal([
  { name: "苹果", cost: 5 }, // 属性名不一致
  { name: "香蕉", price: 3 },
]);

// 运行时才会发现result是NaN!
console.log(result); // NaN
typescript
// TypeScript - 编写时就能发现错误
interface Product {
  name: string;
  price: number;
}

function calculateTotal(items: Product[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// 编写时就会报错!
const result = calculateTotal([
  { name: "苹果", cost: 5 }, // 错误:'cost'不在'Product'中
  { name: "香蕉", price: 3 }, // 正确
]);

// 编译前就能发现问题并修复

TypeScript 的优势

1. 类型安全

类型安全是 TypeScript 最大的价值所在,它能在编译阶段就捕获大量潜在错误。

typescript
// 基本类型约束
function processUserData(id: number, name: string, isActive: boolean) {
  console.log(`用户${id}:${name},状态:${isActive}`);
}

// 编译时会检查参数类型
processUserData(123, "张三", true); // ✓ 正确
processUserData("abc", "李四", false); // ✗ 错误:id应该是number
processUserData(456, 789, true); // ✗ 错误:name应该是string

2. 增强的 IDE 支持

TypeScript 为编辑器提供了丰富的类型信息,极大地提升了开发体验。

typescript
interface User {
  id: number;
  name: string;
  email: string;
  role: "admin" | "user" | "guest";
  lastLogin?: Date; // 可选属性
}

function updateUser(user: User, updates: Partial<User>) {
  return { ...user, ...updates };
}

// IDE会提供:
// 1. 自动补全
const user: User = {
  id: 1,
  name: "张三", // IDE会提示所有必要属性
  // email: ...  // IDE会显示缺少的属性
};

// 2. 类型检查和错误提示
const updated = updateUser(user, {
  lastLogin: new Date(), // ✓ 类型正确
  age: 30, // ✗ 错误:'age'不在'User'类型中
});

// 3. 智能提示
updated.role = "admin"; // IDE会显示可选值:'admin' | 'user' | 'guest'

3. 重构信心

有了类型系统的保护,大型项目的重构变得更加安全。

typescript
// 重构前的代码
class UserService {
  constructor(private database: Database) {}

  async getUser(id: number): Promise<User | null> {
    return this.database.users.findById(id);
  }

  async updateUser(id: number, data: Partial<User>): Promise<User> {
    return this.database.users.update(id, data);
  }
}

// 假设我们要重构:将User类型重构为Person类型
interface Person {
  uuid: string; // 从id改为uuid,类型从number改为string
  name: string;
  email: string;
  role: UserRole;
  lastLogin?: Date;
}

// TypeScript会在所有使用User的地方标记错误,确保重构不会遗漏任何地方
class UserService {
  constructor(private database: Database) {}

  async getUser(id: number): Promise<Person | null> {
    // ✗ 错误:参数类型不匹配
    return this.database.users.findById(id); // ✗ 错误:返回类型不匹配
  }

  async updateUser(id: number, data: Partial<Person>): Promise<Person> {
    // ✗ 错误
    return this.database.users.update(id, data); // ✗ 错误
  }
}

// 重构后的安全代码
class UserService {
  constructor(private database: Database) {}

  async getUser(uuid: string): Promise<Person | null> {
    return this.database.users.findByUuid(uuid);
  }

  async updateUser(uuid: string, data: Partial<Person>): Promise<Person> {
    return this.database.users.updateByUuid(uuid, data);
  }
}

4. 更好的文档和代码可读性

类型本身就是最好的文档。

typescript
// 没有类型的JavaScript
function createOrder(data) {
  // 这个函数接受什么参数?
  // data应该有什么结构?
  // 返回什么?
}

// 有类型的TypeScript
interface OrderData {
  customerId: number;
  items: OrderItem[];
  shippingAddress: Address;
  paymentMethod: PaymentMethod;
}

interface OrderItem {
  productId: number;
  quantity: number;
  unitPrice: number;
}

interface Address {
  street: string;
  city: string;
  state: string;
  zipCode: string;
  country: string;
}

type PaymentMethod = "credit_card" | "debit_card" | "paypal" | "bank_transfer";

interface Order {
  id: string;
  customerId: number;
  items: OrderItem[];
  totalAmount: number;
  status: "pending" | "confirmed" | "shipped" | "delivered" | "cancelled";
  createdAt: Date;
  estimatedDelivery: Date;
}

function createOrder(data: OrderData): Promise<Order> {
  // 函数签名清晰地说明了:
  // 1. 输入参数的结构和类型
  // 2. 返回值的类型
  // 3. 各种可能的状态和值
}

TypeScript 的类型系统

1. 基本类型

TypeScript 提供了 JavaScript 的所有基本类型,以及额外的实用类型:

typescript
// 基本类型
let isDone: boolean = false;
let decimal: number = 6.66;
let color: string = "blue";
let list: number[] = [1, 2, 3];
let x: [string, number] = ["hello", 10]; // 元组

// 特殊类型
let u: undefined = undefined;
let n: null = null;
let any: any = 42;           // 任意类型(谨慎使用)
let unknown: unknown = 42;    // 未知类型(比any更安全)
let never: never = (() => {})(); // 永不存在的类型
let void: void = undefined;   // 无返回值

// 字面量类型
let direction: "north" | "south" | "east" | "west" = "north";
let status: 200 | 404 | 500 = 200;

// 对象类型
interface Point {
  x: number;
  y: number;
}

let point: Point = { x: 10, y: 20 };

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

// 只读属性
interface Config {
  readonly host: string;
  readonly port: number;
}

let config: Config = {
  host: "localhost",
  port: 3000
};

// config.host = "example.com"; // ✗ 错误:host是只读的

2. 函数类型

TypeScript 为函数提供了丰富的类型定义能力:

typescript
// 基本函数类型
function add(x: number, y: number): number {
  return x + y;
}

// 可选参数和默认参数
function greet(name: string, greeting?: string = "Hello") {
  return greeting + ", " + name;
}

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

// 函数表达式类型
let multiply: (x: number, y: number) => number = (x, y) => x * y;

// 异步函数
async function fetchData(url: string): Promise<Response> {
  const response = await fetch(url);
  return response;
}

// 回调函数
function mapArray<T, U>(
  array: T[],
  mapper: (item: T, index: number, array: T[]) => U
): U[] {
  return array.map(mapper);
}

// 使用示例
const numbers = [1, 2, 3, 4, 5];
const doubled = mapArray(numbers, (n) => n * 2); // 类型推导为number[]

3. 高级类型

TypeScript 的高级类型系统提供了强大的类型组合能力:

typescript
// 联合类型(Union Types)
type Status = "pending" | "approved" | "rejected";
type ID = string | number;

function processStatus(status: Status) {
  // TypeScript知道status只能是这三种值之一
  switch (status) {
    case "pending":
      console.log("处理中...");
      break;
    case "approved":
      console.log("已批准");
      break;
    case "rejected":
      console.log("已拒绝");
      break;
  }
}

// 交叉类型(Intersection Types)
interface Person {
  name: string;
  age: number;
}

interface Employee {
  id: string;
  department: string;
}

type EmployeeInfo = Person & Employee;

const emp: EmployeeInfo = {
  name: "张三",
  age: 30,
  id: "EMP001",
  department: "技术部",
};

// 泛型(Generics)
interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
}

function createApiResponse<T>(data: T, success = true): ApiResponse<T> {
  return {
    success,
    data,
    message: success ? "操作成功" : "操作失败",
  };
}

// 使用泛型
const userResponse = createApiResponse({
  id: 1,
  name: "李四",
}); // 类型为ApiResponse<{id: number; name: string;}>

const userListResponse = createApiResponse([
  { id: 1, name: "李四" },
  { id: 2, name: "王五" },
]); // 类型为ApiResponse<{id: number; name: string;}[]>

环境搭建

1. 安装 TypeScript

开始使用 TypeScript 有几种方式:

方式一:全局安装

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

# 检查安装版本
tsc --version

# 编译TypeScript文件
tsc app.ts

方式二:项目级安装(推荐)

bash
# 创建项目目录
mkdir my-typescript-project
cd my-typescript-project

# 初始化package.json
npm init -y

# 安装TypeScript作为开发依赖
npm install --save-dev typescript

# 安装类型声明(Node.js的类型定义)
npm install --save-dev @types/node

# 初始化TypeScript配置
npx tsc --init

2. TypeScript 配置文件

tsc --init会创建一个tsconfig.json文件,这是 TypeScript 项目的核心配置:

json
{
  "compilerOptions": {
    /* 基本选项 */
    "target": "ES2020", // 编译目标版本
    "module": "commonjs", // 模块系统
    "lib": ["ES2020", "DOM"], // 需要包含的库文件
    "outDir": "./dist", // 输出目录
    "rootDir": "./src", // 源代码根目录

    /* 严格检查选项 */
    "strict": true, // 启用所有严格检查
    "noImplicitAny": true, // 禁止隐式any类型
    "strictNullChecks": true, // 严格的null检查
    "strictFunctionTypes": true, // 严格的函数类型检查
    "noImplicitReturns": true, // 函数必须有返回值
    "noFallthroughCasesInSwitch": true, // switch语句必须有break

    /* 模块解析选项 */
    "moduleResolution": "node", // 模块解析策略
    "baseUrl": "./", // 基础路径
    "paths": {
      // 路径映射
      "@/*": ["src/*"],
      "@/components/*": ["src/components/*"]
    },
    "allowSyntheticDefaultImports": true, // 允许合成默认导入

    /* 其他选项 */
    "esModuleInterop": true, // 启用ES模块互操作
    "skipLibCheck": true, // 跳过库文件检查
    "forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
    "declaration": true, // 生成声明文件
    "declarationMap": true, // 生成声明文件的source map
    "sourceMap": true, // 生成source map文件
    "removeComments": true, // 移除注释
    "noEmitOnError": true // 发生错误时不生成文件
  },
  "include": [
    "src/**/*" // 包含的文件
  ],
  "exclude": [
    "node_modules", // 排除的文件
    "dist"
  ]
}

3. 构建工具集成

与 Vite 集成(现代前端开发推荐)

bash
# 创建Vite + TypeScript项目
npm create vite@latest my-app -- --template vanilla-ts

# 或者React + TypeScript
npm create vite@latest my-react-app -- --template react-ts

# 或者Vue + TypeScript
npm create vite@latest my-vue-app -- --template vue-ts

Vite 项目已经配置好了 TypeScript 支持,主要配置文件:

typescript
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  // TypeScript配置会自动从tsconfig.json读取
});

与 Webpack 集成

bash
# 安装必要的依赖
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save-dev ts-loader typescript
npm install --save-dev html-webpack-plugin
javascript
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
  devServer: {
    static: {
      directory: path.join(__dirname, "dist"),
    },
    compress: true,
    port: 3000,
  },
};

4. 编辑器配置

VS Code 推荐设置

json
{
  "typescript.preferences.quoteStyle": "single",
  "typescript.suggest.autoImports": true,
  "typescript.updateImportsOnFileMove.enabled": "always",
  "typescript.preferences.includePackageJsonAutoImports": "on",
  "typescript.workspaceSymbols.scope": "allOpenProjects",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.organizeImports": true
  },
  "typescript.preferences.importModuleSpecifier": "relative"
}

推荐的 VS Code 插件

  1. TypeScript Importer:自动导入模块
  2. TypeScript Hero:类型检查和重构工具
  3. Auto Rename Tag:自动重命名标签
  4. Prettier:代码格式化
  5. ESLint:代码质量检查

第一个 TypeScript 项目

让我们创建一个简单的待办事项应用来体验 TypeScript:

1. 项目结构

my-todo-app/
├── src/
│   ├── models/
│   │   └── Todo.ts
│   ├── components/
│   │   ├── TodoItem.ts
│   │   └── TodoList.ts
│   ├── services/
│   │   └── TodoService.ts
│   ├── app.ts
│   └── index.ts
├── dist/
├── package.json
└── tsconfig.json

2. 定义数据模型

typescript
// src/models/Todo.ts
export interface Todo {
  id: string;
  title: string;
  description?: string;
  completed: boolean;
  createdAt: Date;
  updatedAt: Date;
}

export interface CreateTodoInput {
  title: string;
  description?: string;
}

export interface UpdateTodoInput {
  title?: string;
  description?: string;
  completed?: boolean;
}

3. 实现服务层

typescript
// src/services/TodoService.ts
import { Todo, CreateTodoInput, UpdateTodoInput } from "../models/Todo";

export class TodoService {
  private todos: Todo[] = [];

  getAll(): Todo[] {
    return [...this.todos];
  }

  getById(id: string): Todo | null {
    return this.todos.find((todo) => todo.id === id) || null;
  }

  create(input: CreateTodoInput): Todo {
    const newTodo: Todo = {
      id: this.generateId(),
      title: input.title,
      description: input.description,
      completed: false,
      createdAt: new Date(),
      updatedAt: new Date(),
    };

    this.todos.push(newTodo);
    return newTodo;
  }

  update(id: string, input: UpdateTodoInput): Todo | null {
    const todoIndex = this.todos.findIndex((todo) => todo.id === id);

    if (todoIndex === -1) {
      return null;
    }

    const updatedTodo: Todo = {
      ...this.todos[todoIndex],
      ...input,
      updatedAt: new Date(),
    };

    this.todos[todoIndex] = updatedTodo;
    return updatedTodo;
  }

  delete(id: string): boolean {
    const todoIndex = this.todos.findIndex((todo) => todo.id === id);

    if (todoIndex === -1) {
      return false;
    }

    this.todos.splice(todoIndex, 1);
    return true;
  }

  private generateId(): string {
    return Math.random().toString(36).substring(7);
  }
}

4. 实现组件

typescript
// src/components/TodoItem.ts
import { Todo } from "../models/Todo";

export class TodoItem {
  private element: HTMLElement;
  private todo: Todo;
  private onDelete?: (id: string) => void;
  private onToggle?: (id: string) => void;

  constructor(
    todo: Todo,
    onDelete?: (id: string) => void,
    onToggle?: (id: string) => void
  ) {
    this.todo = todo;
    this.onDelete = onDelete;
    this.onToggle = onToggle;
    this.element = this.createElement();
  }

  private createElement(): HTMLElement {
    const div = document.createElement("div");
    div.className = `todo-item ${this.todo.completed ? "completed" : ""}`;

    div.innerHTML = `
      <div class="todo-content">
        <input type="checkbox" ${this.todo.completed ? "checked" : ""}>
        <span class="todo-title">${this.todo.title}</span>
        ${
          this.todo.description
            ? `<p class="todo-description">${this.todo.description}</p>`
            : ""
        }
      </div>
      <div class="todo-actions">
        <button class="delete-btn">删除</button>
      </div>
    `;

    // 绑定事件
    const checkbox = div.querySelector(
      'input[type="checkbox"]'
    ) as HTMLInputElement;
    const deleteBtn = div.querySelector(".delete-btn") as HTMLButtonElement;

    checkbox.addEventListener("change", () => {
      this.onToggle?.(this.todo.id);
    });

    deleteBtn.addEventListener("click", () => {
      this.onDelete?.(this.todo.id);
    });

    return div;
  }

  update(todo: Todo): void {
    this.todo = todo;
    const newElement = this.createElement();
    this.element.replaceWith(newElement);
    this.element = newElement;
  }

  getElement(): HTMLElement {
    return this.element;
  }
}
typescript
// src/components/TodoList.ts
import { Todo } from "../models/Todo";
import { TodoItem } from "./TodoItem";

export class TodoList {
  private element: HTMLElement;
  private todoItems: Map<string, TodoItem> = new Map();

  constructor(
    todos: Todo[],
    onDelete?: (id: string) => void,
    onToggle?: (id: string) => void
  ) {
    this.element = this.createElement();
    this.render(todos, onDelete, onToggle);
  }

  private createElement(): HTMLElement {
    const div = document.createElement("div");
    div.className = "todo-list";
    return div;
  }

  private render(
    todos: Todo[],
    onDelete?: (id: string) => void,
    onToggle?: (id: string) => void
  ): void {
    this.element.innerHTML = "";

    todos.forEach((todo) => {
      const todoItem = new TodoItem(todo, onDelete, onToggle);
      this.todoItems.set(todo.id, todoItem);
      this.element.appendChild(todoItem.getElement());
    });
  }

  update(todos: Todo[]): void {
    this.render(todos);
  }

  getElement(): HTMLElement {
    return this.element;
  }
}

5. 主应用

typescript
// src/app.ts
import { TodoService } from "./services/TodoService";
import { TodoList } from "./components/TodoList";
import { Todo } from "./models/Todo";

export class App {
  private todoService: TodoService;
  private todoList: TodoList;
  private container: HTMLElement;

  constructor(container: HTMLElement) {
    this.container = container;
    this.todoService = new TodoService();
    this.todoList = new TodoList(
      this.todoService.getAll(),
      this.handleDelete.bind(this),
      this.handleToggle.bind(this)
    );
  }

  render(): void {
    this.container.innerHTML = `
      <div class="app">
        <header class="app-header">
          <h1>待办事项</h1>
          <div class="add-todo">
            <input type="text" id="todo-input" placeholder="添加新任务...">
            <button id="add-btn">添加</button>
          </div>
        </header>
        <main class="app-main">
          <div class="todo-list-container"></div>
        </main>
      </div>
    `;

    this.setupEventListeners();
    this.renderTodoList();
  }

  private setupEventListeners(): void {
    const input = document.getElementById("todo-input") as HTMLInputElement;
    const addBtn = document.getElementById("add-btn") as HTMLButtonElement;

    addBtn.addEventListener("click", () => this.handleAdd(input.value));
    input.addEventListener("keypress", (e) => {
      if (e.key === "Enter") {
        this.handleAdd(input.value);
      }
    });
  }

  private handleAdd(title: string): void {
    if (!title.trim()) return;

    const todo = this.todoService.create({ title: title.trim() });
    this.todoList.update(this.todoService.getAll());

    // 清空输入框
    const input = document.getElementById("todo-input") as HTMLInputElement;
    input.value = "";
  }

  private handleDelete(id: string): void {
    this.todoService.delete(id);
    this.todoList.update(this.todoService.getAll());
  }

  private handleToggle(id: string): void {
    const todo = this.todoService.getById(id);
    if (todo) {
      this.todoService.update(id, { completed: !todo.completed });
      this.todoList.update(this.todoService.getAll());
    }
  }

  private renderTodoList(): void {
    const container = this.container.querySelector(
      ".todo-list-container"
    ) as HTMLElement;
    container.appendChild(this.todoList.getElement());
  }
}

6. 入口文件

typescript
// src/index.ts
import { App } from "./app";

const root = document.getElementById("root");
if (root) {
  const app = new App(root);
  app.render();
} else {
  console.error("找不到根元素");
}

7. HTML 文件

html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>待办事项应用</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script src="./dist/index.js"></script>
  </body>
</html>

总结

TypeScript 通过添加静态类型系统,为 JavaScript 开发带来了革命性的改进。它不仅能在编译阶段捕获大量错误,还提供了更好的开发体验、更安全的重构能力和更清晰的代码文档。

TypeScript 的核心价值

  1. 类型安全:在运行前发现潜在错误
  2. 更好的 IDE 支持:自动补全、错误提示、重构工具
  3. 代码可维护性:类型作为文档,提高代码可读性
  4. 现代化特性:ES6+语法支持,更好的模块系统
  5. 渐进式采用:可以逐步在现有 JavaScript 项目中引入

学习路径建议

  1. 基础语法:掌握基本类型、接口、函数类型
  2. 高级特性:学习泛型、联合类型、交叉类型
  3. 工程化配置:熟悉 tsconfig.json、构建工具集成
  4. 最佳实践:理解类型设计、错误处理模式
  5. 生态工具:掌握 ESLint、Prettier、测试工具

TypeScript 已经成为现代前端开发的标准,掌握它不仅能提高开发效率,还能让你编写出更可靠、更易维护的代码。在接下来的学习中,我们将深入探索 TypeScript 的各个特性,帮助你成为类型系统的大师。

上次更新时间: