一.简介
Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way.
Immer は、より便利な方法で不変状態を操作できるようにする小さなパッケージです
二.核心優位性
その便利な点は主に以下に現れます:
-
1 つの(コア)API のみ:
produce(currentState, producer: (draftState) => void): nextState -
追加のデータ構造を導入しない:List、Map、Set などのカスタムデータ構造はなく、したがって特別な等価性比較メソッドも不要
-
データ操作は完全にタイプに基づく:純粋なネイティブ API でデータを操作し、直感的
例えば:
const myStructure = {
a: [1, 2, 3],
b: 0
};
const copy = produce(myStructure, () => {
// nothings to do
});
const modified = produce(myStructure, myStructure => {
myStructure.a.push(4);
myStructure.b++;
});
copy === myStructure // true
modified !== myStructure // true
JSON.stringify(modified) === JSON.stringify({ a: [1, 2, 3, 4], b: 1 }) // true
JSON.stringify(myStructure) === JSON.stringify({ a: [1, 2, 3], b: 0 }) // true
Immutable が提供する 一整套のデータ構造とその操作 API と比較:
const { Map } = require('immutable');
const originalMap = Map({ a: 1, b: 2, c: 3 });
const updatedMap = originalMap.set('b', 1000);
// New instance, leaving the original immutable.
updatedMap !== originalMap;
const anotherUpdatedMap = originalMap.set('b', 1000);
// Despite both the results of the same operation, each created a new reference.
anotherUpdatedMap !== updatedMap;
// However the two are value equal.
anotherUpdatedMap.equals(updatedMap);
Immer は非常にシンプルに見えます
三.実装原理
2 つの重要なポイント:Copy-on-write と Proxy
Copy-on-write
概念
Copy-on-write (CoW or COW), sometimes referred to as implicit sharing or shadowing, is a resource-management technique used in computer programming to efficiently implement a "duplicate" or "copy" operation on modifiable resources.
書込時コピー(copy-on-write、略称 CoW または COW)、別名隠式共有(implicit sharing)またはシャドウ(shadowing)は、コンピュータプログラミングにおけるリソース管理技術で、変更可能なリソースの「複製」または「コピー」操作を効率的に実装するために使用されます
If a resource is duplicated but not modified, it is not necessary to create a new resource; the resource can be shared between the copy and the original. Modifications must still create a copy, hence the technique: the copy operation is deferred to the first write. By sharing resources in this way, it is possible to significantly reduce the resource consumption of unmodified copies, while adding a small overhead to resource-modifying operations.
具体的には、リソースを複製したが変更しない場合、この新しいリソースを作成する必要はなく、この時点でコピーはオリジナルと同じリソースを共有できます。変更時には依然としてコピーを作成する必要があります。したがって、鍵は:コピー操作を最初の書込み時まで遅延させる ことです。この方法でリソースを共有することで、変更されていないコピーのリソース消費を大幅に削減でき、リソース変更操作のオーバーヘッドをわずかに増加させるだけです
応用
COW 戦略は主に以下の方面で応用されます:
-
仮想メモリ管理:プロセス共有仮想メモリ、fork() システムコールなど
-
ストレージ:論理ボリューム管理、ファイルシステム、データベーススナップショット
-
プログラミング言語:PHP、Qt 中の多くのデータタイプ
-
データ構造:不変のデータ構造の実装、例えば状態ツリー
fork() システムコールを例に:

COW メカニズムを通じてプロセス間のメモリ共有を実現し、必要に応じてコピー
Immer と Copy-on-write
Immer において、Copy-on-write メカニズムはデータ構造のコピーによって生じるパフォーマンス負担を解決するために使用され、下図の通り:

データが変更された(write)時のみデータ構造をコピー(copy)し、否则同じものを共有 するため、したがって:
copy === myStructure // true
modified !== myStructure // true
Proxy
Proxy はネイティブデータ操作 API をフックする方法を提供します。例えば:
const data = { a: 1 };
const proxy = new Proxy(data, {
set(target, key, value, receiver) {
console.log(`Set key = ${key}, value = ${value}`);
return Reflect.set(target, key, value, receiver);
}
});
proxy.a = 2;
// 出力 Set key = a, value = 2
data.a === 2 // true
データ変化を監視できるだけでなく、操作インターセプト、甚至リダイレクトも許可します:
const data = { a: 1 };
const copy = {};
const p = new Proxy(data, {
set(target, key, value, receiver) {
// data に書き戻さない
// return Reflect.set(target, key, value, receiver);
// すべて copy に書き込む
Reflect.set(copy, key, value, copy);
}
});
p.a = 2;
data.a === 1 // true
copy.a === 2 // true
何を発見しましたか?
data はこのようにして不変のデータ構造になりました
P.S.Proxy 構文及び応用シーンに関する詳細情報は、[proxy(代理メカニズム)_ES6 ノート 9](/articles/proxy(代理机制)-es6 笔记 9/) を参照
Copy-on-write + Proxy
最初の例に戻ります:
const modified = produce(myStructure, myStructure => {
myStructure.a.push(4);
myStructure.b++;
});
私たちはProxy と Copy-on-write を魔法を通じて一体化してみます:
function produce(data, producer) {
let copy;
const copyOnWrite = value => {
copy = Object.assign({}, value);
};
const proxy = new Proxy(data, {
set(target, key, value, receiver) {
// 書込時コピー
!copy && copyOnWrite(data);
// すべて copy に書き込む
Reflect.set(copy, key, value, copy);
}
});
producer(proxy);
return copy || data;
}
P.S.注意、ここで提供される produce 実装は Immer 原理を説明するためだけに使用され、浅い bug が存在し、実用価値はありません
コア API produce が得られました:
produce(currentState, producer: (draftState) => void): nextState
Immer において、data 上の proxy は Draft(下書き)と呼ばれます:

非常に形象的で、下書き上の変更(つまり draftState への変更は、Copy-on-write メカニズムに従ってコピーされます)は元データに影響せず、下書き完成(つまり producer 実行完了)後、下書きに従って元データにパッチを当て、新しいデータを取得
非常に巧妙な設計 です。Photoshop 中のレイヤー操作のようです:
-
画像を開く
-
新規レイヤーを作成し、新規レイヤー上で塗る
-
レイヤーをマージ
参考資料
-
[Immer: Immutability the easy way](https://medium.com/ @mweststrate/introducing-immer-immutability-the-easy-way-9d73d8f71cb3)
コメントはまだありません