引言
说实话,我第一次听说TypeScript的时候是拒绝的。”JavaScript不是挺好的吗?为什么还要学一个新的语言?”这是我当时的真实想法。
但当我写过一个万行级别的JavaScript项目,被那些奇怪的类型隐式转换折磨得夜不能寐之后,我终于决定给TypeScript一个机会。结果嘛——真香。
这篇文章就是想把我从”TypeScript是什么”到”能写简单项目”的过程完整记录下来,希望你少走一些弯路。

环境搭建:5分钟启动TypeScript
安装Node.js
TypeScript运行在Node.js环境中,所以第一步是安装Node.js。
Windows/macOS用户:
直接去Node.js官网下载LTS版本,安装过程没什么坑,一路下一步就行。装完之后打开终端验证:
bash
node --version
npm --version
Linux用户:
bash
# Ubuntu/Debian
sudo apt update
sudo apt install nodejs npm
# 验证安装
node --version
npm --version
安装TypeScript编译器
有了npm之后,安装TypeScript就是一行命令的事:
bash
npm install -g typescript
验证安装:
bash
tsc --version
# 应该输出类似:Version 5.4.3
第一个TypeScript程序
创建一个文件夹,比如叫ts-hello,然后在里面新建一个文件hello.ts:
typescript
// hello.ts
function greet(name: string): string {
return `你好,${name}!欢迎学习TypeScript。`;
}
const message = greet("小明");
console.log(message);
注意这个.ts后缀,这就是TypeScript文件的标志。
然后在终端里编译它:
bash
tsc hello.ts
执行完你会发现多了一个hello.js文件。没错,TypeScript代码需要编译成JavaScript才能运行。运行编译结果:
bash
node hello.js
# 输出:你好,小明!欢迎学习TypeScript。
使用配置文件
项目里文件多了,每次手动tsc 文件名就太麻烦了。这时候需要一个tsconfig.json配置文件。
bash
tsc --init
这会在当前目录生成一个默认配置文件。打开它,你会看到很多选项,这里先关注几个常用的:
json
{
"compilerOptions": {
"target": "ES2020", // 编译目标JavaScript版本
"module": "commonjs", // 模块系统
"outDir": "./dist", // 编译输出目录
"rootDir": "./src", // 源码目录
"strict": true, // 开启严格模式(建议!)
"esModuleInterop": true, // 允许默认导入
"skipLibCheck": true // 跳过库文件检查
},
"include": ["src/**/*"], // 要编译的文件
"exclude": ["node_modules"] // 排除的文件
}
配置好之后,把你的.ts文件放到src目录,执行tsc就会自动编译所有文件到dist目录。
基础类型:TypeScript的核心
这是TypeScript最重要的地方。类型系统让代码在写的时候就能发现错误,而不是等到运行时才崩溃。
基础类型有哪些
JavaScript的那些基础类型TypeScript都支持,只是在写法上有点区别:
typescript
// 字符串
let name: string = "张三";
let greeting: string = `你好,${name}`;
// 数字
let age: number = 25;
let price: number = 99.9;
// 布尔值
let isStudent: boolean = true;
// 数组
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ["小明", "小红", "小刚"]; // 另一种写法
// 元组:固定长度和类型的数组
let person: [string, number] = ["张三", 25];
// 枚举:一组有名字的常量
enum Status {
Pending, // 0
Active, // 1
Completed // 2
}
let currentStatus: Status = Status.Active;
// 任意类型:当你真的不确定是什么类型时
let anything: any = 4;
anything = "hello";
anything = true;
// void:表示没有返回值
function logMessage(): void {
console.log("这是一条日志");
}
// null 和 undefined
let n: null = null;
let u: undefined = undefined;
类型注解和类型推断
TypeScript有两种方式确定类型:
类型注解:你手动告诉编译器这是什么类型
typescript
let count: number = 10;
let username: string = "admin";
类型推断:TypeScript根据上下文自动推断类型
typescript
let count = 10; // TypeScript推断这是number类型
let username = "admin"; // TypeScript推断这是string类型
// 后续再赋值其他类型会报错
count = "hello"; // 错误!count只能是number
实际开发中,没必要每个变量都写类型注解,TypeScript够聪明的时候会自己推断。但函数参数、返回值、复杂对象这些地方,类型注解是必要的。
接口:定义对象的形状
接口是TypeScript最强大的特性之一,它让你定义一个对象的结构:
typescript
// 定义一个用户接口
interface User {
id: number;
username: string;
email: string;
age?: number; // 可选属性
readonly createdAt: Date; // 只读属性
}
// 创建符合接口的用户对象
const user: User = {
id: 1,
username: "zhangsan",
email: "zhangsan@example.com",
age: 25,
createdAt: new Date()
};
// 缺少必填属性会报错
const badUser: User = {
id: 2,
username: "lisi"
// 错误!缺少email和createdAt
};
// 尝试修改只读属性会报错
user.createdAt = new Date(); // 错误!createdAt是只读的
接口还能定义方法:
typescript
interface Calculator {
(a: number, b: number): number; // 函数的形状
description: string;
}
const add: Calculator = (a, b) => a + b;
add.description = "加法计算器";
联合类型和交叉类型
有时候一个变量可能有多种类型,这时候用联合类型:
typescript
// 联合类型:可以是字符串或数字
let id: string | number;
id = "123"; // OK
id = 123; // OK
id = true; // 错误!布尔值不行
// 用联合类型限制函数参数
function printId(id: string | number): void {
if (typeof id === "string") {
console.log(`字符串ID: ${id.toUpperCase()}`);
} else {
console.log(`数字ID: ${id.toFixed(2)}`);
}
}
交叉类型:把多个类型合并成一个:
typescript
interface Person {
name: string;
age: number;
}
interface Employee {
company: string;
salary: number;
}
// 交叉类型:既是Person又是Employee
type Worker = Person & Employee;
const worker: Worker = {
name: "张三",
age: 30,
company: "某科技公司",
salary: 15000
};
函数:参数和返回值的类型
TypeScript对函数的类型检查非常严格,这是好事。
函数类型表达式
typescript
// 声明一个函数类型
type AddFunction = (a: number, b: number) => number;
// 实现这个函数
const add: AddFunction = (x, y) => x + y;
// 箭头函数写法
const multiply: AddFunction = (x, y) => x * y;
可选参数和默认参数
typescript
// 可选参数:参数名后面加?
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`;
}
return firstName;
}
console.log(buildName("张")); // "张"
console.log(buildName("张", "三")); // "张三"
// 默认参数:直接在参数列表赋值
function greet(name: string, greeting: string = "你好"): string {
return `${greeting},${name}!`;
}
console.log(greet("小明")); // "你好,小明!"
console.log(greet("小明", "早上好")); // "早上好,小明!"
剩余参数
typescript
// 收集剩余参数为数组
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
函数重载
同一个函数名,根据不同的参数类型返回不同类型:
typescript
// 函数重载签名
function reverse(x: string): string; // 输入字符串返回字符串
function reverse(x: number): number; // 输入数字返回数字
function reverse(x: string | number): string | number {
if (typeof x === "string") {
return x.split("").reverse().join("");
}
return Number(x.toString().split("").reverse().join(""));
}
console.log(reverse("hello")); // "olleh"
console.log(reverse(12345)); // 54321
泛型:让类型灵活起来
泛型是TypeScript最难但最有用的部分。简单说,泛型就是”类型的变量”。
为什么需要泛型
先看一个没有泛型的例子:
typescript
// 这个函数返回数组的第一个元素
function getFirst(arr: any[]): any {
return arr[0];
}
const num = getFirst([1, 2, 3]); // 返回any类型
const str = getFirst(["a", "b"]); // 返回any类型
// 调用时丢失了具体类型信息
num.toFixed(2); // 编译器不知道num是number,可能报错
用泛型重写:
typescript
// T是类型参数,在调用时自动推断
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = getFirst([1, 2, 3]); // 推断T为number
const str = getFirst(["a", "b"]); // 推断T为string
num.toFixed(2); // OK!TypeScript知道num是number
str.toUpperCase(); // OK!TypeScript知道str是string
泛型约束
有时候需要对泛型类型进行限制:
typescript
// 约束T必须有length属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(`长度是:${arg.length}`);
}
logLength("hello"); // OK,字符串有length
logLength([1, 2, 3]); // OK,数组有length
logLength({ length: 10 }); // OK,对象有length
logLength(123); // 错误!数字没有length
多类型参数
typescript
// 交换两个变量的值
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
const result = swap([1, "hello"]);
console.log(result); // ["hello", 1]
实用技巧:工程实践
类型守卫
类型守卫让你在条件判断中缩小变量类型范围:
typescript
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function speak(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow(); // TypeScript知道这是Cat
} else {
animal.bark(); // TypeScript知道这是Dog
}
}
keyof和索引类型
typescript
interface User {
id: number;
name: string;
email: string;
}
// keyof User 等于 "id" | "name" | "email"
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = {
id: 1,
name: "张三",
email: "zhangsan@example.com"
};
const userName = getProperty(user, "name"); // string
const userId = getProperty(user, "id"); // number
const userAge = getProperty(user, "age"); // 错误!"age"不在User中
工具类型
TypeScript内置了很多有用的工具类型:
typescript
interface User {
id: number;
name: string;
email: string;
}
// Partial:所有属性变为可选
type PartialUser = Partial<User>;
// 等于 { id?: number; name?: string; email?: string; }
// Required:所有属性变为必填
type RequiredUser = Required<User>;
// Pick:挑选部分属性
type UserPreview = Pick<User, "id" | "name">;
// 等于 { id: number; name: string; }
// Omit:排除部分属性
type UserWithoutEmail = Omit<User, "email">;
// 等于 { id: number; name: string; }
// Record:创建指定键值对类型
type UserMap = Record<string, User>;
const users: UserMap = {
"zhangsan": { id: 1, name: "张三", email: "zhangsan@example.com" },
"lisi": { id: 2, name: "李四", email: "lisi@example.com" }
};
// Exclude:排除联合类型中的某些类型
type Status = "pending" | "active" | "completed" | "failed";
type ActiveStatus = Exclude<Status, "pending" | "failed">;
// 等于 "active" | "completed"
实战:一个简单的任务列表
学完上面的知识,来动手写个小项目吧。
项目结构
plaintext
task-app/
├── src/
│ ├── types.ts # 类型定义
│ ├── task.ts # 任务类
│ ├── taskList.ts # 任务列表
│ └── index.ts # 入口文件
├── dist/ # 编译输出
├── tsconfig.json
└── package.json
types.ts:类型定义
typescript
// 任务状态枚举
export enum TaskStatus {
Todo = "todo",
InProgress = "in_progress",
Done = "done"
}
// 任务接口
export interface Task {
id: string;
title: string;
description?: string;
status: TaskStatus;
createdAt: Date;
completedAt?: Date;
}
// 创建任务的输入类型
export interface CreateTaskInput {
title: string;
description?: string;
}
// 更新任务的输入类型
export interface UpdateTaskInput {
title?: string;
description?: string;
status?: TaskStatus;
}
task.ts:任务类
typescript
import { Task, TaskStatus, CreateTaskInput, UpdateTaskInput } from "./types";
export class TaskItem implements Task {
id: string;
title: string;
description?: string;
status: TaskStatus;
createdAt: Date;
completedAt?: Date;
constructor(input: CreateTaskInput) {
this.id = this.generateId();
this.title = input.title;
this.description = input.description;
this.status = TaskStatus.Todo;
this.createdAt = new Date();
}
private generateId(): string {
return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
update(input: UpdateTaskInput): void {
if (input.title !== undefined) {
this.title = input.title;
}
if (input.description !== undefined) {
this.description = input.description;
}
if (input.status !== undefined) {
this.status = input.status;
if (input.status === TaskStatus.Done) {
this.completedAt = new Date();
} else {
this.completedAt = undefined;
}
}
}
toString(): string {
const statusText = {
[TaskStatus.Todo]: "待办",
[TaskStatus.InProgress]: "进行中",
[TaskStatus.Done]: "已完成"
};
return `[${statusText[this.status]}] ${this.title}`;
}
}
taskList.ts:任务列表管理
typescript
import { Task, TaskStatus, CreateTaskInput, UpdateTaskInput } from "./types";
import { TaskItem } from "./task";
export class TaskList {
private tasks: Map<string, TaskItem> = new Map();
create(input: CreateTaskInput): Task {
const task = new TaskItem(input);
this.tasks.set(task.id, task);
return task;
}
findById(id: string): Task | undefined {
return this.tasks.get(id);
}
findAll(): Task[] {
return Array.from(this.tasks.values());
}
findByStatus(status: TaskStatus): Task[] {
return this.findAll().filter(task => task.status === status);
}
update(id: string, input: UpdateTaskInput): Task | undefined {
const task = this.tasks.get(id);
if (!task) {
return undefined;
}
task.update(input);
return task;
}
delete(id: string): boolean {
return this.tasks.delete(id);
}
clear(): void {
this.tasks.clear();
}
getStats(): { total: number; todo: number; inProgress: number; done: number } {
const all = this.findAll();
return {
total: all.length,
todo: all.filter(t => t.status === TaskStatus.Todo).length,
inProgress: all.filter(t => t.status === TaskStatus.InProgress).length,
done: all.filter(t => t.status === TaskStatus.Done).length
};
}
}
index.ts:入口文件
typescript
import { TaskStatus } from "./types";
import { TaskList } from "./taskList";
// 创建任务列表实例
const taskList = new TaskList();
// 添加一些任务
const task1 = taskList.create({
title: "学习TypeScript",
description: "完成TypeScript入门教程"
});
const task2 = taskList.create({
title: "搭建项目框架",
description: "使用React + TypeScript"
});
const task3 = taskList.create({
title: "写单元测试"
});
// 更新任务状态
taskList.update(task1.id, { status: TaskStatus.InProgress });
// 打印所有任务
console.log("=== 所有任务 ===");
taskList.findAll().forEach(task => {
const taskItem = taskList.findById(task.id)!;
console.log(taskItem.toString());
});
// 打印统计
console.log("\n=== 任务统计 ===");
const stats = taskList.getStats();
console.log(`总计:${stats.total}`);
console.log(`待办:${stats.todo}`);
console.log(`进行中:${stats.inProgress}`);
console.log(`已完成:${stats.done}`);
// 删除一个任务
taskList.delete(task3.id);
console.log("\n删除任务后:", taskList.findAll().length, "个任务");
编译运行:
bash
tsc
node dist/index.js
输出:
plaintext
=== 所有任务 ===
[进行中] 学习TypeScript
[待办] 搭建项目框架
[待办] 写单元测试
=== 任务统计 ===
总计:3
待办:2
进行中:1
已完成:0
删除任务后: 2 个任务
总结
好,写到这里你应该对TypeScript有了比较完整的认识。让我简单总结一下今天学的内容:
- 环境搭建:Node.js + TypeScript编译器,5分钟搞定
- 基础类型:string、number、boolean、数组、枚举、接口
- 函数类型:参数类型、返回值类型、可选参数、默认参数、泛型
- 泛型:让类型像变量一样灵活使用
- 实用技巧:类型守卫、keyof、工具类型
- 实战项目:一个完整的任务列表应用
坦白说,TypeScript的入门门槛确实比JavaScript高一些,但一旦你习惯了写类型注解,你会发现代码质量提升了一大截——Bug少了,代码更好维护了,IDE的智能提示也更准确了。
如果你之前一直在写JavaScript,强烈建议你新项目尝试用TypeScript,或者把旧项目一点点迁移过去。不需要一步到位,可以先从简单的地方开始加类型注解,慢慢来。
关于内链方面,你可以继续学习Vue3 Composition API实战教程或者RESTful API接口设计规范,这些都是前端开发者的必备技能。
常见问题
Q:TypeScript和JavaScript有什么区别?
A:TypeScript是JavaScript的超集,添加了类型系统和面向对象的特性。TypeScript代码需要编译成JavaScript才能在浏览器或Node.js中运行。简单说,TypeScript = JavaScript + 类型系统 + 编译。
Q:TypeScript难学吗?
A:对于有JavaScript基础的开发者来说,上手TypeScript并不难。最基本的类型注解几乎不需要额外学习。最难的部分是泛型和高级类型,但这些可以在实际项目中慢慢深入。
Q:所有项目都需要用TypeScript吗?
A:不一定。小型项目、原型项目用JavaScript更灵活。但中大型项目、团队协作项目、需要长期维护的项目,用TypeScript能显著提升代码质量和可维护性。
Q:如何从JavaScript迁移到TypeScript?
A:可以把tsconfig.json中的strict设置为false,然后逐个文件把.js改成.ts。TypeScript会兼容大部分JavaScript语法,然后慢慢加上类型注解。
希望这篇教程对你有帮助。如果有问题或建议,欢迎在评论区交流!

发表回复