Immer 实战讲解

文章在 github 开源, 欢迎 Fork 、Star !

前言

Immer 是 mobx 的作者写的一个 immutable 库,核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构,简单易用、体量小巧、设计巧妙,满足了我们对JS不可变数据结构的需求。
无奈网络上完善的文档实在太少,所以自己写了一份,本篇文章以贴近实战的思路和流程,对 Immer 进行了全面的讲解。

数据处理存在的问题

先定义一个初始对象,供后面例子使用:
首先定义一个currentState对象,后面的例子使用到变量currentState时,如无特殊声明,都是指这个currentState对象

let currentState = { p: { x: [2], }, }

哪些情况会一不小心修改原始对象?

// 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 的函数

常用api介绍

使用 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的返回值

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppjgz.html