본문으로 건너뛰기

Immer

무료2019-10-13#Front-End#JS#JavaScript immutable#immutable JavaScript#JS 不可变数据结构#React immutable#Redux immutable

이상적인 불변 데이터 구조가 드디어 나타났다

一.简介

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 중의 레이ヤー 조작과 같습니다:

  • 이미지를 열기

  • 신규 레이ヤー를 생성하고, 신규 레이ヤー 위에서 칠하기

  • 레이ヤー를 병합

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성