I. Introduction
Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way.
Immer provides a more convenient way to work with immutable state
II. Core Advantages
Its convenience is mainly reflected in:
-
Only one (core) API:
produce(currentState, producer: (draftState) => void): nextState -
No introduction of additional data structures: No custom data structures like List, Map, Set, etc., therefore no need for special equality comparison methods
-
Data operations are completely type-based: Operate data with pure native APIs, conforms to intuition
For example:
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
Compared to Immutable's provided complete set of data structures and their operation APIs:
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 appears too concise
III. Implementation Principle
Two key points: Copy-on-write and Proxy
Copy-on-write
Concept
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 or COW), also called implicit sharing or shadowing, is a resource management technique in computer programming used to efficiently copy or duplicate modifiable resources
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.
Specifically, if a resource is copied but not modified, there's no need to create this new resource, at this point the copy can share the same resource with the original, still need to create a copy when modifying. Therefore, the key is: defer the copy operation to the first write. By sharing resources in this way, it can significantly reduce resource consumption of unmodified copies, while only slightly increasing overhead of resource modification operations
Application
COW strategy is mainly applied in the following aspects:
-
Virtual memory management: Process sharing virtual memory, fork() system calls, etc.
-
Storage: Logical volume management, file systems, database snapshots
-
Programming languages: Many data types in PHP, Qt
-
Data structures: Implementing immutable data structures, such as state trees
Taking fork() system call as an example:

Implement memory sharing between processes through COW mechanism, copy on demand
Immer and Copy-on-write
In Immer, Copy-on-write mechanism is used to solve performance burden produced by copying data structures, as shown below:

Only copy data structure (copy) when data changes (write), otherwise share the same one, therefore:
copy === myStructure // true
modified !== myStructure // true
Proxy
Proxy provides a way to Hook native data operation APIs, for example:
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;
// Output Set key = a, value = 2
data.a === 2 // true
Not only can detect data changes, but also allows operation interception, even redirection:
const data = { a: 1 };
const copy = {};
const p = new Proxy(data, {
set(target, key, value, receiver) {
// Don't write back to data
// return Reflect.set(target, key, value, receiver);
// Write everything to copy
Reflect.set(copy, key, value, copy);
}
});
p.a = 2;
data.a === 1 // true
copy.a === 2 // true
Discovered something?
data thus becomes an immutable data structure
P.S. For more information about Proxy syntax and application scenarios, see [proxy (Proxy Mechanism)_ES6 Notes 9](/articles/proxy(代理机制)-es6 笔记 9/)
Copy-on-write + Proxy
Back to the original example:
const modified = produce(myStructure, myStructure => {
myStructure.a.push(4);
myStructure.b++;
});
We try to integrate Proxy with Copy-on-write through magic:
function produce(data, producer) {
let copy;
const copyOnWrite = value => {
copy = Object.assign({}, value);
};
const proxy = new Proxy(data, {
set(target, key, value, receiver) {
// Copy on write
!copy && copyOnWrite(data);
// Write everything to copy
Reflect.set(copy, key, value, copy);
}
});
producer(proxy);
return copy || data;
}
P.S. Note, the produce implementation provided here is only used to explain Immer principle, has shallow bugs, not of practical value
Got the core API produce:
produce(currentState, producer: (draftState) => void): nextState
In Immer, proxy above data is called Draft:

Very vivid, modifications on the draft (i.e., modifications to draftState, will be copied according to Copy-on-write mechanism) don't affect source data, after draft is completed (i.e., producer execution completes), patch source data according to draft, get new data
Very clever design, just like layer operations in Photoshop:
-
Open image
-
Create new layer, paint on the new layer
-
Merge layers
No comments yet. Be the first to share your thoughts.