immutable入坑指南

1,245 阅读4分钟
原文链接: www.aliued.com

本文作者:冬柠

前言

今天在用 immutable 改写以前的旧代码。数据的嵌套结构有可能很深的情况, 内部对于数据的增删改查和各种循环递归操作有很多。用了 immutable.js (其实也配合使用了TypeScript,效果更佳) 来改造了下。一个总结体验那就是~

immutable初相识

immutable 的英文意思是“不可变的”。从名字就已经体现出 immutable.js 的精髓了。在immutable的世界观里, 一切都是不可变的数据。对 Immutable 数据的修改会返回一个新的 Immutable 对象。
而传统的Js里十分让人头疼的一点就是对象的数据传递是引用传递, 更新了新的对象也会影响原有的对象。从而使用各种 deepExtend, deepCopy方法来解决这个问题。而使用immutable.js就没有这个烦恼。
举个栗子:

let obj1 = Immutable.Map({ a: 1, b: 2, c: 3 })
let obj2 = obj1
obj2 === obj1
// return true(此时obj2实例和obj1实例指向了同一个地址)
obj2 = obj2.set("b",4)
// 此时obj2已经被更新,指向了另一个地址
obj2 === obj1
// return false
// 改变obj2并不影响obj1

常用的概念和操作

常用的对象

  1. Map: 键值对结构,通常对应js中的object结构转换过来的类型。但是和js的object的很多特性有明显的区分。
    Object只接受字符串作为键名,然而Map接受任何类型的数据作为键名。
let obj = { 1: "one" }
Object.keys(obj) // [ "1" ]
console.log(obj["1"], obj[1]) // "one","one"
let map = Map(obj)
console.log(map.get("1"), map.get(1)) // "one", undefined

Map支持键值对的遍历(map,forEach,filter)等操作。大大方便了js中Object.keys()来遍历对象的写法
2. OrderedMap: 一种有序的Map类型。弥补了Object对象无序的缺憾。
3. List: 有序列表, 对应js中的Array
4. Set: 不可重复的列表

常用的操作

1.和js数据类型相互转化
  • Immutable.Map({}) 对象换成Map
  • Immutable.List([]) 数组换成List
  • Immutable.formJS()
  • 识别传入的数据并自动转换成相应Map | List类型
  • 对于Immutable对象, 有 toJS(), toJSON(), toArray(), toObject()方法转换成相应的原生Js结构。
2.判断两个对象的值是否相等
let obj1 = Immutable.Map({ a: 1, b: 2, c: 3 })
let obj2 = Immutable.Map({ a: 1, b: 2, c: 3 })
Immutable.is(obj1, obj2); // or obj1.equals(obj2)
// return ture
3.深度取值
let map1 = Immutable.Map({ a: {d: { target : 1}}, b: 2, c: 3 })
const target = Immutable.getIn(map1, ["a","d","target"])
// target: 1
4.深度赋值

有一个cursor(游标)的概念, 可以进行深度赋值的操作。但会在下一个版本即将被废弃。建议使用setIn来代替。

let map1 = Immutable.Map({ a: {d: { target : 1}}, b: 2, c: 3 })
map1 = Immutable.setIn(map1, ["a","d","target"], 4)
// map1: { a: {d: { target : 4}}, b: 2, c: 3 }
5.浅合并(类似于Object.assign, 只合并了第一层)
const map1 = Immutable.Map({ a: {d: { target : 1}}});
const map2 = Immutable.Map({ a: {c: 1}});
const map3 = Immutable.merge(map1,map2)
console.log(map3.toJS())
// { a: {c: 1}}
6.深度合并
const map1 = Immutable.Map({ a: {d: { target : 1}}});
const map2 = Immutable.Map({ a: {c: 1}});
const map3 = Immutable.mergeDeep(map1,map2)
console.log(map3.toJS())
// { a: {c: 1, d: { target : 1}}}}

优势

1.节省内存

  • 使用了Trie数据结构来进行储存。使用了结构共享,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
    比如对象
const data = {
to: 7,
tea: 3,
ted: 4,
ten: 12,
A: 15,
i: 11,
in: 5,
inn: 9
}

根据trie结构,存储的结构类似于

如果更改了了tea字段 3 -\> 14,那么只需改变四个节点,来更新形成新的树, 这就是结构共享。

2.提升计算性能

使用了 Trie数据结构。避免深度比较。如果两个对象的值相等。那么他们的hashCode也相等。从而比较hashCode即可比较两个对象的值是否相等,避免了深度比较。

3.拥抱函数式编程

4.使用便利

  • 因为数据是不可变的。同时提供的API面向开发者也十分友好。

使用immutable的注意事项

1.变量命名

非常值得注意的一个点。因为可能引用了其他的库或者文件,使得代码里存在immutable数据和非immutable数据。所以immutable变量需要加上 ? 前缀来区分

2.API上的习惯

赋值操作之后要修改原数据记得要赋值。不然无法更新数据。
?data = ?data.set("hello","immutable")

3.无法使用function传值来修改数据

let ?data = Map({hello: "immutable"});
function changeData(?data){
?data = ?data.set("hello","world")
}
changeData(?data)
console.log(?data.toJS())
// {hello: "immutable"}

如上, ?data并不会改变。所以在写改变数据的方法的时候需要返回新值。然后覆盖旧值。

let ?data = Map({hello: "immutable"});
function changeData(?data){
return ?data.set("hello","world")
}
?data = changeData(?data)
console.log(?data.toJS())
// {hello: "world"}

4.为了性能和节省内存, Immutable.js 会努力避免创建新的对象。如果没有数据变化发生的话。

const { Map } = require('immutable')
const originalMap = Map({ a: 1, b: 2, c: 3 })
const updatedMap = originalMap.set('b', 2)
updatedMap === originalMap // return ture

如上代码虽然进行了一顿操作。然而数据并没有改变。所以updatedMap和originalMap还是指向了同一个对象。

const updatedMap = originalMap.set('b', 4)
updatedMap === originalMap
// return false

此时返回false

5.Immutable的使用场景

虽然immutable有种种好处, 但并不是在所有场景都建议使用Immutable, 对于数据结构复杂,操作很多的使用immutable比较合适。但对于简单的应用场景, 还是使用原生的js吧。

文章参考