文章在 github 开源, 欢迎 Fork 、Star !
前言Immer 是 mobx 的作者写的一个 immutable 库,核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构,简单易用、体量小巧、设计巧妙,满足了我们对JS不可变数据结构的需求。
无奈网络上完善的文档实在太少,所以自己写了一份,本篇文章以贴近实战的思路和流程,对 Immer 进行了全面的讲解。
先定义一个初始对象,供后面例子使用:
首先定义一个currentState对象,后面的例子使用到变量currentState时,如无特殊声明,都是指这个currentState对象
哪些情况会一不小心修改原始对象?
// Q1 let o1 = currentState; o1.p = 1; // currentState 被修改了 o1.p.x = 1; // currentState 被修改了 // Q2 fn(currentState); // currentState 被修改了 function fn(o) { o.p1 = 1; return o; }; // Q3 let o3 = { ...currentState }; o3.p.x = 1; // currentState 被修改了 // Q4 let o4 = currentState; o4.p.x.push(1); // currentState 被修改了 解决引用类型对象被修改的办法深度拷贝,但是深拷贝的成本较高,会影响性能;
ImmutableJS,非常棒的一个不可变数据结构的库,可以解决上面的问题,But,跟 Immer 比起来,ImmutableJS 有两个较大的不足:
需要使用者学习它的数据结构操作方式,没有 Immer 提供的使用原生对象的操作方式简单、易用;
它的操作结果需要通过toJS方法才能得到原生对象,这使得在操作一个对象的时候,时刻要主要操作的是原生对象还是 ImmutableJS 的返回结果,稍不注意,就会产生意想不到的 bug。
看来目前已知的解决方案,我们都不甚满意,那么 Immer 又有什么高明之处呢?
immer功能介绍 安装immer欲善其事必先利其器,安装 Immer 是当前第一要务
npm i --save immer immer如何fix掉那些不爽的问题Fix Q1、Q3
import produce from 'immer'; let o1 = produce(currentState, draft => { draft.p.x = 1; })Fix Q2
import produce from 'immer'; fn(currentState); // currentState 被修改了 function fn(o) { return produce(o, draft => { draft.p1 = 1; }) };Fix Q4
import produce from 'immer'; let o4 = produce(currentState, draft => { draft.p.x.push(1); })是不是使用非常简单,通过小试牛刀,我们简单的了解了 Immer ,下面将对 Immer 的常用 api 分别进行介绍。
概念说明Immer 涉及概念不多,在此将涉及到的概念先行罗列出来,阅读本文章过程中遇到不明白的概念,可以随时来此处查阅。
currentState
被操作对象的最初状态
draftState
根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的任何修改都将被记录并用于生成 nextState 。在此过程中,currentState 将不受影响
nextState
根据 draftState 生成的最终状态
produce 生产
用来生成 nextState 或 producer 的函数
producer 生产者
通过 produce 生成,用来生产 nextState ,每次执行相同的操作
recipe 生产机器
用来操作 draftState 的函数
使用 Immer 前,请确认将immer包引入到模块中
import produce from 'immer'or
import { produce } from 'immer'这两种引用方式,produce 是完全相同的
produce备注:出现PatchListener先行跳过,后面章节会做介绍
第1种使用方式:语法:
produce(currentState, recipe: (draftState) => void | draftState, ?PatchListener): nextState
例子1:
let nextState = produce(currentState, (draft) => { }) currentState === nextState; // true例子2:
let currentState = { a: [], p: { x: 1 } } let nextState = produce(currentState, (draft) => { draft.a.push(2); }) currentState.a === nextState.a; // false currentState.p === nextState.p; // true由此可见,对 draftState 的修改都会反应到 nextState 上,而 Immer 使用的结构是共享的,nextState 在结构上又与 currentState 共享未修改的部分,共享效果如图(借用的一篇 Immutable 文章中的动图,侵删):
自动冻结功能Immer 还在内部做了一件很巧妙的事情,那就是通过 produce 生成的 nextState 是被冻结(freeze)的,(Immer 内部使用Object.freeze方法,只冻结 nextState 跟 currentState 相比修改的部分),这样,当直接修改 nextState 时,将会报错。
这使得 nextState 成为了真正的不可变数据。
例子:
let nextState = produce(currentState, (draft) => { draft.p.x.push(2); }) currentState === nextState; // true 第2种使用方式利用高阶函数的特点,提前生成一个生产者 producer
语法:
produce(recipe: (draftState) => void | draftState, ?PatchListener)(currentState): nextState
例子:
let producer = produce((draft) => { draft.x = 2 }); let nextState = producer(currentState); recipe的返回值