第四部分:赋予网页健壮的灵魂 —— TypeScript(中)
目录
- 4 类与面向对象:构建复杂的组件
- 4.1 类的定义与成员
- 4.2 继承 (Inheritance)
- 4.3 接口实现 (Implements)
- 4.4 抽象类 (Abstract Class)
- 4.5 静态成员 (Static Members)
- 5 更高级的类型:让类型系统更灵活
- 5.1 联合类型 (`|`)
- 5.2 交叉类型 (`&`)
- 5.3 字面量类型 (Literal Types)
- 5.4 枚举 (`enum`)
- 5.5 `any` vs `unknown`
- 5.6 `void` 和 `never`
- 5.7 类型断言 (`as` 或 `<Type>`)
- 5.8 练习
- 6 模块化与工具链:组织代码和提升效率
- 6.1 模块 (`import` 和 `export`)
- 6.2 编译模块化的 TypeScript 代码
- 6.3 模块打包工具 (Bundlers)
- 6.4 练习
4 类与面向对象:构建复杂的组件
在复杂的智能家居系统中,我们不会把所有功能都写在一起,而是会设计独立的模块,比如照明模块、安防模块、娱乐模块等。每个模块有自己的内部工作原理(属性和方法),对外提供特定的操作接口。面向对象编程 (OOP) 和 TypeScript 的类就是用来实现这种模块化设计的强大工具。
类 (Class) 是创建对象的蓝图或模板。它封装了数据(属性)和操作数据的方法。TypeScript 为 JavaScript 的类添加了类型系统和更强的可见性控制。
4.1 类的定义与成员
// 04.ts// 定义一个表示灯的类
class Light {// 属性 (成员变量)// 默认是 public,表示可以在类的外部访问brightness: number;// private 成员只能在类内部访问,就像灯内部的电路细节不对外暴露private isOn: boolean;// protected 成员可以在类内部和其子类中访问,就像一个家族内部的秘密配方protected maxBrightness: number = 100;// 只读属性,只能在构造函数中初始化readonly id: string;// 构造函数:在创建对象时执行,用于初始化属性constructor(initialBrightness: number = 0, id: string) {this.brightness = initialBrightness;this.isOn = initialBrightness > 0;this.id = id;console.log(`灯 ${this.id} 已创建,初始亮度: ${this.brightness}`);}// 方法 (成员函数)// public 方法,可以在类的外部调用turnOn(): void {if (!this.isOn) {this.isOn = true;console.log(`灯 ${this.id} 打开了.`);this.brightness = 50; // 打开时设置一个默认亮度}}turnOff(): void {if (this.isOn) {this.isOn = false;console.log(`灯 ${this.id} 关闭了.`);this.brightness = 0;}}// private 方法,只能在类内部调用private checkStatus(): void {console.log(`灯 ${this.id} 当前状态: ${this.isOn ? '开' : '关'}`);}// public 方法,内部调用 private 方法reportStatus(): void {this.checkStatus();console.log(`当前亮度: ${this.brightness}`);}// 访问 protected 属性get maxLevel(): number {return this.maxBrightness;}
}// 创建 Light 对象 (实例化)
const livingRoomLight = new Light(0, "LR1");
const kitchenLight = new Light(30, "K1");livingRoomLight.turnOn();
livingRoomLight.reportStatus(); // 调用 public 方法,内部使用了 private 方法// 尝试访问 private 或 protected 成员会报错
// console.log(livingRoomLight.isOn); // 编译时报错:Property 'isOn' is private and only accessible within class 'Light'.
// console.log(livingRoomLight.maxBrightness); // 编译时报错:Property 'maxBrightness' is protected and only accessible within class 'Light' and its subclasses.// 尝试修改 readonly 属性会报错 (在构造函数外)
// livingRoomLight.id = "new-id"; // 编译时报错:Cannot assign to 'id' because it is a read-only property.console.log("客厅灯最大亮度:", livingRoomLight.maxLevel); // 通过 public getter 访问 protected 属性
4.2 继承 (Inheritance)
子类可以继承父类的属性和方法,并可以添加自己的新属性或方法,或者覆盖父类的方法。这就像在现有的基础灯具(父类)上,开发出更高级的智能灯具(子类),它保留了基本功能,但也增加了颜色调节、定时开关等新功能。
// 04.ts (接着上面的代码)// 定义一个继承自 Light 的智能灯类
class SmartLight extends Light {color: string;constructor(initialBrightness: number = 0, id: string, initialColor: string = "白色") {// 调用父类的构造函数,必须是子类构造函数中的第一行super(initialBrightness, id);this.color = initialColor;console.log(`智能灯 ${this.id} 已创建,颜色: ${this.color}`);}// 子类可以添加新的方法setColor(color: string): void {this.color = color;console.log(`灯 ${this.id} 颜色设置为: ${this.color}`);}// 子类可以覆盖父类的方法 (方法重写)reportStatus(): void {// 可以调用父类的同名方法super.reportStatus();console.log(`当前颜色: ${this.color}`);// 子类可以访问父类的 protected 属性console.log(`智能灯最大亮度限制: ${this.maxBrightness}`);}// 尝试访问 private 成员会报错// checkStatus() // 编译时报错:Property 'checkStatus' is private and only accessible within class 'Light'.
}const bedroomSmartLight = new SmartLight(10, "BR1", "蓝色");
bedroomSmartLight.turnOn();
bedroomSmartLight.setColor("红色");
bedroomSmartLight.reportStatus(); // 调用子类重写后的方法
4.3 接口实现 (Implements)
接口不仅可以描述对象的形状,还可以用来约束类的行为。一个类可以声明它“实现”了某个接口,这意味着这个类必须提供接口中定义的所有属性和方法。这就像一个智能设备必须遵循某个行业标准(接口),才能接入到整个智能家居系统中。
// 04.ts (接着上面的代码)// 定义一个接口,描述可以控制电源的设备
interface PowerControllable {powerOn(): void;powerOff(): void;isOn(): boolean;
}// 定义一个表示智能插座的类,实现 PowerControllable 接口
class SmartOutlet implements PowerControllable {private deviceName: string;private status: boolean;constructor(name: string) {this.deviceName = name;this.status = false; // 默认关闭}powerOn(): void {if (!this.status) {this.status = true;console.log(`${this.deviceName} 已通电.`);}}powerOff(): void {if (this.status) {this.status = false;console.log(`${this.deviceName} 已断电.`);}}isOn(): boolean {return this.status;}// 类可以有接口之外的其他成员getDeviceName(): string {return this.deviceName;}
}const deskFanOutlet = new SmartOutlet("台扇插座");
deskFanOutlet.powerOn();
console.log(`${deskFanOutlet.getDeviceName()} 状态: ${deskFanOutlet.isOn() ? '开' : '关'}`);
deskFanOutlet.powerOff();// 一个类可以实现多个接口
interface SettableTimer {setTimer(minutes: number): void;
}// 假设有一个既可以控制电源,又可以设置定时的智能设备
class SmartDevice implements PowerControllable, SettableTimer {// ... 实现 PowerControllable 和 SettableTimer 的所有方法deviceName: string;status: boolean;timerMinutes: number = 0;constructor(name: string) {this.deviceName = name;this.status = false;}powerOn(): void { /* ... */ console.log(`${this.deviceName} 通电`); this.status = true; }powerOff(): void { /* ... */ console.log(`${this.deviceName} 断电`); this.status = false; }isOn(): boolean { return this.status; }setTimer(minutes: number): void {this.timerMinutes = minutes;console.log(`${this.deviceName} 设置定时 ${minutes} 分钟`);}
}
4.4 抽象类 (Abstract Class)
抽象类不能直接实例化,只能作为其他类的父类。它可能包含抽象方法(只有方法签名,没有实现)和具体方法(有实现)。子类必须实现父类中的所有抽象方法。这就像一个“通用电器”的设计概念,它定义了所有电器都应该有的基本操作(比如打开/关闭),但具体如何实现这些操作(比如是灯亮还是风扇转)由具体的子类(灯类、风扇类)来完成。
// 04.ts (接着上面的代码)// 定义一个抽象的智能设备类
abstract class AbstractSmartDevice {abstract deviceType: string; // 抽象属性,子类必须提供protected status: boolean = false;abstract turnOn(): void; // 抽象方法,子类必须实现abstract turnOff(): void; // 抽象方法,子类必须实现// 具体方法getStatus(): boolean {return this.status;}// 构造函数constructor(protected id: string) { // protected 参数属性,会创建一个同名的 protected 属性console.log(`抽象设备 ${this.id} 已创建`);}
}// 不能直接创建抽象类的实例
// const myDevice = new AbstractSmartDevice("abc"); // 编译时报错:Cannot create an instance of an abstract class.// 继承抽象类并实现抽象成员
class ConcreteSmartLight extends AbstractSmartDevice {deviceType: string = "智能灯"; // 实现抽象属性constructor(id: string, private brightness: number = 0) {super(id);}turnOn(): void { // 实现抽象方法this.status = true;this.brightness = 50;console.log(`智能灯 ${this.id} 打开,亮度 ${this.brightness}`);}turnOff(): void { // 实现抽象方法this.status = false;this.brightness = 0;console.log(`智能灯 ${this.id} 关闭`);}setBrightness(level: number): void {if (this.status) {this.brightness = level;console.log(`智能灯 ${this.id} 亮度设置为 ${this.brightness}`);} else {console.log(`智能灯 ${this.id} 已关闭,无法调节亮度`);}}
}const hallwayLight = new ConcreteSmartLight("H1");
hallwayLight.turnOn();
hallwayLight.setBrightness(80);
console.log("走廊灯状态:", hallwayLight.getStatus());
hallwayLight.turnOff();
4.5 静态成员 (Static Members)
类的属性和方法默认是属于类实例的(即创建对象后才能访问)。但有时候,有些属性或方法是属于类本身,而不是任何特定的实例,可以使用 static
关键字。这就像智能家居系统的某个全局配置参数,或者一个用于创建所有设备实例的工厂方法,它们不依赖于某个具体的设备。
// 04.ts (接着上面的代码)class DeviceManager {// 静态属性:所有设备共用的计数器static deviceCount: number = 0;// 静态方法:用于注册新设备static registerDevice(): void {DeviceManager.deviceCount++; // 在静态方法中访问静态属性console.log(`新设备已注册,当前共有 ${DeviceManager.deviceCount} 个设备.`);}constructor() {DeviceManager.registerDevice(); // 在构造函数中调用静态方法 (不太常见,但可行)}
}// 访问静态属性和方法,无需创建类的实例
DeviceManager.registerDevice(); // 输出:新设备已注册,当前共有 1 个设备.
DeviceManager.registerDevice(); // 输出:新设备已注册,当前共有 2 个设备.console.log("总设备数:", DeviceManager.deviceCount); // 输出:总设备数: 2// 创建类实例时也可以触发静态方法的调用 (如果构造函数里调用了)
const device1 = new DeviceManager(); // 输出:新设备已注册,当前共有 3 个设备.
const device2 = new DeviceManager(); // 输出:新设备已注册,当前共有 4 个设备.// 尝试通过实例访问静态成员会报错
// console.log(device1.deviceCount); // 编译时报错:Property 'deviceCount' is a static member of type 'DeviceManager'.
// device1.registerDevice(); // 编译时报错:Property 'registerDevice' is a static member of type 'DeviceManager'.
编译 04.ts
:
tsc --project 04-typescript/tsconfig.json
会生成 04.js
文件。然后在 HTML 中引入 04.js
。
执行后的效果
小结: 类和面向对象编程是构建复杂应用的重要范式。TypeScript 为类添加了强大的类型和可见性控制,支持继承和接口实现,使得代码结构更清晰,更易于管理和扩展,就像有了更标准化的智能家居模块。
练习:
- 定义一个基类
Vehicle
,包含属性speed
(number) 和方法move()
。 - 创建一个子类
Car
继承自Vehicle
,添加属性brand
(string),并重写move()
方法,使其输出汽车正在移动。 - 定义一个接口
Drawable
,包含方法draw(): void
。 - 创建一个类
Circle
,实现Drawable
接口,并在draw
方法中输出绘制圆形的信息。 - 修改
Light
类,使其brightness
属性默认是protected
。创建一个继承自Light
的DimmingLight
类,添加一个setBrightness(level: number)
方法,该方法可以访问父类的brightness
属性来调节亮度。
5 更高级的类型:让类型系统更灵活
TypeScript 的类型系统非常灵活,不仅限于基本类型和固定结构。它提供了多种方式来描述数据可能存在的多种状态或组合,这就像智能家居系统中的一个设备可能有多重模式(联合类型),或者需要同时满足多个功能规范(交叉类型)。
5.1 联合类型 (|
)
联合类型表示一个变量可以持有多种类型中的任意一种。这就像一根电线,有时候用来传输数据信号,有时候用来传输控制信号,但它总归是这两种中的一种。
// 05.ts// 一个变量可以是 string 或 number
let status: string | number;status = "在线"; // 正确
status = 1; // 正确// status = true; // 编译时报错:Type 'boolean' is not assignable to type 'string | number'.// 函数参数也可以是联合类型
function printId(id: number | string): void {console.log(`ID: ${id}`);// 在使用联合类型的变量时,需要进行类型检查 (类型缩小)if (typeof id === 'string') {// 在这个块里,id 被缩小为 string 类型console.log(id.toUpperCase());} else {// 在这个块里,id 被缩小为 number 类型console.log(id.toFixed(2)); // number 类型的方法}
}printId(101);
printId("abc-123");
// printId(true); // 编译时报错
5.2 交叉类型 (&
)
交叉类型表示一个类型是多种类型的组合,它必须同时满足所有类型的要求。这就像一个设备既是照明设备,又是安防设备,它必须同时具备照明和安防的所有功能。
// 05.ts (接着上面的代码)interface Switchable {turnOn(): void;turnOff(): void;
}interface Dimmable {setBrightness(level: number): void;
}// 交叉类型:同时具备 Switchable 和 Dimmable 的能力
type SmartLamp = Switchable & Dimmable;// 创建一个对象,它必须实现 Switchable 和 Dimmable 的所有方法
const mySmartLamp: SmartLamp = {turnOn: () => console.log("灯打开"),turnOff: () => console.log("灯关闭"),setBrightness: (level) => console.log(`亮度设置为 ${level}`)
};mySmartLamp.turnOn();
mySmartLamp.setBrightness(75);
mySmartLamp.turnOff();
5.3 字面量类型 (Literal Types)
字面量类型允许你指定变量的值只能是一个特定的字符串、数字或布尔值。这就像一个开关,它的状态只能是“开”或“关”,不能是其他任何值。
// 05.ts (接着上面的代码)// 变量的值只能是 "success", "error", "loading" 中的一个
let apiStatus: "success" | "error" | "loading";apiStatus = "success"; // 正确
apiStatus = "loading"; // 正确// apiStatus = "done"; // 编译时报错:Type '"done"' is not assignable to type '"success" | "error" | "loading"'.// 函数参数或返回值也可以是字面量类型
function setDirection(direction: "up" | "down" | "left" | "right"): void {console.log(`移动方向: ${direction}`);
}setDirection("up");
// setDirection("forward"); // 编译时报错
5.4 枚举 (enum
)
枚举允许你定义一组命名的常量。这对于表示一组相关的、有限的取值非常有用,比如星期几、交通灯颜色、订单状态等。它们让代码更易读。这就像给智能家居系统的各种工作模式定义了清晰的名称,而不是用数字或字符串的魔术值。
// 05.ts (接着上面的代码)// 数字枚举 (默认从 0 开始)
enum Direction {Up, // 0Down, // 1Left, // 2Right // 3
}let playerDirection: Direction = Direction.Up;
console.log("玩家方向 (数字):", playerDirection); // 输出 0
console.log("玩家方向 (名称):", Direction[playerDirection]); // 输出 "Up"// 可以指定起始值
enum StatusCode {Success = 200,NotFound = 404,InternalError = 500
}let responseStatus: StatusCode = StatusCode.Success;
console.log("响应状态码:", responseStatus); // 输出 200// 字符串枚举 (推荐,可读性更好)
enum ApiStatus {Fetching = "FETCHING",Success = "SUCCESS",Error = "ERROR"
}let currentApiStatus: ApiStatus = ApiStatus.Fetching;
console.log("当前API状态:", currentApiStatus); // 输出 "FETCHING"
5.5 any
vs unknown
any
: 表示可以是任何类型。使用any
会关闭 TypeScript 的类型检查,回到原生 JavaScript 的状态。谨慎使用,它破坏了 TypeScript 提供的安全性。就像一根没有标签的电线,你不知道它是传输什么信号的,使用时非常危险。unknown
: 表示未知类型。与any
不同的是,使用unknown
类型的变量之前,你必须先进行类型检查或类型断言,才能对其进行操作。这就像一根你不知道用途的电线,在使用它之前,你必须先用仪表测试清楚它的类型。
// 05.ts (接着上面的代码)let data: any = 123;
data = "hello";
data = true;
data.toFixed(2); // 不会报错,any 类型不做检查
data.toUpperCase(); // 不会报错,any 类型不做检查let dataUnknown: unknown = "hello world";// 尝试直接对 unknown 类型进行操作会报错
// dataUnknown.toUpperCase(); // 编译时报错:'dataUnknown' is of type 'unknown'.// 必须先进行类型检查或断言
if (typeof dataUnknown === 'string') {console.log(dataUnknown.toUpperCase()); // 在这个块里,dataUnknown 被缩小为 string
}// 或者使用类型断言 (后面会讲)
// console.log((dataUnknown as string).toUpperCase());
5.6 void
和 never
void
: 表示函数没有返回值。这是最常见的,比如只执行某个操作而不返回结果的函数。never
: 表示函数永远不会返回结果。这通常用于会抛出错误或包含无限循环的函数。这就像一个警报系统,一旦触发就进入一个不可停止的状态。
// 05.ts (接着上面的代码)// 没有返回值的函数
function logAction(action: string): void {console.log(`执行动作: ${action}`);
}logAction("初始化系统");// 永远不会返回的函数 (例如,抛出错误)
function throwError(message: string): never {throw new Error(message);
}// 永远不会返回的函数 (例如,无限循环)
// function infiniteLoop(): never {
// while (true) {
// // ...
// }
// }// 调用会抛出错误的函数
// try {
// throwError("出错了!");
// } catch (e) {
// console.error("捕获到错误:", e.message);
// }
5.7 类型断言 (as
或 <Type>
)
有时候,你比 TypeScript 更清楚一个变量的实际类型。类型断言就是告诉编译器,“相信我,这个变量就是这个类型”。这就像你看到一根没有标签的电线,但根据经验你确定它是电源线,你就可以“断言”它是电源线。
重要提示: 类型断言只在编译阶段起作用,不会改变变量的实际运行时类型或执行任何额外的检查。如果断言错误,可能会导致运行时错误。谨慎使用!
有两种语法:
value as Type
(JSX 中推荐使用)<Type>value
(在.tsx
文件中与 JSX 语法冲突,不推荐)
// 05.ts (接着上面的代码)let someValue: unknown = "这是个字符串";// 使用 as 进行类型断言
let strLength1: number = (someValue as string).length;
console.log("字符串长度 (as):", strLength1);// 使用 <Type> 进行类型断言 (在 tsx 文件中可能与 JSX 冲突)
let strLength2: number = (<string>someValue).length;
console.log("字符串长度 (<Type>):", strLength2);// 危险的断言:如果 someValue 实际上不是字符串
let anotherValue: unknown = 123;
// let dangerousLength: number = (anotherValue as string).length; // 编译时不会报错,但运行时会出错!// 获取 DOM 元素时经常用到类型断言
// const element = document.getElementById("my-canvas") as HTMLCanvasElement;
// // 现在 TypeScript 知道 element 是一个 HTMLCanvasElement 类型,可以使用其特有的属性和方法
// const ctx = element.getContext("2d");
编译 05.ts
:
tsc --project 04-typescript/tsconfig.json
会生成 05.js
文件。然后在 HTML 中引入 05.js
。
执行后的效果
小结: 联合类型、交叉类型、字面量类型、枚举等高级类型使得 TypeScript 能够更精确地描述复杂的数据结构和取值范围。理解 any
和 unknown
的区别以及何时使用类型断言是编写安全 TypeScript 代码的关键。
5.8 练习
- 定义一个联合类型
ContactInfo
,可以是string
(邮箱) 或number
(电话号码)。声明一个变量myContact
为此类型,并分别赋值一个邮箱地址和电话号码。 - 定义一个接口
HasArea
,包含方法getArea(): number
。定义一个接口HasPerimeter
,包含方法getPerimeter(): number
。定义一个交叉类型Shape
,它是HasArea
和HasPerimeter
的组合。创建一个对象,实现Shape
类型。 - 定义一个字符串字面量类型
TrafficLightColor
,它的值只能是"Red"
,"Yellow"
,"Green"
中的一个。编写一个函数changeLight(color: TrafficLightColor): void
。 - 定义一个数字枚举
LogLevel
,包含Debug = 0
,Info = 1
,Warning = 2
,Error = 3
。编写一个函数logMessage(level: LogLevel, message: string): void
,根据日志级别输出信息。
6 模块化与工具链:组织代码和提升效率
随着项目代码量的增加,我们不可能把所有代码都放在一个文件里。模块化是将代码分割成独立、可复用的文件的方式,这就像智能家居系统中的各个房间都有独立的电路箱和布线,互不干扰但又能通过主干线连接。TypeScript 完全支持 ES Modules (import
/export
)。
同时,TypeScript 的强大之处在于它的编译器 tsc
和丰富的配置选项,以及与现代前端工具链的集成。
6.1 模块 (import
和 export
)
在 TypeScript 中,每个 .ts
文件默认就是一个模块。你可以使用 export
关键字导出模块中的变量、函数、类、接口等,然后使用 import
关键字在其他模块中引入它们。
创建两个文件:utils.ts
和 main.ts
。
utils.ts
:
// utils.ts - 一个工具函数模块// 导出常量
export const PI = 3.14159;// 导出函数
export function add(x: number, y: number): number {return x + y;
}// 导出接口
export interface Config {apiUrl: string;timeout: number;
}// 导出类
export class Logger {log(message: string): void {console.log(`[日志] ${message}`);}
}// 默认导出 (一个模块只能有一个默认导出)
export default class AppSettings {theme: string = 'light';
}
main.ts
:
// main.ts - 主应用模块// 导入单个或多个导出
import { PI, add, Logger, Config } from './utils'; // 注意文件后缀省略// 导入默认导出 (可以取任意名字)
import Settings from './utils'; // 注意这里没有大括号// 导入模块中的所有导出,并将其放入一个命名空间对象中
import * as Utils from './utils';console.log("PI:", PI);
console.log("2 + 3 =", add(2, 3));const appLogger = new Logger();
appLogger.log("应用启动");const appConfig: Config = {apiUrl: "http://api.example.com",timeout: 5000
};
console.log("应用配置:", appConfig);const settings = new Settings();
console.log("应用设置主题:", settings.theme);// 访问命名空间中的导出
console.log("Utils.PI:", Utils.PI);
6.2 编译模块化的 TypeScript 代码
直接使用 tsc main.ts
编译可能会遇到问题,因为浏览器默认不支持 ES Modules 语法(需要特定的 <script type="module">
标签,并且文件路径和扩展名有要求)。更常见的做法是使用 tsconfig.json
配置文件来管理整个项目的编译设置。
tsconfig.json
文件:
这个文件位于项目的根目录,用来告诉 tsc
编译器如何编译你的 TypeScript 项目。
初始化 tsconfig.json
: 在项目根目录运行命令:
tsc --init
这会生成一个带有大量注释的 tsconfig.json
文件。一些重要的配置项:
"compilerOptions"
: 编译选项的核心配置对象。"target"
: 指定编译后的 JavaScript 版本 (如 “es5”, “es6”, “es2020” 等)。"module"
: 指定生成的模块系统 (如 “commonjs”, “es2015”, “esnext” 等)。对于现代 Web 开发,“es2015” 或 “esnext” 配合模块打包工具更常用。"outDir"
: 指定编译后 JavaScript 文件的输出目录。"rootDir"
: 指定 TypeScript 源文件的根目录。"strict"
: 启用所有严格的类型检查选项(强烈推荐开启!)。"esModuleInterop"
: 允许使用 ES Modules 语法导入 CommonJS 模块。"forceConsistentCasingInFileNames"
: 强制文件名大小写一致。"skipLibCheck"
: 跳过声明文件的类型检查,提高编译速度。
"include"
: 指定需要编译的文件或目录(默认是当前目录及子目录下的所有.ts
文件)。"exclude"
: 指定不需要编译的文件或目录(如node_modules
)。
修改 tsconfig.json
示例:
{"compilerOptions": {"target": "es2018", // 编译到较新版本的 JS,现代浏览器支持"module": "esnext", // 使用最新的 ES Module 语法"outDir": "./dist", // 编译输出到 dist 目录"rootDir": "./src", // TypeScript 源文件在 src 目录"strict": true, // 开启所有严格检查"esModuleInterop": true,"forceConsistentCasingInFileNames": true,"skipLibCheck": true},"include": ["src/**/*.ts" // 包含 src 目录下所有子目录中的 .ts 文件],"exclude": ["node_modules"]
}
现在,将你的 utils.ts
和 main.ts
文件移到 src
目录下。然后在项目根目录运行 tsc
命令(不带文件名):
tsc
tsc
命令会自动查找 tsconfig.json
文件并按照其中的配置编译项目。编译后的 .js
文件会输出到 ./dist
目录。
6.3 模块打包工具 (Bundlers)
虽然 tsc
可以编译 TypeScript 模块,但生成的 ES Module 文件在浏览器中直接使用可能需要 <script type="module">
标签,并且处理复杂的依赖关系(比如导入 node_modules
中的库)并不方便。
现代前端开发通常会使用模块打包工具 (Bundlers),如 Webpack, Parcel, Vite 等。这些工具可以处理模块间的依赖关系,将多个模块打包成一个或少数几个优化过的 JavaScript 文件,并通常内置或容易配置对 TypeScript 的支持。
模块打包工具会读取你的入口文件(例如 dist/main.js
),分析其依赖关系,然后将所有需要的代码打包到一个或多个文件中。
基本打包流程(概念性):
- 你编写
.ts
文件,使用import
/export
。 - 使用
tsc
编译.ts
文件到.js
文件(位于outDir
)。 - 使用打包工具(如 Webpack)读取
outDir
中的入口.js
文件。 - 打包工具分析依赖,将所有相关的
.js
文件整合成最终用于浏览器的文件(通常也包含代码优化、压缩等)。 - 在 HTML 中只引入打包后的文件。
很多现代打包工具(如 Vite)甚至可以直接处理 .ts
文件,无需先用 tsc
编译到单独的 .js
目录,简化了流程。理解 tsconfig.json
仍然非常重要,因为它控制了 TypeScript 的编译行为和类型检查规则。
小结: 模块化是组织大型 TypeScript 项目的关键。ES Modules (import
/export
) 是标准的模块化方案。tsconfig.json
文件是 TypeScript 项目的配置中心,控制着编译器的行为。模块打包工具是现代前端开发中处理模块依赖和优化代码不可或缺的一部分。
6.4 练习
- 创建一个新的项目目录。
- 在目录中运行
npm init -y
初始化一个 Node.js 项目。 - 安装 TypeScript:
npm install typescript --save-dev
(或者全局安装npm install -g typescript
)。 - 运行
tsc --init
生成tsconfig.json
文件。 - 创建
src
目录,并在其中创建calculator.ts
和app.ts
文件。 - 在
calculator.ts
中导出一个函数multiply(a: number, b: number): number
和一个常量E = 2.71828
。 - 在
app.ts
中导入multiply
和E
,使用它们进行计算并在控制台输出结果。 - 修改
tsconfig.json
,设置outDir
为dist
,rootDir
为src
,target
为es2018
,module
为esnext
,并开启strict
。 - 在项目根目录运行
tsc
命令,查看dist
目录下生成的.js
文件。 - 创建一个简单的
index.html
文件,引入dist/app.js
(使用<script type="module" src="dist/app.js"></script>
),在浏览器中查看控制台输出。
相关文章:
第四部分:赋予网页健壮的灵魂 —— TypeScript(中)
目录 4 类与面向对象:构建复杂的组件4.1 类的定义与成员4.2 继承 (Inheritance)4.3 接口实现 (Implements)4.4 抽象类 (Abstract Class)4.5 静态成员 (Static Members) 5 更高级的类型:让类型系统更灵活5.1 联合类型 (|)5.2 交叉类型 (&)5.3 字面量类…...
Learning vtkjs之ImageMarchingCubes
体积 等值面处理 介绍 vtkImageMarchingCubes - 对体积进行等值面处理 给定一个指定的等值,使用Marching Cubes算法生成一个等值面。 效果 新建了一个球,对比一下原始的(透明的)和ISO的效果 核心代码 参数部分 const updat…...
【“星睿O6”AI PC开发套件评测】+ tensorflow 初探
因为本次我的项目计划使用 tensorflow,所以这篇文章主要想做一个引子,介绍如何在“星睿O6”上搭建 tensorflow 的开发环境和验证测试。本文主要分为几个部分: 在“星睿O6”上编译安装 tensorflow基于 MNIST 数据集的模型训练和评估 tensorf…...
通义灵码全面接入Qwen3:AI编程进入智能体时代,PAI云上部署实战解析
引言:AI编程的范式革命 2025年4月30日,阿里云通义灵码宣布全面支持新一代大模型Qwen3,并同步推出编程智能体功能,标志着AI辅助开发从“工具助手”向“自主决策智能体”的跃迁。与此同时,阿里云PAI平台上线Qwen3全系列…...
如何禁止AutoCAD这类软件联网
推荐二、三方法,对其他软件影响最小 一、修改Hosts文件 Hosts文件是一个存储域名与IP地址映射关系的文本文件,通过修改Hosts文件可以将AutoCAD的域名指向本地回环地址(127.0.0.1),从而实现禁止联网的目的。具体步骤如…...
音视频项目在微服务领域的趋势场景题深度解析
音视频项目在微服务领域的趋势场景题深度解析 在互联网大厂Java求职者的面试中,经常会被问到关于音视频项目在微服务领域的应用场景的相关问题。本文通过一个故事场景来展示这些问题的实际解决方案。 第一轮提问 面试官:马架构,欢迎来到我…...
100 个 NumPy 练习
本文翻译整理自:https://github.com/rougier/numpy-100 文章目录 关于 100 个 NumPy 练习相关链接资源关键功能特性 100 个 NumPy 练习题1、导入 NumPy 包并命名为 np (★☆☆)2、打印 NumPy 版本和配置信息 (★☆☆)3、创建一个大小为 10 的空向量 (★☆☆)4、如何…...
在Carla中构建自动驾驶:使用PID控制和ROS2进行路径跟踪
机器人软件开发什么是 P、PI 和 PID 控制器?比例 (P) 控制器比例积分 (PI) 控制器比例-积分-微分 (PID) 控制器横向控制简介CARLA ROS2 集成纵向控制横向控制关键要点结论引用 机器人软件开发 …...
Windows和 macOS 上安装 `nvm` 和 Node.js 16.16.0 的详细教程。
Windows和 macOS 上安装 nvm 和 Node.js 16.16.0 的详细教程。 --- ### 1. 安装 nvm(Node Version Manager) nvm 是一个 Node.js 版本管理工具,可以轻松安装和切换不同版本的 Node.js。 #### Windows 安装 nvm 1. **下载 nvm 安装包**&#x…...
day11 python超参数调整
模型组成:模型 算法 实例化设置的外参(超参数) 训练得到的内参调参评估:调参通常需要进行两次评估。若不使用交叉验证,需手动划分验证集和测试集;但许多调参方法自带交叉验证功能,实际中可省略…...
Linux C++ xercesc xml 怎么判断路径下有没有对应的节点
在Linux环境下使用Xerces-C库处理XML文件时,判断路径下是否存在对应的节点可以通过以下几个步骤实现: 加载XML文档 首先,你需要加载XML文档。这可以通过创建一个xercesc::DOMParser对象并使用它的parse方法来实现。 #include <xercesc/…...
罗技K580蓝牙键盘连接mac pro
罗技K580蓝牙键盘,满足了我们的使用需求。最棒的是,它能够同时连接两个设备,通过按F11和F12键进行切换,简直不要太方便! 连接电脑 💻 USB连接 1、打开键盘:双手按住凹槽两边向前推࿰…...
Socket-UDP
Socket(套接字 )是计算机网络中用于实现进程间通信的重要编程接口,是对 TCP/IP 协议的封装 ,可看作是不同主机上应用进程之间双向通信端点的抽象。以下是详细介绍: 作用与地位 作为应用层与传输层、网络层协议间的中…...
【游戏ai】从强化学习开始自学游戏ai-2 使用IPPO自博弈对抗pongv3环境
文章目录 前言一、环境设计二、动作设计三、状态设计四、神经网路设计五、效果展示其他问题总结 前言 本学期的大作业,要求完成多智能体PPO的乒乓球对抗环境,这里我使用IPPO的方法来实现。 正好之前做过这个单个PPO与pong环境内置的ai对抗的训练&#…...
LeRobot 项目部署运行逻辑(三)——机器人及舵机配置
Lerobot 目前的机器人硬件以舵机类型为主,并未配置机器人正逆运动学及运动学,遥操作映射以舵机关节角度为主 因此,需要在使用前需要对舵机各项参数及初始位置进行配置 目录 1 Mobile ALOHA 配置 2 Dynamixel 配置 2.1 配置软件 2.2 SDK …...
Ubuntu20.04安装NVIDIA Warp
Ubuntu20.04安装NVIDIA Warp 安装测试 Warp的gitee网址 Warp的github网址 写在前面:建议安装前先参考readme文件自检系统驱动和cuda是否支持,个人实测建议是python3.9,但python3.8.20也可以使用。 写在前面:后续本人可能会使用这…...
电子病历高质量语料库构建方法与架构项目(临床情景理解模块篇)
引言 随着人工智能技术在医疗健康领域的广泛应用,电子病历(Electronic Medical Records,EMR)作为临床医疗数据的重要载体,已成为医学研究和临床决策支持的关键资源。电子病历高质量语料库的构建为医疗人工智能模型的训练和应用提供了基础支撑,其中临床情境理解模块是连接…...
WPF性能优化举例
WPF性能优化集锦 一、UI渲染性能优化 1. 虚拟化技术 ListView/GridView虚拟化: <ListView VirtualizingStackPanel.IsVirtualizing="True"VirtualizingStackPanel.VirtualizationMode="Recycling"ScrollViewer.IsDeferredScrollingEnabled=…...
【CUDA pytorch】
ev win10 3050ti 联想笔记本 nvcc --version 得到 PS C:\Users\25515> nvcc --version nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2022 NVIDIA Corporation Built on Tue_May__3_19:00:59_Pacific_Daylight_Time_2022 Cuda compilation tools, release …...
mac下载homebrew 安装和使用git
mac下载homebrew 安装和使用git 本人最近从windows换成mac,记录一下用homebrew安装git的过程 打开终端 command 空格,搜索终端 安装homebrew 在终端中输入下面命令,来安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githu…...
Elasticsearch入门速通01:核心概念与选型指南
一、Elasticsearch 是什么? 一句话定义: 开源分布式搜索引擎,擅长处理海量数据的实时存储、搜索与分析,是ELK技术栈(ElasticsearchKibanaBeatsLogstash)的核心组件。 核心能力: 近实时搜索&…...
应对过度处方挑战:为药物推荐任务微调大语言模型(Xiangnan He)
Abstract 药物推荐系统因其有潜力根据患者的临床数据提供个性化且有效的药物组合,在医疗保健领域备受关注。然而,现有方法在适应不同的电子健康记录(EHR)系统以及有效利用非结构化数据方面面临挑战,导致其泛化能力有限…...
41 python http之requests 库
Python 的requests库就像你的 "接口助手",用几行代码就能发送 HTTP 请求,自动处理复杂的网络交互,让你告别手动拼接 URL 和解析响应的痛苦! 一、快速入门:3 步搞定基本请求 1.1 安装库:一键开启助手功能 pip install requests 1.2 发送 GET 请求 import r…...
百度网盘golang实习面经
goroutine内存泄漏的情况?如何避免? goroutine内存泄漏基本上是因为异常导致阻塞, 可以导致阻塞的情况 1 死锁, goroutine 等待的锁发生了死锁情况 2 chan没有正常被关闭,导致读取读chan的goroutine阻塞 如何避免 1 避免死锁 2 正常关闭 3 使用context管…...
super_small_toy_tpu
super_small_toy_tpu 小狼http://blog.csdn.net/xiaolangyangyang 1、基础框图 2、源码下载: GitHub - dldldlfma/super_small_toy_tpu 3、安装iverilog、vvp、gtkwave windows安装:https://bleyer.org/icarus/ ubuntu安装:sudo ap…...
Redis缓存穿透、缓存击穿与缓存雪崩:如何在.NET Core中解决
在高并发的互联网系统中,缓存技术作为优化系统性能的重要手段,已被广泛应用。然而,缓存系统本身也存在一些常见的问题,尤其是 缓存穿透、缓存击穿 和 缓存雪崩。这些问题如果处理不当,可能导致系统性能严重下降&#x…...
驱动车辆诊断测试创新 | 支持诊断测试的模拟器及数据文件转换生成
一 背景和挑战 | 背景: 随着汽车功能的日益丰富,ECU和域控制器的复杂性大大增加,导致测试需求大幅上升,尤其是在ECU的故障诊断和性能验证方面。然而,传统的实车测试方法难以满足高频率迭代和验证需求,不仅…...
VS Code技巧2:识别FreeCAD对象
在使用VS Code阅读FreeCAD代码或者FreeCAD的工作台代码时,VS Code无法识别FreeCAD对象,会提示Import “FreeCAD” could not be resolved: 问题解决如下几步即可。 第一步:确认 FreeCAD 的 Python 环境路径 在FreeCAD的Python控制…...
泰迪杯特等奖案例学习资料:基于多模态融合与边缘计算的智能温室环境调控系统
(第十二届泰迪杯数据挖掘挑战赛特等奖案例解析) 一、案例背景与核心挑战 1.1 应用场景与行业痛点 在现代设施农业中,温室环境调控直接影响作物产量与品质。传统温室管理存在以下问题: 环境参数耦合性高:温度、湿度、光照、CO₂浓度等参数相互影响,人工调控易顾此失彼。…...
猿人学web端爬虫攻防大赛赛题第13题——入门级cookie
1. F12开发者模式 刷新第一页,仔细研究发现里面有三次请求名为13的请求,根据题目提示cookie关键字,所以主要留意请求和响应的cookie值。 三次请求都带了sessionid,说明存在session(后面写代码要用session来写&#x…...
机器指标监控技术方案
文章目录 机器指标监控技术方案架构图组件简介Prometheus 简介核心特性适用场景 Grafana 简介核心特性适用场景 Alertmanager 简介核心特性适用场景 数据采集机器Node ExporterMySQL ExporterRedis ExporterES ExporterRocketMQ ExporterSpringcloud ExporterNacos 数据存储短期…...
数据库设计理论:从需求分析到实现的全流程解析
引言 在当今信息爆炸的时代,数据已成为企业和组织最宝贵的资产之一。如何有效地组织、存储和管理这些数据,是数据库设计需要解决的核心问题。一个优秀的数据库设计能够提高系统性能,确保数据一致性,降低维护成本,而糟…...
一文详解 Linux下的开源打印系统CUPS(Common UNIX Printing System)
文章目录 前言一、CUPS 简介二、CUPS 常用指令解析2.1 安装 CUPS2.2 启动/重启服务2.3 添加打印机(核心操作)2.4 设置默认打印机2.5 打印文件2.6 查看打印任务2.7 取消打印任务2.8 查看、移除已添加的打印机 三、调试与常见问题3.1 日志查看3.2 驱动问题…...
uniapp打包apk详细教程
目录 1.打apk包前提条件 2.获取uni-app标识 3.进入dcloud开发者后台 4.开始打包 1.打apk包前提条件 1.在HBuilderX.exe软化中,登录自己的账号 2.在dcloud官网,同样登录自己的账号。没有可以免费注册。 2.获取uni-app标识 获取方法:点…...
C++初阶-string类2
目录 1.迭代器 1.1普通迭代器的使用 1.2string::begin 1.3string::end 1.4const迭代器的使用 1.5泛型迭代器和const反向迭代器 1.6string::rbegin 1.6string::rend 1.7string::cbegin、string::cend、string::crbegin、string::crend 与begin/end、rbegin/rend的区别 …...
Qt QComboBox 下拉复选多选(multicombobox)
Qt QComboBox 下拉复选多选(multicombobox),备忘,待更多测试 【免费】QtQComboBox下拉复选多选(multicombobox)资源-CSDN文库...
逻辑回归之参数选择:从理论到实践
在机器学习的广阔领域中,逻辑回归作为一种经典的有监督学习算法,常用于解决分类问题。它以其简单易懂的原理和高效的计算性能,在实际应用中备受青睐。然而,要充分发挥逻辑回归的优势,参数选择是关键环节。本文将结合信…...
10、属性和数据处理---c++17
一、[[fallthrought]] 用途:在 switch 语句中标记某个分支 (case) 故意不写 break,明确告知编译器“执行穿透”是有意为之。 仅在需要向下穿透时使用,且应添加注释说明原因 #include<cstdio> #include<iostream> using namesp…...
conda管理python环境
安装conda 使用anaconda官网安装地址:https://www.anaconda.com/download/success 配置镜像环境 conda config --add channels Index of /anaconda/pkgs/main/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror conda config --add channels Index of /an…...
【Python学习路线】零基础到项目实战系统
目录 🌟 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 🧠 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 💻 二、实战演示环境配置要求核心代码实现运行结果验证 ⚡ 三、性能对比测试方法论量化数据对比…...
C/C++核心机制深度解析:指针、结构体与动态内存管理(面试精要)
C/C核心机制深度解析:指针、结构体与动态内存管理(面试精要) 引言 在系统级编程领域,C/C语言凭借对硬件的直接操作能力和高效的内存管理机制,长期占据主导地位。面试中,指针、结构体和动态内存管理作为三…...
宇树科技举办“人型机器人格斗大赛”
2025 年 5 月至 6 月,一场全球瞩目的科技盛宴 —— 全球首场 “人形机器人格斗大赛”,将由杭州宇树科技盛大举办。届时,观众将迎来机器人格斗领域前所未有的视觉震撼。 为打造最强参赛阵容,宇树科技技术团队在过去数周里…...
getattr 的作用
getattr 是 Python 内置的一个函数,用于“动态地”获取对象的属性。**它允许你在运行时通过属性名称(字符串形式)来访问对象的属性,而不用在代码中直接硬编码属性名。**下面详细介绍该方法的用法和注意事项: ────…...
腾讯云服务器性能提升全栈指南(2025版)
腾讯云服务器性能提升全栈指南(2025版) 一、硬件选型与资源优化 1. 实例规格精准匹配 腾讯云服务器提供计算型CVM、内存型MEM、大数据型Hadoop等12种实例类型。根据业务特性选择: • 高并发Web应用:推荐SA3实例࿰…...
Kotlin与Jetpack Compose的详细使用指南
Kotlin与Jetpack Compose的详细使用指南,综合最新技术实践和官方文档整理: 一、环境配置与基础架构 项目创建 在Android Studio中选择Empty Compose Activity模板,默认生成包含Composable预览的MainActivity2要求Kotlin版本≥1.8.0&…...
潇洒郎: 100% 成功搭建Docker私有镜像仓库并管理、删除镜像
1、Registry Web管理界面 2、拉取Registry-Web镜像 创建配置文件 tee /opt/zwx-registry/web-config.yml <<-EOF registry:url: http://172.28.73.90:8010/v2name: registryreadonly: falseauth:enabled: false EOF 拉取docker-registry-web镜像并绑定Registry仓库 …...
【Spring Boot 注解】@ConfigurationProperties
文章目录 ConfigurationProperties注解一、简介二、依赖引入三、基本用法四、主要特性五、激活方式六,优点七、与 Value 对比 ConfigurationProperties注解 一、简介 ConfigurationProperties 是 Spring Boot 提供的一个强大注解,用于将外部配置&#…...
阿里云服务迁移实战: 06-切换DNS
概述 按前面的步骤,所有服务迁移完毕之后,最后就剩下 DNS 解析修改了。 修改解析 在域名解析处,修改域名的解析地址即可。 如果 IP 已经过户到了新账号,则不需要修改解析。 何确保业务稳定 域名解析更换时,由于 D…...
Java实现归并排序算法
1. 归并排序原理图解 归并排序是一种分治算法,其核心思想是将数组分成两半,分别对这两半进行排序,然后将排序后的两半合并。以下是归并排序的步骤: 1. 分治: - 将数组分成两半。 - 递归地对每半部分进行归并排序。 2. …...
Vue 项目中运行 `npm run dev` 时发生的过程
步骤1:找到「任务说明书」(package.json) 当你输入 npm run dev,系统首先会去查项目的 「任务说明书」(即 package.json 文件),看看 dev 这个任务具体要做什么。 示例代码(package.json 片段)…...