immer

引用数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let currentState = {
p: {
x: [2],
},
}

// 哪些情况会修改原始对象
let o1 = currentState;
o1.p = 1;
o1.p.x = 1;

function fn(o) {
o.p1 = 1;
return o;
};
fn(currentState);

let o3 = {
...currentState
};

o3.p.x = 1;


let o4 = currentState;
o4.p.x.push(1);

解决方法

  1. 深度拷贝,但是如果数据量巨大,那么深度拷贝所消耗的成本较高,会影响性能;
  2. ImmutableJS,使用起来不够方便
  3. immer

immer简单使用方法

  1. 当我们调用 immer 的 API produce时,immer 将内部暂时存储着我们的目标对象
  2. immer 暴露一个 draft (草稿)给我们
  3. 我们在 draft 上作修改
  4. immer 接收修改后的draft,immer 基于传入的 目标对象 照着draft 的修改 返回一个新的 对象
    1
    produce(currentState, recipe: (draftState) => void | draftState, ?PatchListener): nextState
    produce
  • currentState

    被操作对象的最初状态

  • draftState

    根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的任何修改都将被记录并用于生成 nextState 。在此过程中,currentState 将不受影响

  • nextState

    根据 draftState 生成的最终状态

  • produce 生产

    用来生成 nextState 或 producer 的函数。

  • roducer 生产者

    通过 produce 生成,用来生产 nextState ,每次执行相同的操作。

  • recipe 生产机器

    用来操作 draftState 的函数。

自动冻结功能

Immer 还在内部做了一件很巧妙的事情,那就是通过 produce 生成的 nextState 是被冻结(freeze)的,(Immer 内部使用Object.freeze方法,只冻结 nextState 跟 currentState 相比修改的部分),这样,当直接修改 nextState 时,将会报错。这使得 nextState 成为了真正的不可变数据。

COW Copy-on-write

写时再进行复制,当我们复制资源操作只是利用一个引用将副本和原版共享资源,当我们去修改这个资源的时候再去进行创建副本。

有的时候复制这件事没多大必要,完全可以复用之前的,这时候可以只是引用之前的那份,在写内容的时候才去复制对应的一部分内容。这样如果内容用于读的话,就免去了复制,而如果需要写,才会真正复制部分内容来做修改。这种技术在操作系统的内存管理和文件系统中很常见。

COW策略应用

  • 虚拟内存管理:进程共享虚拟内存、fork()系统调用等
  • 存储:逻辑卷管理、文件系统、数据库快照
  • 编程语言:PHP、Qt 中的许多数据类型
  • 数据结构:实现不可变的数据结构,如状态树

Proxy

大家肯定对proxy都很熟悉了,利用proxy去监听数据变化,进行操作拦截。促使数据变为不可变的。

1
var proxy = new Proxy(target, handler)

target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

  • set(target,propKey,value,receiver):拦截对象属性的设置 (主要使用)

模拟一个简单的produce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}