愚墨的博客
  • 首页
  • 前端技术
  • 面试
只争朝夕不负韶华
  1. 首页
  2. 前端框架
  3. React
  4. 正文

Immutable & Immer

2019年03月05日 3841点热度 0人点赞 0条评论

文章来源于我在组内给同事分享的 Immer 和 immutable 的使用和对比,总结一下。

两个库都是用来解决数据的Immutable 问题的。但是使用上有很大区别。

Shared mutable state is the root of all evil(共享的可变状态是万恶之源)

Immutable

优点

  1. Immutable 降低了 Mutable 带来的复杂度 可变(Mutable)数据耦合了 Time 和 Value 的概念,造成了数据很难被回溯。
  2. 节省内存 Immutable.js 使用了 Structure Sharing (结构共享) 会尽量复用内存。没有被引用的对象会被垃圾回收。
    import { Map} from 'immutable';
    let a = Map({
      select: 'users',
      filter: Map({ name: 'Cam' })
    })
    let b = a.set('select', 'people');
    
    a === b; // false
    
    a.get('filter') === b.get('filter'); // true

    上面 a 和 b 共享了没有变化的 filter 节点。

  3. Undo/Redo(撤销重写),Copy/Paste,甚至时间回溯这些功能做起来小菜一碟
  4. 并发安全 因为js 单线程,所以没啥卵用
  5. 拥抱函数式编程 Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。

函数式编程的本质:函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。

缺点

  1. 容易与原生对象混淆 虽然 Immutable.js 尽量尝试把 API 设计的原生对象类似,有的时候还是很难区别到底是 Immutable 对象还是原生对象,容易混淆操作。 Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 map.get('key') 而不是 map.key,array.get(0) 而不是 array[0]。另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。
  2. 当使用外部库的时候,一般需要使用原生对象,也很容易忘记转换。

下面给出一些办法来避免类似问题发生:

  • 使用 Flow 或 TypeScript 这类有静态类型检查的工具
  • 约定变量命名规则:如所有 Immutable 类型对象以 $$ 开头。
  • 使用 Immutable.fromJS 而不是 Immutable.Map 或 Immutable.List 来创建对象,这样可以避免 Immutable 和原生对象间的混用

 

API

太多了,图上也不是很全

 

 

 

总结: 功能很好,就是太难用,侵入性太强。放弃!

 

 

Immer

 

常用API就一个produce,掌握之后就可以应对绝大多数业务场景

produce

几个基本概念

  • currentState 被操作对象的最初状态
  • draftState 根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的任何修改都将被记录并用于生成 nextState 。在此过程中,currentState 将不受影响
  • nextState 根据 draftState 的修改生成的最终状态
  • recipe 操作方法 用来操作 draftState 的函数
produce(currentState, recipe: (draftState) => void): nextState

produce的第一种使用姿势

let currentState = {
  x:1,
  y:1
}
let nextState = produce(currentState, (draft) => {
  draft.x = 2;
})

console.log(currentState);  // {x:1,y:1}
console.log(nextState); // { x: 2, y: 1 }
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 gif的样子

自动冻结

 

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

let currentState = {
  a: [],
  p: {
    x: [1]
  }
}
let nextState = produce(currentState, (draft) => {
  draft.p.x.push(2);
})
// nextState.p.x.push(3);  报错
nextState.a.push(1)
nextState.c = 3; // 无效
console.log(nextState); // { a: [ 1 ], p: { x: [ 1, 2 ] } }

produce的第二种使用姿势

produce(recipe: (draftState) => void | draftState)(currentState): nextState

传入的第一个参数是函数

高阶函数的写法,提前生成一个producer方法,当producer 方法被调用的时候,它会把第一个参数用作你希望改变的 currentState

let producer = produce((draft) => {
  draft.x = 2
});
let nextState = producer(currentState);

正因为有这个操作,所以我们可以很方便使用到setState 中,因为 setState 有接收函数作为参数的能力。

// 想要更新state 中的name = 'BBB';
let producer = produce((draft)=>{
  draft.name='BBB'
})
this.setState(producer)

注意recipe的返回值

  • recipe 没有返回值时:nextState 是根据 recipe 函数内的 draftState 生成的;

  • recipe 有返回值时:nextState 是根据 recipe 函数的返回值生成的

但是不能同时修改了draftState 也 return 一个新的state,否则会报错,提示你只能修改draft 或者 return value;

let currentState = {
  name: 'aaa'
}

let nextState = produce(currentState,(draft)=>{
  draft.name = 'bbb';
 // return {v:'v'}
})

console.log(nextState)

Patches 补丁

Patches 可以在recipe执行期间记录所有操作,方便去做一些数据回滚或者是数据跟踪调试啥的。

patch 对象长这个模样

interface Patch {
  op: "replace" | "remove" | "add" // 一次更改的动作类型
  path: (string | number)[] // 此属性指从树根到被更改树杈的路径
  value?: any // op为 replace、add 时,才有此属性,表示新的赋值
}

语法

produce(
  currentState, 
  recipe,
  // 通过 patchListener 函数,暴露正向和反向的补丁数组
  patchListener: (patches: Patch[], inversePatches: Patch[]) => void
)

新API applyPatches

// Demo 1
let state1 = {
  x: 1,
  y: 1
}
console.log(state1);
let patches1 = [];
let inversePatches1 = [];

let state2 = produce(state1, (draft) => {
  draft.x = 2;
  draft.y = 2;
}, (patches, inversePatches) => {
  patches1 = patches;
  inversePatches1 = inversePatches;
});

console.log(state2);

let patches2 = [];
let inversePatches2 = [];

let state3 = produce(state2, (draft) => {
  draft.x = 3;
  draft.y = 3;
}, (patches, inversePatches) => {
  patches2 = patches;
  inversePatches2 = inversePatches;
});

console.log(state3);

let backState2 = applyPatches(state3,inversePatches2)

console.log(backState2);

let backState1 = applyPatches(state2,inversePatches1)

console.log(backState1);

不允许跨级回滚

let state1 = {
  list:[
    {
      name:'thor'
    },
    {
      name:'Rogers'
    },
  ]
}
console.log(state1);

let patches1 = [];
let inversePatches1 = [];

let state2 = produce(state1, (draft) => {
  draft.list.push({name:'Natasha'});
  draft.list.shift();
}, (patches, inversePatches) => {
  patches1 = patches;
  inversePatches1 = inversePatches;
});
console.log(state2);
let patches2 = [];
let inversePatches2 = [];
let state3 = produce(state2, (draft) => {
  delete draft.list;
  draft.world = [{name:'Marvel'}]
}, (patches, inversePatches) => {
  patches2 = patches;
  inversePatches2 = inversePatches;
});

console.log(state3);

let backState2 = applyPatches(state3,inversePatches2)

console.log(backState2);

let backState1 = applyPatches(state2,inversePatches1)

console.log(backState1);

createDraft、finishDraft

let currentState = {
  x: 1
}
let draft = createDraft(currentState);
draft.x=2;
const nextState = finishDraft(draft);
console.log(currentState);
console.log(nextState);

 

Immer 完败 Immutable,用immer 得永生。

标签: 暂无
最后更新:2019年07月05日

愚墨

保持饥渴的专注,追求最佳的品质

点赞
< 上一篇
下一篇 >

文章评论

取消回复

搜搜看看
历史遗迹
  • 2023年5月
  • 2022年9月
  • 2022年3月
  • 2022年2月
  • 2021年12月
  • 2021年8月
  • 2021年7月
  • 2021年5月
  • 2021年4月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年9月
  • 2020年7月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年1月
  • 2019年5月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年9月
  • 2018年3月
  • 2018年2月
  • 2018年1月
  • 2017年11月
  • 2017年7月
  • 2017年6月
  • 2017年3月
  • 2017年2月
  • 2017年1月
  • 2016年12月
  • 2016年11月
  • 2016年9月
  • 2016年8月
  • 2016年7月
  • 2016年6月
  • 2016年5月
  • 2016年4月
  • 2016年3月
  • 2016年2月
  • 2016年1月
  • 2015年12月
  • 2015年10月
  • 2015年9月
  • 2015年7月
  • 2015年6月
  • 2015年4月

COPYRIGHT © 2020 愚墨的博客. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS