大白话TypeScript第三章高级特性学习
大白话TypeScript第三章高级特性学习
第三章主要是学习 TypeScript 的高级特性,这些特性会让你在编程时更加灵活和高效,就好比给你提供了更强大的工具,能让你搭建出更复杂、更精巧的“代码大厦”。下面咱们来详细说说泛型、类型断言、类型别名和交叉类型这几个特性。
1. 泛型
泛型就像是一个“万能模板”,它可以让你编写一些通用的代码,这些代码可以处理不同类型的数据,而不需要为每种类型都单独写一份代码。
想象一下,你有一个函数,它的作用是返回传入的参数,不管这个参数是什么类型。要是不用泛型,你可能就得为不同类型分别写不同的函数。但用了泛型,一个函数就能搞定所有类型。
// 定义一个泛型函数 identity,它接收一个类型参数 T
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数处理字符串类型
let output1 = identity<string>("Hello, TypeScript!");
console.log(output1);
// 使用泛型函数处理数字类型,这里可以省略 <number>,TypeScript 会自动推断类型
let output2 = identity(123);
console.log(output2);
在上面的代码中,<T>
就是泛型的类型参数,它就像一个占位符,可以代表任何类型。当你调用 identity
函数时,你可以明确指定 T
是什么类型(比如 identity<string>
),也可以让 TypeScript 自动根据传入的参数推断类型(比如 identity(123)
)。这样,同一个 identity
函数就能处理不同类型的参数了。
2. 类型断言
类型断言就像是你在告诉 TypeScript 编译器:“嘿,我知道这个变量是什么类型,你就相信我吧!”有时候,TypeScript 没办法准确知道一个变量的具体类型,但你自己心里清楚,这时候就可以用类型断言来明确指定类型。
有两种语法可以进行类型断言:一种是使用 <类型>值
的形式,另一种是使用 值 as 类型
的形式。
// 定义一个 any 类型的变量,any 类型可以代表任何类型
let someValue: any = "This is a string";
// 使用 <类型>值 的语法进行类型断言
let strLength1: number = (<string>someValue).length;
console.log(strLength1);
// 使用 值 as 类型 的语法进行类型断言
let strLength2: number = (someValue as string).length;
console.log(strLength2);
在这个例子中,someValue
被定义为 any
类型,TypeScript 并不知道它实际上是一个字符串。但我们通过类型断言告诉编译器,someValue
就是一个字符串,这样就可以安全地访问它的 length
属性了。
3. 类型别名和交叉类型
类型别名
类型别名就像是给一个类型取了个“外号”,当一个类型比较复杂或者需要多次使用时,用类型别名可以让代码更简洁、易读。
// 定义一个类型别名 Point,它代表一个包含 x 和 y 两个属性的对象类型
type Point = {
x: number;
y: number;
};
// 使用类型别名定义变量
let myPoint: Point = { x: 10, y: 20 };
console.log(myPoint);
在这个例子中,我们定义了一个类型别名 Point
,它代表一个包含 x
和 y
两个 number
类型属性的对象。然后我们就可以用 Point
来定义变量,这样代码看起来更清晰,而且如果以后这个类型需要修改,只需要在类型别名定义的地方改一次就可以了。
交叉类型
交叉类型就像是把多个类型合并成一个类型,就像把不同的拼图块拼在一起,得到一个更完整的拼图。交叉类型使用 &
符号来表示。
// 定义一个类型 Person,包含 name 和 age 属性
type Person = {
name: string;
age: number;
};
// 定义一个类型 Employee,包含 employeeId 属性
type Employee = {
employeeId: number;
};
// 定义一个交叉类型 PersonEmployee,它同时包含 Person 和 Employee 的所有属性
type PersonEmployee = Person & Employee;
// 创建一个符合 PersonEmployee 类型的对象
let personEmployee: PersonEmployee = {
name: "John",
age: 30,
employeeId: 12345
};
console.log(personEmployee);
在这个例子中,PersonEmployee
是 Person
和 Employee
的交叉类型,所以它既要有 name
和 age
属性,又要有 employeeId
属性。通过交叉类型,我们可以灵活地组合不同的类型,创建出更复杂的类型。
类型断言在什么场景下使用比较合适?
类型断言就像是你在编程时给 TypeScript 编译器“拍胸脯保证”:“我知道这个变量实际是什么类型,你就按我告诉你的来处理,出了事我负责!”下面给你详细说说在哪些场景下用类型断言比较合适,还会配上代码示例。
1. 当你从 any
类型中提取特定类型的值时
在 TypeScript 里,any
类型就像是一个“大杂烩”,它可以代表任何类型。有时候你可能会从某个地方得到一个 any
类型的变量,但你心里清楚它实际上是什么类型,这时候就可以用类型断言把它变成你想要的类型。
代码示例:
// 定义一个 any 类型的变量,它可以是任何类型的值
let someValue: any = "这是一个字符串";
// 我们知道 someValue 实际上是字符串类型,用类型断言把它当作字符串处理
let strLength: number = (someValue as string).length;
console.log(strLength);
在这个例子中,someValue
被定义为 any
类型,TypeScript 编译器并不知道它到底是什么类型。但我们自己知道它是字符串,所以使用类型断言 someValue as string
告诉编译器把它当作字符串处理,这样就能安全地访问字符串的 length
属性了。
2. 处理 DOM 元素时
当你通过 JavaScript 的 DOM 操作获取页面上的元素时,TypeScript 可能无法准确知道这个元素的具体类型。但你可以根据实际情况使用类型断言来明确元素的类型,从而调用该类型特有的方法和属性。
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<input type="text" id="myInput">
<script src="script.ts"></script>
</body>
</html>
// 通过 id 获取页面上的 input 元素
const inputElement = document.getElementById('myInput');
// 我们知道 inputElement 实际上是 HTMLInputElement 类型,用类型断言明确类型
const input = inputElement as HTMLInputElement;
// 现在可以安全地访问 HTMLInputElement 特有的属性和方法
input.value = "你好,TypeScript!";
console.log(input.value);
在这个例子中,document.getElementById('myInput')
返回的是 HTMLElement
类型,这是一个比较通用的类型。但我们知道它实际上是一个 HTMLInputElement
类型的输入框元素,所以使用类型断言 inputElement as HTMLInputElement
把它转换为 HTMLInputElement
类型,这样就可以安全地访问 value
属性来设置和获取输入框的值了。
3. 处理第三方库返回的不确定类型数据时
有些第三方库返回的数据类型可能不太明确,或者返回的是一个通用类型,但你根据文档或经验知道它实际上是什么类型,这时候也可以使用类型断言。
代码示例:
// 假设这是一个第三方库的函数,返回值类型是 any
function getSomeData(): any {
return { name: "张三", age: 20 };
}
// 我们知道返回的数据实际上是一个包含 name 和 age 属性的对象
const data = getSomeData() as { name: string; age: number };
console.log(data.name);
console.log(data.age);
在这个例子中,getSomeData
函数返回的是 any
类型,但我们知道它返回的是一个包含 name
和 age
属性的对象,所以使用类型断言把返回值转换为我们期望的类型,这样就可以直接访问 name
和 age
属性了。
不过要注意,类型断言只是告诉编译器按照你指定的类型来处理变量,它并不会改变变量的实际类型。如果你的断言是错误的,可能会在运行时出现问题,所以使用类型断言时要确保自己的判断是正确的。
类型断言和类型转换的区别
类型转换
类型转换就像是把一个东西从一种“形态”变成另一种“形态”。在编程里,它指的是把一个变量从一种数据类型变成另一种数据类型,这个过程是实实在在地改变了变量的数据类型。
比如说,你有一个整数类型的数字 5
,你想把它变成字符串类型,就可以通过类型转换把它变成 "5"
。在 JavaScript 里,有很多内置的方法可以实现类型转换,比如 String()
函数可以把其他类型转换成字符串,Number()
函数可以把其他类型转换成数字。
// 把数字 5 转换成字符串 "5"
let num = 5;
let str = String(num);
console.log(typeof str); // 输出 "string"
// 把字符串 "10" 转换成数字 10
let strNum = "10";
let realNum = Number(strNum);
console.log(typeof realNum); // 输出 "number"
在这个过程中,变量本身的数据类型发生了改变,num
原本是 number
类型,经过 String()
转换后,str
变成了 string
类型。
类型断言
类型断言就像是你给编译器“打个招呼”,告诉它:“我很清楚这个变量实际上是什么类型,你就按照我告诉你的类型来处理它吧”。但实际上,变量本身的数据类型并没有真正改变,只是在编译阶段让编译器按照你指定的类型去检查和处理代码。
比如说,有一个变量,它的类型被定义为 any
(可以是任何类型),但你心里知道它实际上是一个字符串类型,这时候你就可以使用类型断言来告诉编译器把它当作字符串来处理。
let someValue: any = "这是一个字符串";
// 使用类型断言告诉编译器 someValue 是字符串类型
let strLength = (someValue as string).length;
console.log(strLength);
这里 someValue
虽然被定义为 any
类型,但我们通过 someValue as string
这种类型断言的方式,让编译器把它当作字符串来处理,从而可以安全地访问字符串的 length
属性。但实际上 someValue
本身的数据类型并没有改变,它还是 any
类型。
两者的主要区别
对数据类型的实际影响
- 类型转换:会真正改变变量的数据类型,变量在转换后就变成了新的数据类型,后续的操作都是基于新类型进行的。
- 类型断言:不会改变变量的数据类型,只是在编译阶段影响编译器对代码的类型检查和处理,变量本身的实际类型并没有发生变化。
用途不同
- 类型转换:通常用于需要将一个数据类型转换为另一个数据类型以满足特定的操作需求,比如进行数学运算时需要把字符串转换为数字。
- 类型断言:主要用于当编译器无法准确推断变量的类型,但你自己明确知道变量的实际类型时,通过断言来绕过编译器的类型检查,让代码能够正常编译和运行。
安全性
- 类型转换:如果转换不当,可能会导致数据丢失或出现意外的结果。比如把一个非数字字符串转换为数字时,可能会得到
NaN
(Not a Number)。 - 类型断言:如果断言错误,可能会在运行时导致错误,因为类型断言只是告诉编译器按照指定类型处理,但并没有实际改变数据类型,一旦代码在运行时按照错误的类型进行操作,就会出错。所以使用类型断言时要确保自己的判断是正确的。
交叉类型的实际应用场景有哪些?
交叉类型就像是把不同的东西组合在一起,形成一个更强大、更丰富的新东西。下面给你详细说说它在实际编程里的一些应用场景。
1. 合并不同来源的配置对象
在开发软件的时候,我们经常会遇到需要配置各种参数的情况。有时候,这些配置信息来自不同的地方,每个地方提供的配置有不同的属性。这时候,交叉类型就能派上用场了,它可以把这些不同的配置对象合并成一个完整的配置对象。
比如说,你在开发一个网页应用,需要对页面的样式和交互行为进行配置。有一个基础的样式配置对象,还有一个专门针对交互的配置对象,你可以用交叉类型把它们合并成一个完整的配置对象。
// 基础样式配置类型
type StyleConfig = {
backgroundColor: string;
color: string;
};
// 交互配置类型
type InteractionConfig = {
onClick: () => void;
onHover: () => void;
};
// 合并后的完整配置类型
type FullConfig = StyleConfig & InteractionConfig;
// 定义一个完整的配置对象
const myConfig: FullConfig = {
backgroundColor: 'blue',
color: 'white',
onClick: () => console.log('Clicked!'),
onHover: () => console.log('Hovered!')
};
// 使用配置对象
function applyConfig(config: FullConfig) {
// 应用样式配置
document.body.style.backgroundColor = config.backgroundColor;
document.body.style.color = config.color;
// 绑定交互事件
document.body.addEventListener('click', config.onClick);
document.body.addEventListener('mouseover', config.onHover);
}
applyConfig(myConfig);
在这个例子里,StyleConfig
是关于样式的配置,InteractionConfig
是关于交互的配置,通过交叉类型 FullConfig
把它们合并在一起。这样,我们就可以创建一个包含所有配置信息的对象 myConfig
,然后把它传递给 applyConfig
函数,一次性应用所有的配置。
2. 实现接口的扩展与组合
当你需要创建一个新的类型,它既要包含某个接口的所有属性和方法,又要添加一些额外的属性和方法时,交叉类型就可以帮助你实现这个需求。
比如,你有一个基础的 User
接口,定义了用户的基本信息,然后你想创建一个新的类型,这个类型除了包含 User
接口的信息外,还包含一些管理员特有的信息。
// 基础用户接口
interface User {
name: string;
age: number;
}
// 管理员特有的信息接口
interface AdminPrivileges {
canManageUsers: boolean;
canDeletePosts: boolean;
}
// 管理员用户类型,是 User 和 AdminPrivileges 的交叉类型
type AdminUser = User & AdminPrivileges;
// 创建一个管理员用户对象
const admin: AdminUser = {
name: 'Alice',
age: 30,
canManageUsers: true,
canDeletePosts: true
};
// 函数用于处理管理员用户
function handleAdminUser(user: AdminUser) {
console.log(`管理员 ${user.name} 可以管理用户: ${user.canManageUsers}`);
console.log(`管理员 ${user.name} 可以删除帖子: ${user.canDeletePosts}`);
}
handleAdminUser(admin);
在这个例子中,AdminUser
是 User
和 AdminPrivileges
的交叉类型,它既包含了 User
接口的 name
和 age
属性,又包含了 AdminPrivileges
接口的 canManageUsers
和 canDeletePosts
属性。这样,我们就可以创建一个符合 AdminUser
类型的对象 admin
,并把它传递给 handleAdminUser
函数进行处理。
3. 处理第三方库的类型兼容性问题
有时候,你在使用多个第三方库时,这些库可能定义了不同的类型,但你需要把它们的功能组合在一起使用。交叉类型可以帮助你解决类型兼容性问题,让不同库的类型能够一起工作。
比如,有一个库提供了一个 Logger
类型,用于记录日志,另一个库提供了一个 Formatter
类型,用于格式化数据。你想创建一个新的对象,它既可以记录日志,又可以格式化数据。
// 日志记录器类型
type Logger = {
log: (message: string) => void;
};
// 数据格式化器类型
type Formatter = {
format: (data: any) => string;
};
// 组合类型,既可以记录日志又可以格式化数据
type LoggerFormatter = Logger & Formatter;
// 模拟一个日志记录器
const myLogger: Logger = {
log: (message) => console.log(`日志: ${message}`)
};
// 模拟一个数据格式化器
const myFormatter: Formatter = {
format: (data) => JSON.stringify(data)
};
// 合并成一个对象
const combined: LoggerFormatter = {
...myLogger,
...myFormatter
};
// 使用合并后的对象
const data = { key: 'value' };
const formattedData = combined.format(data);
combined.log(formattedData);
在这个例子中,LoggerFormatter
是 Logger
和 Formatter
的交叉类型。我们分别创建了 myLogger
和 myFormatter
对象,然后通过对象展开语法把它们合并成一个符合 LoggerFormatter
类型的对象 combined
。这样,combined
对象就既可以格式化数据,又可以记录日志了。
总的来说,交叉类型在实际编程中很有用,它可以帮助我们灵活地组合不同的类型,满足各种复杂的需求。
交叉类型和联合类型有什么区别?
在 TypeScript 里,交叉类型和联合类型是两个很实用的类型工具,但它们的作用有很大不同,下面用大白话详细讲讲它们的区别。
概念理解
交叉类型
交叉类型就像是把不同的东西拼接在一起,形成一个新的、包含所有部分的整体。用符号 &
来表示。你可以把它想象成是把多个类型的特性合并到一起,最终得到的类型要同时满足所有参与合并的类型的要求。
联合类型
联合类型则像是给变量提供了多种选择,只要满足其中一种类型就行。用符号 |
来表示。这就好比你去超市买水果,你可以选择苹果或者香蕉,只要是其中一种就符合要求。
具体区别
类型要求
- 交叉类型:要求变量同时具备所有参与交叉的类型的属性和方法。也就是说,这个变量要满足每一个类型的规定。
// 定义一个 Person 类型
type Person = {
name: string;
age: number;
};
// 定义一个 Employee 类型
type Employee = {
employeeId: number;
department: string;
};
// 交叉类型,同时具备 Person 和 Employee 的属性
type PersonEmployee = Person & Employee;
// 创建一个符合 PersonEmployee 类型的对象
const personEmp: PersonEmployee = {
name: '张三',
age: 30,
employeeId: 123,
department: '技术部'
};
在这个例子中,PersonEmployee
类型是 Person
和 Employee
的交叉类型,所以 personEmp
对象必须同时有 name
、age
、employeeId
和 department
这些属性。
- 联合类型:变量只需要满足参与联合的类型中的某一种就行。
// 定义一个联合类型,可以是 string 或者 number
type StringOrNumber = string | number;
// 下面两个赋值都是合法的
let value1: StringOrNumber = 'hello';
let value2: StringOrNumber = 123;
这里 StringOrNumber
是 string
和 number
的联合类型,value1
是 string
类型,value2
是 number
类型,它们都符合 StringOrNumber
这个联合类型的要求。
类型兼容性
- 交叉类型:只有当一个对象包含了交叉类型中所有类型的属性和方法时,它才和这个交叉类型兼容。
// 定义一个函数,接收 PersonEmployee 类型的参数
function printPersonEmployee(person: PersonEmployee) {
console.log(`${person.name},年龄:${person.age},员工编号:${person.employeeId},部门:${person.department}`);
}
// 下面这个对象因为缺少 Employee 的属性,不兼容 PersonEmployee 类型
// const invalidPersonEmp = { name: '李四', age: 25 };
// printPersonEmployee(invalidPersonEmp); // 会报错
// 这个对象同时具备 Person 和 Employee 的属性,兼容 PersonEmployee 类型
const validPersonEmp: PersonEmployee = {
name: '李四',
age: 25,
employeeId: 456,
department: '销售部'
};
printPersonEmployee(validPersonEmp);
- 联合类型:只要一个对象的类型是联合类型中的某一种,它就和这个联合类型兼容。
// 定义一个函数,接收 StringOrNumber 类型的参数
function printValue(value: StringOrNumber) {
if (typeof value === 'string') {
console.log(`这是一个字符串:${value}`);
} else {
console.log(`这是一个数字:${value}`);
}
}
printValue('你好'); // 传入 string 类型,兼容 StringOrNumber 类型
printValue(789); // 传入 number 类型,也兼容 StringOrNumber 类型
使用场景
- 交叉类型:常用于合并多个类型的特性,比如在合并不同来源的配置对象、扩展接口等场景中很有用。
- 联合类型:当一个变量可能有多种不同的类型时使用,比如函数的参数可能接收不同类型的值,或者一个变量在不同情况下可能是不同的类型。
总之,交叉类型强调的是“都要有”,联合类型强调的是“选其一”。在实际编程中,根据具体的需求来选择使用交叉类型还是联合类型。
除了上面提到的方法,还有哪些实践项目可以帮助巩固 TypeScript 的基础?
以下这些实践项目也能有效帮助你巩固 TypeScript 的基础:
游戏类项目
1. 猜数字游戏
- 功能描述:程序随机生成一个 1 - 100 之间的数字,玩家通过输入猜测的数字来尝试猜出这个随机数。程序会根据玩家的输入给出“猜大了”“猜小了”或“猜对了”的提示,直到玩家猜对为止。
- 巩固要点:运用条件判断语句来处理不同的猜测结果,使用循环结构让玩家可以多次猜测,同时掌握随机数生成和用户输入的处理。
- 示例代码片段:
import readline from 'readline';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const randomNumber = Math.floor(Math.random() * 100) + 1;
function guessNumber() {
rl.question('请输入一个 1 - 100 之间的数字: ', (input) => {
const guess = parseInt(input);
if (isNaN(guess) || guess < 1 || guess > 100) {
console.log('输入无效,请输入 1 - 100 之间的数字。');
guessNumber();
} else if (guess < randomNumber) {
console.log('猜小了,再试试!');
guessNumber();
} else if (guess > randomNumber) {
console.log('猜大了,再试试!');
guessNumber();
} else {
console.log('猜对了,恭喜你!');
rl.close();
}
});
}
guessNumber();
2. 井字棋游戏
- 功能描述:这是一个经典的两人对战游戏,在一个 3x3 的棋盘上,玩家轮流在空格中放置自己的标记(通常是 X 或 O),先在横、竖或对角线上连成三个标记的玩家获胜。如果棋盘填满且没有玩家获胜,则游戏平局。
- 巩固要点:使用二维数组来表示棋盘,实现棋盘状态的更新和显示,运用条件判断来检查是否有玩家获胜或平局,以及处理玩家的输入。
- 示例代码片段:
const board: string[][] = [
[' ', ' ', ' '],
[' ', ' ', ' '],
[' ', ' ', ' ']
];
let currentPlayer = 'X';
function printBoard() {
for (let i = 0; i < 3; i++) {
console.log(board[i].join(' | '));
if (i < 2) {
console.log('---------');
}
}
}
function makeMove(row: number, col: number) {
if (board[row][col] === ' ') {
board[row][col] = currentPlayer;
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
return true;
}
return false;
}
// 这里省略了检查获胜和平局的逻辑以及用户输入处理部分
数据可视化类项目
1. 简单柱状图生成器
- 功能描述:根据用户输入的数据,生成一个简单的文本形式的柱状图。用户输入一组数据,程序将这些数据以柱状图的形式展示出来,柱子的高度与数据的值成正比。
- 巩固要点:学会处理数组数据,使用循环结构来生成柱状图的图形表示,掌握基本的字符串拼接和格式化输出。
- 示例代码片段:
function generateBarChart(data: number[]) {
const maxValue = Math.max(...data);
for (let i = 0; i < data.length; i++) {
const barLength = Math.round((data[i] / maxValue) * 20);
const bar = '*'.repeat(barLength);
console.log(`${i + 1}: ${bar} (${data[i]})`);
}
}
const data = [10, 20, 15, 30];
generateBarChart(data);
2. 成绩折线图模拟
- 功能描述:模拟展示学生的成绩变化情况,使用数组存储学生不同时期的成绩,然后将这些成绩绘制成简单的折线图(文本形式)。
- 巩固要点:处理数组中的数据,使用循环和条件判断来绘制折线图的各个部分,理解数据的趋势和变化的表示方法。
- 示例代码片段:
function drawLineChart(scores: number[]) {
const maxScore = Math.max(...scores);
for (let y = maxScore; y >= 0; y--) {
let line = '';
for (let x = 0; x < scores.length; x++) {
if (scores[x] === y) {
line += 'o ';
} else if (scores[x] > y) {
line += '| ';
} else {
line += ' ';
}
}
console.log(line);
}
console.log('-'.repeat(scores.length * 2));
}
const scores = [70, 80, 65, 90];
drawLineChart(scores);
小型服务器类项目
1. 简单的文件服务器
- 功能描述:使用 Node.js 和 TypeScript 搭建一个简单的 HTTP 服务器,该服务器可以根据用户的请求返回指定目录下的文件内容。例如,用户访问
http://localhost:3000/index.html
,服务器将返回index.html
文件的内容。 - 巩固要点:学习使用 Node.js 的
http
模块创建服务器,处理 HTTP 请求和响应,掌握文件系统操作,如读取文件内容。 - 示例代码片段:
import http from 'http';
import fs from 'fs';
import path from 'path';
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, 'public', req.url || 'index.html');
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('File not found');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
}
});
});
const port = 3000;
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
2. 简单的 API 服务器
- 功能描述:创建一个简单的 RESTful API 服务器,提供一些基本的接口,如获取数据列表、根据 ID 获取单个数据等。可以使用内存数组来模拟数据库,存储和管理数据。
- 巩固要点:理解 RESTful API 的设计原则,处理不同类型的 HTTP 请求(如 GET、POST、PUT、DELETE),使用 JSON 格式进行数据的传输和处理。
- 示例代码片段:
import http from 'http';
const data = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
];
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/api/items') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
} else if (req.method === 'GET' && req.url?.startsWith('/api/items/')) {
const id = parseInt(req.url.split('/')[3]);
const item = data.find(item => item.id === id);
if (item) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(item));
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Item not found');
}
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not found');
}
});
const port = 3001;
server.listen(port, () => {
console.log(`API server is running on port ${port}`);
});
通过这些项目的实践,你可以更深入地理解 TypeScript 的各种特性和应用场景,进一步巩固基础知识。