TypeScript 不可變性 readonly, Readonly<T>的使用小結
哈嘍,大家好,歡迎來到【哈希茶館】!在日常的全棧開發(fā)中,我們經(jīng)常會遇到因數(shù)據(jù)被意外修改而導致的各種詭異 Bug。特別是在 JavaScript 這種動態(tài)語言中,對象的屬性默認是可變的,這在大型或多人協(xié)作的項目中很容易引入不確定性。TypeScript 作為 JavaScript 的超集,為我們提供了強大的類型系統(tǒng),其中就包括了實現(xiàn)不可變性的有效工具。今天,我們就來聊聊如何使用 readonly 和 Readonly<T> 來提升代碼的健壯性和可維護性。
什么是不可變性?為什么它很重要?
簡單來說,不可變性(Immutability)指的是一個對象在創(chuàng)建之后,其狀態(tài)就不能再被修改。如果我們想改變這個對象,不能直接修改原始對象,而是應該創(chuàng)建一個新的對象,這個新對象擁有修改后的狀態(tài)。
擁抱不可變性有諸多好處:
- 可預測性更高:當你知道一個數(shù)據(jù)結構不會被意外改變時,代碼的行為就更容易預測和推理。
- 簡化調(diào)試:如果應用狀態(tài)是不可變的,追蹤狀態(tài)變更就變得簡單,因為每次變更都會產(chǎn)生新的對象。
- 性能優(yōu)化:在某些場景下(例如 React 的 PureComponent 或 Redux),通過比較對象的引用就能快速判斷數(shù)據(jù)是否發(fā)生變化,從而避免不必要的渲染或計算。
- 更易于并發(fā)處理:不可變數(shù)據(jù)天然線程安全,因為它們不會被多個線程同時修改(雖然在前端 JavaScript 中并發(fā)場景相對較少,但這個理念依然重要)。
readonly修飾符:讓屬性只讀
TypeScript 提供了 readonly 修飾符,可以用來標記類的屬性或接口的屬性,表示這些屬性在對象初始化之后就不能再被修改。
1. 類屬性的readonly
class UserProfile {
readonly userId: string;
name: string;
constructor(userId: string, name: string) {
this.userId = userId; // 可以在構造函數(shù)中賦值
this.name = name;
}
updateName(newName: string) {
this.name = newName; // 普通屬性可以修改
}
// 嘗試修改 readonly 屬性會導致編譯錯誤
// updateUserId(newUserId: string) {
// this.userId = newUserId; // Error: Cannot assign to 'userId' because it is a read-only property.
// }
}
const user = new UserProfile("user-123", "Alice");
console.log(user.userId); // "user-123"
user.updateName("Bob");
console.log(user.name); // "Bob"
// user.userId = "user-456"; // Error: Cannot assign to 'userId' because it is a read-only property.
在上面的例子中,userId 屬性被 readonly 修飾后,只能在構造函數(shù) constructor 中被初始化。之后任何嘗試修改 userId 的行為都會在編譯階段被 TypeScript 編譯器捕獲并報錯。
2. 接口屬性的readonly
readonly 同樣可以用于接口定義:
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.
function movePoint(p: Point, dx: number, dy: number): Point {
// 不能直接修改 p.x 和 p.y
// 要返回一個新的 Point 對象
return { x: p.x + dx, y: p.y + dy };
}
const p2 = movePoint(p1, 5, 5);
console.log(p1); // { x: 10, y: 20 } (原始對象未改變)
console.log(p2); // { x: 15, y: 25 } (返回了新對象)
這里,Point 接口的 x 和 y 屬性都是只讀的。當我們嘗試修改 p1.x 時,編譯器會報錯。movePoint 函數(shù)也遵循了不可變性的原則,它沒有修改傳入的 Point 對象,而是返回了一個全新的 Point 對象。
注意:readonly 提供的是淺不可變性。如果 readonly 屬性本身是一個對象,那么這個對象的屬性仍然是可以修改的。
interface Config {
readonly serverUrl: string;
readonly settings: {
timeout: number;
retries: number;
};
}
const appConfig: Config = {
serverUrl: "https://api.example.com",
settings: {
timeout: 5000,
retries: 3,
},
};
// appConfig.serverUrl = "https://new.api.example.com"; // Error: Cannot assign to 'serverUrl' because it is a read-only property.
appConfig.settings.timeout = 10000; // 這是允許的!
console.log(appConfig.settings.timeout); // 10000
appConfig.settings 本身是 readonly 的,意味著你不能給 appConfig.settings 賦一個新的對象。但是 settings 對象內(nèi)部的 timeout 屬性依然是可變的。
Readonly<T>工具類型:創(chuàng)建全只讀類型
當我們需要將一個現(xiàn)有類型的所有屬性都變成只讀時,TypeScript 的 Readonly<T> 工具類型就派上用場了。它會構造一個新類型,將類型 T 的所有屬性都設置為 readonly。
interface MutableConfigs {
port: number;
debugMode: boolean;
features: string[];
}
// 使用 Readonly<T> 將 MutableConfigs 的所有屬性變?yōu)橹蛔x
type ImmutableConfigs = Readonly<MutableConfigs>;
const initialConfigs: MutableConfigs = {
port: 3000,
debugMode: true,
features: ["featureA", "featureB"]
};
// immutableAppConfig 的類型是 ImmutableConfigs
const immutableAppConfig: ImmutableConfigs = initialConfigs;
// immutableAppConfig.port = 8080; // Error: Cannot assign to 'port' because it is a read-only property.
// immutableAppConfig.debugMode = false; // Error: Cannot assign to 'debugMode' because it is a read-only property.
// 同樣,Readonly<T> 也是淺不可變的
// immutableAppConfig.features.push("featureC"); // 這仍然是允許的,因為 features 是一個數(shù)組對象
// console.log(immutableAppConfig.features); // ["featureA", "featureB", "featureC"]
// 如果要使數(shù)組也只讀,可以這樣定義:
interface TrulyImmutableConfigs {
readonly port: number;
readonly debugMode: boolean;
readonly features: ReadonlyArray<string>; // 使用 ReadonlyArray<T>
}
const trulyImmutableAppConfig: TrulyImmutableConfigs = {
port: 8000,
debugMode: false,
features: ["logging", "monitoring"]
};
// trulyImmutableAppConfig.features.push("newFeature"); // Error: Property 'push' does not exist on type 'readonly string[]'.
// trulyImmutableAppConfig.features[0] = "newLogging"; // Error: Index signature in type 'readonly string[]' only permits reading.
Readonly<T> 非常適合用于函數(shù)參數(shù)或返回值,以明確表示函數(shù)不會(或不應該)修改傳入的對象,或者返回一個不應被修改的對象。
function processConfig(config: Readonly<MutableConfigs>): void {
console.log(`Processing config for port: ${config.port}`);
// config.port = 8081; // Error: Cannot assign to 'port' because it is a read-only property.
}
processConfig(initialConfigs);
ReadonlyArray<T>
對于數(shù)組,TypeScript 提供了 ReadonlyArray<T> 類型。一個 ReadonlyArray<T> 移除了所有會修改數(shù)組的方法(如 push, pop, splice 等),并且其索引簽名也是只讀的,防止通過索引直接修改元素。
let readOnlyNumbers: ReadonlyArray<number> = [1, 2, 3, 4, 5]; // readOnlyNumbers.push(6); // Error: Property 'push' does not exist on type 'readonly number[]'. // readOnlyNumbers[0] = 0; // Error: Index signature in type 'readonly number[]' only permits reading. // 可以使用不會修改原數(shù)組的方法 const doubledNumbers = readOnlyNumbers.map(n => n * 2); console.log(doubledNumbers); // [2, 4, 6, 8, 10] console.log(readOnlyNumbers); // [1, 2, 3, 4, 5] (原始數(shù)組未改變)
實踐中的考量與深不可變性
readonly 和 Readonly<T> 為我們提供了在 TypeScript 中實現(xiàn)不可變性的基礎工具。它們主要提供的是淺不可變性。這意味著如果一個只讀屬性本身是一個復雜對象(如對象或數(shù)組),那么這個復雜對象的內(nèi)部屬性仍然是可變的。
要實現(xiàn)深不可變性(Deep Immutability),即對象及其所有嵌套的子對象都不可變,通常需要:
- 遞歸地定義只讀類型:手動為嵌套結構定義
readonly。 - 使用第三方庫:像 Immer 或 Immutable.js 這樣的庫提供了更完善的深不可變數(shù)據(jù)結構和操作方法。
在大多數(shù)日常開發(fā)場景中,TypeScript 內(nèi)建的淺不可變性已經(jīng)能解決很多問題,特別是在定義配置對象、函數(shù)契約等方面。當處理復雜的狀態(tài)管理(如 Redux store)時,可以考慮引入專門的不可變數(shù)據(jù)庫。
總結
擁抱不可變性是提升代碼質(zhì)量、減少 Bug、增強系統(tǒng)可維護性的重要手段。TypeScript 的 readonly 修飾符和 Readonly<T> 工具類型為我們提供了在編譯時強制實施不可變性的能力。
- 使用
readonly標記那些初始化后不應再改變的類屬性或接口屬性。 - 使用
Readonly<T>將現(xiàn)有類型的所有屬性轉(zhuǎn)換為只讀,常用于函數(shù)參數(shù)和返回值。 - 對于數(shù)組,使用
ReadonlyArray<T>來確保數(shù)組內(nèi)容不被修改。
雖然它們提供的是淺不可變性,但這已經(jīng)是向前邁出的一大步。養(yǎng)成使用這些工具的習慣,你的代碼會變得更加穩(wěn)健和可預測。
到此這篇關于TypeScript 不可變性 readonly, Readonly<T>的使用小結的文章就介紹到這了,更多相關TypeScript 不可變性 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在HTML中引入JavaScript的三種方式及優(yōu)缺點詳解
本文詳細介紹了HTML中引入JavaScript的三種方式,包括事件句柄的使用(如點擊事件觸發(fā)警告框)、腳本塊的編寫位置及其執(zhí)行順序,以及引入外部JS文件的實踐,需要的朋友可以參考下2026-02-02
JavaScript圣杯布局與雙飛翼布局實現(xiàn)案例詳解
這篇文章主要介紹了JavaScript圣杯布局與雙飛翼布局實現(xiàn)案例,這是前端面試中需要掌握的知識點,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08
純JavaScript實現(xiàn)獲取onclick、onchange等事件的值
這篇文章主要介紹了JavaScript獲取onclick、onchange等事件的值的方法,需要的朋友可以參考下2014-12-12

