一.简介
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)
아직 댓글이 없습니다