概要
JavaScriptはミューテーション(変更)を許容する言語だが、
だからといって変更可能な構造を野放しにすべきではない。
「イミュータブル(不変)」とは、
状態を直接変更せず、新しい状態として再生成する設計を意味する。
これは特に 状態管理 / UI更新 / ロジックの純粋性維持 において、
副作用を排除し、予測可能でテスト容易なコードを生むために不可欠である。
本稿では、イミュータブル設計の基本と実践、ミューテーションの制御戦略、データ構造の選定原則について体系的に解説する。
1. イミュータブル設計の基本原則
const user = { name: 'Taro', age: 25 };
// ❌ 直接変更(ミューテーション)
user.age = 26;
// ✅ イミュータブル(再生成)
const updatedUser = { ...user, age: 26 };
- ✅ 既存オブジェクトを 変更せずに新たに生成
- ✅ 変更のトラッキングや状態の差分検出が容易に
2. 配列操作におけるミューテーション対策
const list = [1, 2, 3];
// ❌ 直接push(ミューテーション)
list.push(4);
// ✅ スプレッドで追加(イミュータブル)
const newList = [...list, 4];
// ✅ フィルターで削除
const filtered = list.filter(n => n !== 2);
- ✅
map
,filter
,reduce
などの純粋関数的手法が有効 - ✅ ソートやスプライスなどの破壊的操作は避ける
3. ネストされた構造の更新
const state = {
profile: {
name: 'Taro',
contact: {
email: 'taro@example.com'
}
}
};
// ✅ ネストを崩さず再生成
const updated = {
...state,
profile: {
...state.profile,
contact: {
...state.profile.contact,
email: 'jiro@example.com'
}
}
};
- ✅ 深い更新には 構造的再構成が必要
- ✅ Immer.js などのライブラリで簡潔に保てる場合も
4. イミュータブル設計の利点
- ✅ 予測可能な状態遷移(差分が明確)
- ✅ 副作用の排除(データ汚染がない)
- ✅ 時間的整合性の確保(過去の状態を参照可能)
- ✅ テストが容易(状態の再現性が高い)
- ✅ 状態比較が容易(シャロー比較で済むことが多い)
5. 状態管理における具体的適用:Reduxなど
function reducer(state, action) {
switch (action.type) {
case 'UPDATE_NAME':
return {
...state,
name: action.payload
};
default:
return state;
}
}
- ✅ Reduxなどのライブラリは イミュータブル前提
- ✅ reducerは副作用を含まない純粋関数として設計される
設計判断フロー
① その状態は他の箇所と参照共有されていないか?
② 状態更新の過程で、元の構造を破壊していないか?
③ ネスト構造は分割して更新可能か? → 分離して再構成できるか?
④ データ構造はスプレッドやmap/filterで扱える形か?
⑤ ライブラリ導入(Immerなど)によって可読性と安全性が向上するか?
よくあるミスと対策
❌ obj[prop] = value
による直接代入
→ ✅ スプレッド構文や関数的手段で再生成
❌ 破壊的操作(push, splice, sort)でデータが壊れる
→ ✅ 非破壊的なメソッドを選択。必要ならslice + concatなど
❌ ネストが深くなり再生成が困難
→ ✅ 状態の分割設計 or Immer.jsなどの支援ライブラリ活用
結語
イミュータブル設計とは、「書き換えない」という禁則ではない。
それは**“状態を外部から隔離し、予測可能な構造として制御するための設計戦略”**である。
- 状態は再生成し、構造の流れとして追跡可能に
- 参照を汚さず、副作用のない制御を保証
- 状態変化を「イベントの履歴」として設計できる
JavaScriptにおけるイミュータブル設計とは、
“変化を安全に扱うための非破壊的構造戦略”である。