immer、immutability-helper、immutable、seamless-immutable はすべて、JavaScriptアプリケーションで不変性(immutability)を実現するためのライブラリです。これらは、オブジェクトや配列の更新時に元のデータを変更せずに新しいコピーを作成することで、予期しない副作用を防ぎ、状態管理を安全かつ予測可能にします。特にReactなどのフレームワークと組み合わせて、パフォーマンス最適化やバグの防止に貢献します。
フロントエンド開発において、状態の不変性(immutability)はバグの防止やパフォーマンス最適化の鍵です。しかし、JavaScriptの標準オブジェクトはミュータブル(変更可能)なため、意図せず元のデータを書き換えてしまうリスクがあります。この課題を解決するために登場した4つの代表的なライブラリ — immer、immutability-helper、immutable、seamless-immutable — を、実際の開発視点から深く比較します。
まず、共通のユースケースとして「ネストされたオブジェクトの一部を更新する」処理を各ライブラリでどう記述するかを見てみましょう。
immerimmerは「プロデューサー関数内で通常のミュータブル操作を書く」という革新的なアプローチを採用しています。内部でProxyを使って変更をトラッキングし、自動的に新しい不変オブジェクトを生成します。
import { produce } from 'immer';
const state = {
user: { name: 'Alice', settings: { theme: 'light' } }
};
const nextState = produce(state, draft => {
draft.user.settings.theme = 'dark'; // ミュータブルに見えるが、実際は不変
});
immutability-helperこのライブラリはコマンド形式で更新を指定します。$set、$pushなどの特殊キーを使い、更新内容を宣言的に記述します。
import update from 'immutability-helper';
const state = {
user: { name: 'Alice', settings: { theme: 'light' } }
};
const nextState = update(state, {
user: {
settings: {
theme: { $set: 'dark' }
}
}
});
⚠️ 注意:
immutability-helperは公式npmページで「This package is deprecated. Please use immer instead.」と明記されており、新規プロジェクトでの使用は推奨されません。
immutableimmutableは完全に独自のデータ型(Map、Listなど)を提供します。標準のJavaScriptオブジェクトとは互換性がなく、専用のAPIで操作する必要があります。
import { fromJS } from 'immutable';
const state = fromJS({
user: { name: 'Alice', settings: { theme: 'light' } }
});
const nextState = state.setIn(['user', 'settings', 'theme'], 'dark');
// 結果はImmutable.Mapなので、Reactで使うには.toJS()などで変換が必要
seamless-immutableこのライブラリはJavaScriptの標準オブジェクトを拡張し、setやmergeといったメソッドを追加します。見た目は普通のオブジェクトですが、内部で不変性を保証します。
import Immutable from 'seamless-immutable';
const state = Immutable({
user: { name: 'Alice', settings: { theme: 'light' } }
});
const nextState = state.setIn(['user', 'settings', 'theme'], 'dark');
// 戻り値は依然として通常のオブジェクトのように扱える
immer:既存のJavaScriptの書き方をそのまま使えるため、学習コストがほぼゼロ。特にReduxのcreateReducerなどと組み合わせると、コードが非常に読みやすくなります。immutability-helper:コマンド形式のため、パスが深くなると冗長で可読性が低下します。例えば、{ a: { b: { c: { $set: value } } } } のように階層がネストされます。immutable:専用のAPI(getIn、setIn、updateInなど)を使う必要があり、標準のobj.propアクセスができません。また、toJS()による変換はパフォーマンスオーバーヘッドを生みます。seamless-immutable:setInやmergeといったメソッドは直感的ですが、標準のオブジェクトと混在させると予期しない挙動を引き起こすことがあります(例:typeof obj === 'object'がtrueでも、obj.setInが存在しない場合)。immer:型推論が非常に優れており、プロデューサー関数内のdraftも完全に型安全です。immutability-helper:型定義はありますが、コマンド形式のため型安全性が限定的です。immutable:独自の型システムを持つため、標準の型との相互運用には注意が必要です。seamless-immutable:型定義はありますが、拡張されたオブジェクトの型を正しく扱うにはラッパー関数が必要になる場合があります。immer:Proxyを使用するため、IE11や古いAndroidブラウザでは動作しません。ただし、immerはenableES5()というレガシー対応モードも提供しています(パフォーマンスは劣化します)。immutability-helper:ES5互換のため、古い環境でも動作しますが、非推奨のため使用すべきではありません。immutable:純粋なJavaScriptで実装されているため、幅広い環境で動作します。seamless-immutable:Object.freezeを再帰的に使用するため、ES5以上で動作しますが、大規模オブジェクトではフリーズ処理が重くなります。immutable:永続的データ構造(Persistent Data Structures)を採用しており、共有部分を再利用するため、大規模データでもメモリ効率が高いです。immer:変更箇所のみをコピーするため、一般的なユースケースでは十分な効率を発揮します。seamless-immutable:完全なディープコピーを行うため、大きなオブジェクトではメモリ使用量が増加します。immutability-helper:同様にディープコピーに基づくため、大規模データでは非効率です。immerimmerを採用しており、公式推奨のパターンです。ミュータブル風の記法で reducer を書けるため、コードがシンプルになります。// Redux Toolkit + immer
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setTheme: (state, action) => {
state.user.settings.theme = action.payload; // OK!
}
}
});
immutableseamless-immutableimmutable または immer + enableES5()immerのES5モードは利用可能ですが、パフォーマンスが犠牲になるため、本当に必要か検討すべきです。| ライブラリ | 推奨度 | 主な特徴 | 制限事項 |
|---|---|---|---|
immer | ⭐⭐⭐⭐⭐ | ミュータブル風記法、高開発体験、TypeScript対応 | Proxy未対応環境では使えない(ES5モードあり) |
immutability-helper | ⚠️ 非推奨 | コマンド形式、ES5互換 | 公式で非推奨、新規使用不可 |
immutable | ⭐⭐⭐☆ | 高性能、大規模データ向け | 専用API、変換オーバーヘッド |
seamless-immutable | ⭐⭐☆ | 標準オブジェクト拡張、直感的API | 大規模データで非効率、メンテナンス不安 |
immerを検討してください。開発速度、可読性、メンテナンス性のバランスが最も優れています。immutability-helperは絶対に使わないでください。公式で非推奨と明言されています。immutableを検討します。その際は、チーム全体の学習コストを考慮してください。seamless-immutableは、既存コードとの互換性が最重要で、かつデータサイズが小さい場合に限定して検討します。不変性は現代のフロントエンド開発において必須の概念ですが、ライブラリ選びは「技術的要件」と「チームの開発文化」の両方を考慮することが成功の鍵です。
immerは「ミュータブルな書き方でイミュータブルな更新を行う」という独自のアプローチを採用しており、開発体験が非常に自然です。TypeScriptとの親和性も高く、学習コストが低いため、新規プロジェクトや既存コードベースへの導入に最適です。内部でProxiesを使用しているため、ネストの深いオブジェクトでも直感的に操作できます。ただし、IE11などProxyをサポートしない環境では使用できません。
immutability-helperはReact公式ドキュメントで過去に紹介されていたライブラリですが、現在は非推奨(deprecated)と明記されており、新規プロジェクトでの使用は避けるべきです。代わりにimmerやimmutableなどの代替手段を検討してください。このライブラリはコマンド形式(例: {$set: ...})で更新を記述するため、可読性が低く、型安全性にも劣ります。
immutableは完全に独自のデータ構造(Map、Listなど)を提供し、高いパフォーマンスとメモリ効率を実現します。大規模なデータセットや頻繁な更新が必要なケースに向いていますが、JavaScriptの標準オブジェクトとは異なるAPIを強制されるため、学習コストとコードの複雑さが増します。また、他のライブラリやAPIとの連携には変換処理(toJS()など)が必要になる点に注意が必要です。
seamless-immutableはJavaScriptの標準オブジェクトや配列を拡張し、setやmergeなどのメソッドで不変性を保ちながら操作できるようにします。APIが直感的で、既存のオブジェクト指向コードとの親和性が高いのが特徴です。ただし、内部で再帰的な凍結(Object.freeze)を利用しているため、大規模なオブジェクトではパフォーマンスが劣化する可能性があります。また、近年の更新が少なく、メンテナンス状況に注意が必要です。
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!