関数から「副作用」を排除せよ!TypeScriptで不変性(Immutability)を強制する最強の設計術
こんにちは。TypeScriptの深淵を日々探求しているアーキテクトです。
皆さんは関数を書くとき、「この関数、受け取ったオブジェクトの中身をうっかり書き換えてしまっていないかな?」と不安になったことはありませんか?
大規模なアプリケーション開発において、バグの温床となるのは決まって「予期せぬデータの書き換え(副作用)」です。今回は、TypeScriptの型システムを駆使して、「引数を渡した側が安心して関数を呼べる」ための、Readonlyを強制する設計術を伝授します。
—
なぜ「Readonly」が必要なのか?
TypeScriptは非常に強力ですが、デフォルトでは「オブジェクトの参照」が渡された場合、その中身を書き換えることができてしまいます。
type User = { name: string; age: number };
function celebrateBirthday(user: User) {
// 悪夢の始まり:元のオブジェクトを直接書き換えてしまう(副作用)
user.age += 1;
}
const me = { name: “Taro”, age: 25 };
celebrateBirthday(me);
console.log(me.age); // 26!元のデータが勝手に書き換わった!
これ、一人で書いている分にはいいですが、チーム開発では「データがいつの間にか変わっている」という地獄のようなデバッグを引き起こしますよね。これを防ぐのが `Readonly
—
型レベルで不変性を強制する:Readonlyの極意
不変性(Immutability)を保証するには、大きく分けて2つのアプローチがあります。
1. ユーティリティ型 `Readonly` を使う
既存の型に対して、「これ以降、中身は書き換え禁止だよ」と型システムに宣言させる方法です。
function safeCelebrate(user: Readonly
// user.age += 1;
// ↑ ここでコンパイルエラー!
// “Cannot assign to ‘age’ because it is a read-only property.”
// 安全に新しいオブジェクトを返すのが関数型の流儀です
return { …user, age: user.age + 1 };
}
2. インターフェース定義で最初から「readonly」にする
ドメインモデル(データ構造)を定義する段階で、プロパティを「読み取り専用」にしてしまう方法です。これが最も堅牢で、現場で推奨される手法です。
interface User {
readonly name: string;
readonly age: number;
}
const me: User = { name: “Taro”, age: 25 };
// me.age = 26; // 即座にコンパイルエラーで弾く!
—
陥りやすい罠:深い階層の「破壊」
ここで一つ、中級者の方がよくハマるポイントをお伝えします。`readonly` は「浅いコピー(Shallow Copy)」に対してのみ有効です。
オブジェクトの中にさらにオブジェクトがある場合、その内部までは保護されません。
interface Config {
readonly settings: {
readonly theme: string;
};
}
const config: Config = { settings: { theme: “dark” } };
// config.settings.theme = “light”;
// ↑ これはエラーだが…
// もし “readonly” がついていない定義なら、内部は書き換えられてしまう!
もし完全に不変性を担保したい場合は、`ReadonlyDeep`(type-festなどのライブラリを活用)や、再帰的なユーティリティ型を自作する必要があります。ここを意識できると、あなたのコードの信頼性は一段と跳ね上がります。
—
まとめ:信頼される関数の作り方
TypeScriptの型システムは、単なる「エラーチェック」ではなく、「仕様の宣言」です。
- 引数には可能な限り `Readonly` をつける: 「この関数は引数を変更しませんよ」という契約を型で交わす。
- 破壊的な操作を避け、新しいオブジェクトを生成する: スプレッド演算子 `{ …obj }` を活用し、イミュータブルな設計を心がける。
- 副作用を末端に追いやる: データを変換する純粋関数(Pure Function)と、副作用を扱う関数を分離する。
「ここをクリアすれば、TypeScriptの基本はバッチリマスター」と言いましたが、実はこれこそが大規模フロントエンド開発におけるクリーンアーキテクチャの入り口でもあります。
型システムをあなたの味方につけて、バグのない美しいコードを書いていきましょう。何か疑問があれば、いつでも聞いてくださいね。応援しています!