第四部分:赋予网页健壮的灵魂 —— 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 为类添加了强大的类型和可见性控制,支持继承和接口实现,使得代码结构更清晰,更易于管理和扩展,就像有了更标准化的智能家居模块。

练习:

  1. 定义一个基类 Vehicle,包含属性 speed (number) 和方法 move()
  2. 创建一个子类 Car 继承自 Vehicle,添加属性 brand (string),并重写 move() 方法,使其输出汽车正在移动。
  3. 定义一个接口 Drawable,包含方法 draw(): void
  4. 创建一个类 Circle,实现 Drawable 接口,并在 draw 方法中输出绘制圆形的信息。
  5. 修改 Light 类,使其 brightness 属性默认是 protected。创建一个继承自 LightDimmingLight 类,添加一个 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 voidnever

  • 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 更清楚一个变量的实际类型。类型断言就是告诉编译器,“相信我,这个变量就是这个类型”。这就像你看到一根没有标签的电线,但根据经验你确定它是电源线,你就可以“断言”它是电源线。

重要提示: 类型断言只在编译阶段起作用,不会改变变量的实际运行时类型或执行任何额外的检查。如果断言错误,可能会导致运行时错误。谨慎使用!

有两种语法:

  1. value as Type (JSX 中推荐使用)
  2. <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 能够更精确地描述复杂的数据结构和取值范围。理解 anyunknown 的区别以及何时使用类型断言是编写安全 TypeScript 代码的关键。

5.8 练习

  1. 定义一个联合类型 ContactInfo,可以是 string (邮箱) 或 number (电话号码)。声明一个变量 myContact 为此类型,并分别赋值一个邮箱地址和电话号码。
  2. 定义一个接口 HasArea,包含方法 getArea(): number。定义一个接口 HasPerimeter,包含方法 getPerimeter(): number。定义一个交叉类型 Shape,它是 HasAreaHasPerimeter 的组合。创建一个对象,实现 Shape 类型。
  3. 定义一个字符串字面量类型 TrafficLightColor,它的值只能是 "Red", "Yellow", "Green" 中的一个。编写一个函数 changeLight(color: TrafficLightColor): void
  4. 定义一个数字枚举 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 模块 (importexport)

在 TypeScript 中,每个 .ts 文件默认就是一个模块。你可以使用 export 关键字导出模块中的变量、函数、类、接口等,然后使用 import 关键字在其他模块中引入它们。

创建两个文件:utils.tsmain.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.tsmain.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),分析其依赖关系,然后将所有需要的代码打包到一个或多个文件中。

基本打包流程(概念性):

  1. 你编写 .ts 文件,使用 import/export
  2. 使用 tsc 编译 .ts 文件到 .js 文件(位于 outDir)。
  3. 使用打包工具(如 Webpack)读取 outDir 中的入口 .js 文件。
  4. 打包工具分析依赖,将所有相关的 .js 文件整合成最终用于浏览器的文件(通常也包含代码优化、压缩等)。
  5. 在 HTML 中只引入打包后的文件。

很多现代打包工具(如 Vite)甚至可以直接处理 .ts 文件,无需先用 tsc 编译到单独的 .js 目录,简化了流程。理解 tsconfig.json 仍然非常重要,因为它控制了 TypeScript 的编译行为和类型检查规则。

小结: 模块化是组织大型 TypeScript 项目的关键。ES Modules (import/export) 是标准的模块化方案。tsconfig.json 文件是 TypeScript 项目的配置中心,控制着编译器的行为。模块打包工具是现代前端开发中处理模块依赖和优化代码不可或缺的一部分。

6.4 练习

  1. 创建一个新的项目目录。
  2. 在目录中运行 npm init -y 初始化一个 Node.js 项目。
  3. 安装 TypeScript: npm install typescript --save-dev (或者全局安装 npm install -g typescript)。
  4. 运行 tsc --init 生成 tsconfig.json 文件。
  5. 创建 src 目录,并在其中创建 calculator.tsapp.ts 文件。
  6. calculator.ts 中导出一个函数 multiply(a: number, b: number): number 和一个常量 E = 2.71828
  7. app.ts 中导入 multiplyE,使用它们进行计算并在控制台输出结果。
  8. 修改 tsconfig.json,设置 outDirdistrootDirsrctargetes2018moduleesnext,并开启 strict
  9. 在项目根目录运行 tsc 命令,查看 dist 目录下生成的 .js 文件。
  10. 创建一个简单的 index.html 文件,引入 dist/app.js(使用 <script type="module" src="dist/app.js"></script>),在浏览器中查看控制台输出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79163.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3源码学习-提交限制

文章目录 前言✅ 1. ESLint 限制&#x1f527; 配置位置&#xff1a;✅ 启用了哪些规则&#xff08;核心&#xff09;&#xff1a;&#x1f4e6; 使用的插件和标准&#xff1a; ✅ 2. TSC 编译限制关键选项&#xff1a; ✅ 3. Git Hook 校验工具链配置例子&#xff08;package.…

Arthas 使用攻略

目录 背景 Arthas是什么&#xff1f; 安装 使用arthas-boot&#xff08;推荐&#xff09; 启动 常用命令 一键生成arthas命令的插件(强烈推荐) watch 一、命令语法结构 二、核心参数详解 三、实战场景 1. 基础观测 - 查看入参和返回值 2. 条件过滤 - 只关注特定参…

冥想类短视频批量剪辑自动混剪技术实践:从素材处理到智能合成全解析

一、引言&#xff1a;工业化内容生产的技术突围 在心理健康类内容爆发的当下&#xff0c;冥想类短视频凭借「低制作成本 高用户粘性」的特性成为热门赛道。本文结合实战经验&#xff0c;解析如何通过模块化素材处理、参数化合成引擎、自动化质量控制等技术手段&#xff0c;构…

【自定义控件实现最大高度和最大宽度实现】

背景 开发中偶尔遇到控件宽度或者高度在自适应的情况下&#xff0c;有个边界值&#xff0c;也就是最大值。 比如高度自适应的情况下最大高度300dp这种场景。 实现 关键节点代码&#xff1a; Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)…

综合练习三

使用到的知识点&#xff1a;xml文件&#xff0c;初始化file数据&#xff0c;提取file文件数据 题目&#xff1a;水文检测系统 备注&#xff1a;可以把序号作为该条数据的唯一标识&#xff08;即UUID&#xff09;&#xff0c;而不是第一条第二条数据这样的类型。代码是后者&…

Microsoft Entra ID 详解:现代身份与访问管理的核心

Microsoft Entra ID(原名为 Azure Active Directory,简称 Azure AD)是微软推出的云端身份和访问管理服务,专为现代混合环境设计,支持企业安全地管理用户身份、控制资源访问,并集成多种应用与服务。以下从核心功能到最佳实践全面解析 Entra ID。 1. Entra ID 的核心定位 …

从技术角度看Facebook的隐私保护机制

在数字化时代&#xff0c;隐私保护成为了公众关注的焦点。作为全球最大的社交网络平台之一&#xff0c;Facebook 在隐私保护方面采取了一系列技术措施。本文将从技术角度探讨 Facebook 的隐私保护机制&#xff0c;揭示它是如何在提供个性化服务的同时&#xff0c;确保用户隐私信…

基于策略模式实现灵活可扩展的短信服务架构

基于策略模式实现灵活可扩展的短信服务架构 引言 在企业级应用开发中&#xff0c;短信服务是不可或缺的基础功能之一。随着业务发展&#xff0c;我们可能需要接入多个短信服务提供商&#xff08;如阿里云、腾讯云、第三方短信网关等&#xff09;&#xff0c;并能够在不修改核…

Vue 3 单文件组件中 VCA 语法糖及核心特性详解

在 Vue.js 的开发世界里&#xff0c;单文件组件&#xff08;Single File Components&#xff0c;简称 SFC&#xff09;是构建复杂应用的基石。它将 HTML、CSS 和 JavaScript 代码封装在一个.vue文件中&#xff0c;极大地提高了代码的可维护性和复用性。 本文将深入探讨单文件组…

【Unity C#从零到精通】项目深化:构建核心游戏循环、UI与动态敌人系统

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…

SNR8016语音模块详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main文件 usart.h文件 usart.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 SNR8016语音模块是智纳捷科技生产的一种离线语音识别模块&#xff0c;设计适合用于DIY领域&#xff0c;开放用户设…

「动态规划」线性DP:最长上升子序列(LIS)|编辑距离 / LeetCode 300|72(C++)

概述 DP&#xff0c;即动态规划是解决最优化问题的一类算法&#xff0c;我们要通过将原始问题分解成规模更小的、相似的子问题&#xff0c;通过求解这些易求解的子问题来计算原始问题。 线性DP是一类基本DP&#xff0c;我们来通过它感受DP算法的奥义。 最长上升子序列&#x…

【NumPy完全指南】从基础操作到高性能计算实战

&#x1f4d1; 目录 一、NumPy核心价值1.1 科学计算现状分析1.2 ndarray设计哲学 二、核心数据结构解析2.1 ndarray内存布局2.2 数据类型体系 三、矢量化编程实践3.1 通用函数(ufunc)示例3.2 广播机制图解 四、高性能计算进阶4.1 内存预分配策略4.2 Cython混合编程 五、典型应用…

你的项目有‘哇‘点吗?

你的项目有哇点吗&#xff1f; 刷了一下午招聘软件&#xff0c;发现没&#xff1f;大厂JD里总爱写有创新力者优先——可你们的简历&#xff0c;创新力还不如食堂菜单&#xff01; 程序员写项目最大的误区&#xff1a;把创新当彩蛋藏最后&#xff01;什么参与需求评审负责模块…

2025年危化品安全员考试题库及答案

一、单选题 126.安全生产监督管理部门和负有安全生产监督管理职责的有关部门逐级上报事故情况,每级上报的时间不得超过&#xff08;&#xff09;小时。 A.2 B.6 C.12 答案&#xff1a;A 127.按照《安全生产法》规定,危险化学品生产经营单位的从业人员不服从管理,违反安全生…

第十六届蓝桥杯 C/C++ B组 题解

做之前的真题就可以发现&#xff0c;蓝桥杯特别喜欢出找规律的题&#xff0c;但是我还是低估了官方的执念。本博客用于记录第一次蓝桥的过程&#xff0c;代码写的很烂&#xff0c;洛谷已经有的题解&#xff0c;这里不再赘述&#xff0c;只说自己遇到的问题。用于以后回顾和查找…

C++ 基于多设计模式下的同步异步⽇志系统-2项目实现

⽇志系统框架设计 1.⽇志等级模块:对输出⽇志的等级进⾏划分&#xff0c;以便于控制⽇志的输出&#xff0c;并提供等级枚举转字符串功能。 ◦ OFF&#xff1a;关闭 ◦ DEBUG&#xff1a;调试&#xff0c;调试时的关键信息输出。 ◦ INFO&#xff1a;提⽰&#xff0c;普通的提⽰…

提示词工程(GOT)把思维链推理过程图结构化

Graph of Thoughts&#xff08;GOT&#xff09;&#xff1f; 思维图&#xff08;Graph of Thoughts&#xff09;是一种结构化的表示方法&#xff0c;用于描述和组织模型的推理过程。它将信息和思维过程以图的形式表达&#xff0c;其中节点代表想法或信息&#xff0c;边代表它们…

登录github失败---解决方案

登录github失败—解决方案 1.使用 Microsoft Edge 浏览器 2.https://www.itdog.cn/dns/ 查询 github.global.ssl.fastly.net github.com 两个 域名的 IP 3.修改DNS 为 8.8.8.8 8.8.4.4 4.修改windows hosts 文件 5. 使用 Microsoft Edge 浏览器 打开github.com

Spring AOP概念及其实现

一、什么是AOP 全称Aspect Oriented Programming&#xff0c;即面向切面编程&#xff0c;AOP是Spring框架的第二大核心&#xff0c;第一大为IOC。什么是面向切面编程&#xff1f;切面就是指某一类特定的问题&#xff0c;所以AOP也可以称为面向特定方法编程。例如对异常的统一处…