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); // NaNtypescript
// 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应该是string2. 增强的 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 --init2. 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-tsVite 项目已经配置好了 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-pluginjavascript
// 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 插件
- TypeScript Importer:自动导入模块
- TypeScript Hero:类型检查和重构工具
- Auto Rename Tag:自动重命名标签
- Prettier:代码格式化
- 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.json2. 定义数据模型
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 的核心价值
- 类型安全:在运行前发现潜在错误
- 更好的 IDE 支持:自动补全、错误提示、重构工具
- 代码可维护性:类型作为文档,提高代码可读性
- 现代化特性:ES6+语法支持,更好的模块系统
- 渐进式采用:可以逐步在现有 JavaScript 项目中引入
学习路径建议
- 基础语法:掌握基本类型、接口、函数类型
- 高级特性:学习泛型、联合类型、交叉类型
- 工程化配置:熟悉 tsconfig.json、构建工具集成
- 最佳实践:理解类型设计、错误处理模式
- 生态工具:掌握 ESLint、Prettier、测试工具
TypeScript 已经成为现代前端开发的标准,掌握它不仅能提高开发效率,还能让你编写出更可靠、更易维护的代码。在接下来的学习中,我们将深入探索 TypeScript 的各个特性,帮助你成为类型系统的大师。