第四部分:賦予網頁健壯的靈魂 —— 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.pswp.cn/bicheng/79163.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/79163.shtml
英文地址,請注明出處:http://en.pswp.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也可以稱為面向特定方法編程。例如對異常的統一處…