immer、immutability-helper、seamless-immutable は、JavaScript アプリケーション、特に React や Redux を使用する際に、状態の不変性(immutability)を維持するためのライブラリです。手動でオブジェクトをコピーする繁琐な作業を減らし、バグの少ない予測可能な状態更新を実現します。immer は現在の業界標準であり、プロキシを使用して「変更する」コードを書きながら不変な結果を生成します。immutability-helper はコマンドベースの構文で更新を記述し、seamless-immutable は通常の配列やオブジェクトのように振る舞う不変データ構造を提供しますが、現在はレガシーな扱いを受ける傾向にあります。
JavaScript、特に React や Redux を使った開発において、状態の不変性(immutability)を保つことはバグを減らすために重要です。しかし、スプレッド演算子(...)を使った手動のコピーは、ネストが深くなるとミスやパフォーマンスの問題を引き起こします。immer、immutability-helper、seamless-immutable はこの問題を解決しますが、アプローチと現代の開発環境への適合性は大きく異なります。
各ライブラリは、状態を更新するための独自の構文を持っています。これが開発者の日常作業に最も影響します。
immer は「ドラフト」オブジェクトを直接変更するスタイルです。
import { produce } from "immer";
const baseState = { user: { name: "Alice", age: 25 } };
const nextState = produce(baseState, (draft) => {
// 直接変更する
draft.user.name = "Bob";
draft.user.age = 26;
});
immutability-helper はコマンド($set、$push など)を使って更新を指示します。
import update from "immutability-helper";
const baseState = { user: { name: "Alice", age: 25 } };
const nextState = update(baseState, {
user: {
name: { $set: "Bob" },
age: { $set: 26 }
}
});
seamless-immutable は、不変オブジェクトのメソッド(set、setIn)を呼び出します。
import Immutable from "seamless-immutable";
const baseState = Immutable({ user: { name: "Alice", age: 25 } });
// setIn を使ってネストされたプロパティを更新
const nextState = baseState.setIn(["user", "name"], "Bob")
.setIn(["user", "age"], 26);
ライブラリの内部動作は、アプリの規模が大きくなった際のパフォーマンスに影響します。
immer は構造共有(structural sharing)を使用します。
// Immer: 変更されていない 'settings' は参照が共有される
const state = { user: {...}, settings: {...} };
const next = produce(state, draft => { draft.user.name = "New" });
// state.settings === next.settings (true)
immutability-helper は指定されたパスを深くコピーします。
// Immutability-helper: 指定されたパスをコピー
const next = update(state, { user: { name: { $set: "New" } } });
// 開発者が意識して構造共有を考慮する必要がある
seamless-immutable は作成時にデータを凍結(freeze)します。
// Seamless-immutable: 変更を試みるとエラー
try {
baseState.user.name = "Bob"; // TypeError が発生する
} catch (e) {
// 不変性が強制される
}
ミスを防ぐ機能は、チーム開発や大規模コードベースで重要です。
immer は開発モードで凍結チェックを行います。
// Immer: 開発モードでのみ厳密なチェック
// 通常のコードフローでは静かに失敗する可能性がある
immutability-helper は安全性の保証を提供しません。
// Immutability-helper: 手動での注意が必要
// 元の state を直接変更するコードを書いてもエラーにならない
seamless-immutable は最強のランタイム保護を提供します。
// Seamless-immutable: 強力な保護
const obj = Immutable({ a: 1 });
obj.a = 2; // 即座にエラーで止まる
現代の開発では TypeScript サポートが不可欠です。
immer は型推論が非常に優秀です。
// Immer: 型推論が効く
const nextState = produce(state, (draft) => {
draft.count += 1; // 型チェックが有効
});
immutability-helper は型定義がありますが、煩雑です。
$set など)を型で表現する必要があり、型エラーが出にくい反面、型定義が複雑になります。// Immutability-helper: コマンドの型定義が必要
update(state, {
count: { $inc: 1 } // 型のサポートはあるが冗長
});
seamless-immutable は独自の型ラッパーが必要です。
Immutable<T> のように型をラップする必要があり、通常の TypeScript 型との互換性で問題が起きることがあります。// Seamless-immutable: 専用の型が必要
const state: Immutable.Object<{ count: number }>;
| 機能 | immer | immutability-helper | seamless-immutable |
|---|---|---|---|
| 構文スタイル | 可変コード風(ドラフト) | コマンド指定($set) | メソッドチェーン(setIn) |
| 内部技術 | Proxy(現代) | オブジェクトコピー | 凍結された構造 |
| 不変性強制 | 開発モードのみ | なし(開発者依存) | 常に強制(エラー投げる) |
| TypeScript | 優秀(自動推論) | 普通(コマンド型必要) | 特殊(ラッパー型必要) |
| メンテナンス | 活発 | 低(レガシー) | 低(レガシー) |
immer が現代のフロントエンド開発における事実上の標準です。可読性、パフォーマンス、TypeScript サポートのすべてにおいてバランスが取れており、Redux Toolkit や Zustand などの主要ライブラリでも採用されています。新規プロジェクトではこれ一択と言えます。
immutability-helper と seamless-immutable は、特定のレガシーシステムを維持する場合を除き、新規採用は推奨しません。特に seamless-immutable はメンテナンスが停滞しており、immer で同等の安全性と利便性が得られるため、移行を検討すべきです。
最終的なアドバイス:状態管理で迷ったら immer を使い、不変性の強制が必要なら ESLint のルールや TypeScript の設定で補うのが、現在のエコシステムでは最も合理的な選択です。
新しいプロジェクトや Redux Toolkit を使用する場合は、迷わず immer を選択してください。可変データのようにコードを書けるため可読性が高く、TypeScript のサポートも充実しています。パフォーマンスと開発者体験のバランスが最も優れており、コミュニティでのサポートも厚いです。
プロキシが使えない古い環境や、更新ロジックを宣言的なコマンドで明確に定義したい場合に検討します。ただし、ネストが深いと構文が煩雑になるため、既存のレガシーコードを維持する場合を除き、新規採用は推奨されません。
厳密な不変性の強制が必要で、かつ誤って状態を変更してしまうことを防ぎたい古いプロジェクトでのみ使用を検討します。ただしメンテナンス頻度は低く、多くの機能が immer で代替可能なため、新規プロジェクトでの使用は避けるべきです。
Create the next immutable state tree by simply modifying the current tree
Winner of the "Breakthrough of the year" React open source award and "Most impactful contribution" JavaScript open source award in 2019
You can use Gitpod (a free online VSCode like IDE) for contributing online. With a single click it will launch a workspace and automatically:
yarn run start.so that you can start coding straight away.
The documentation of this package is hosted at https://immerjs.github.io/immer/
Did Immer make a difference to your project? Join the open collective at https://opencollective.com/immer!