immutability-helper 学习笔记 -1

本来想将有关于immutability-helper的博文放在一起学React系列博文中,但是考虑到该插件不仅仅在React中实用到,所以就单独拿出来分两期写。

发现问题

immutability意为不变,不变性,永恒性。至于该插件能做什么,我想它的作者对它的标注已经很明确了mutate a copy of data without changing the original source,意为:在不改变原始来源的情况下改变数据副本
同时在这里笔者再推荐另一个与之相似的插件,那就是Facebook出品的immutable-js (我们可以认为immutability-helper是immutable-js的终极简化版)。脸书对immutable-js的标注也很明确:Immutable persistent data collections for Javascript which increase efficiency and simplicity,意为:使得Javascript中的持久数据集合保持不可变,从而提高效率和简单性

仔细阅读他们对两个插件格子的描述,我们能得到一个关键字,那就是不可变。为什么要不可变?在这里笔者觉得从React角度来解释可能会更加清晰。我们都知道React组件本身会包含N个状态,而且这些状态会通过setState方法去更新,也就是说React组件的状态是可变的,实际上本就是这么一回事。但是作为React的初学者可能会误解状态是可变的这句话。怎么说?因为对于React组件而言,状态的变化是State的变化而不是State值的变化,什么?看不懂?那我们继续往下说。假如我们初始化了这样一个状态:

this.state={
    name:'xiaoming',
    hobbies:['qq','wx']
}

现在才是解释什么叫State的变化而不是State值的变化。假如我们先这样做:

this.setState({
    name:'hanmeimei'
})

经过这个操作以后,name对应的DOM节点会得到更新,而如果我们这样做:

function changeHobby() {
    let hobbies = this.state.hobbies;
    hobbies.push('Coding');
    this.setState({
        hobbies: hobbies
    })
}

引出问题

运行了这个方法以后会发生什么?Nothing Happened, But why?
这个问题也是经常发生在React初学者身上,因为React在检测状态变化时候是一个浅检测,换句话说就是只关注name或者hobbies这两个key对应的value有没有变化,有变化就重新Render否则不做任何动作。那如何去检测value有没有变化,其实就是根据变量的地址。我们都知道变量在内存中都有唯一的地址。拿刚刚的两个例子来说,为什么更新了name和更新了hobbies这两个状态会有两个不同的结果?那是因为在JavaScritp中,string就是被设计成不可变,而数组则可变。换句话说我们无法在保证字符串内存地址不变的情况下改变字符串但却可以保证数组在内存地址不变的情况下增加或者删除其中的某一个元素。所以这就是为什么我们更新hobbies这个状态的情况组件并没有刷新的原因,我们用例子来证明下数组这一特性:

let hobbies = ['qq', 'wx'];
let hobbies_2 = hobbies;
hobbies_2.push('Coding');

console.log(hobbies === hobbies_2);

可以看出这两个变量实际上是同一个。
再举个例子并且是在日常开发中肯定会遇到过的。假如现在要将后台的传来的数据渲染成一个列表,数据如下:

[
    {'id':0,name:'xiaoming'},
    {'id':1,name:'lilei'},
    {'id':2,name:'hanmeimei'},
]

这是一个很简单的操作。然后我们做了一个更新的功能,当我们将id为1的那条的姓名改成'Miss Li',那该怎么办才能保证在setState后让该列表组件重新渲染?估计大家心里立刻有两个方案,一个是重新获取后台数据然后重新渲染,另一个是重新拷贝一份数据然后更改相应的地方后重新set给某个State完成重新渲染。但是我们可以看出,这两个操作最终的效果仍然是创建另一个与初始数组在内容上完全相同的数组,虽然相对繁琐但是着实有效。不过这两个方案各有缺点,比如第一个方案需要额外增加一次网络请求,第二个是万一数据量过于庞大就会造成内存的浪费。
既然这样有什么更好的解决方案呢?到这里我相信大家就明白了该篇博客的用意了,因为这个immutability-helper在笔者近期项目中使用最频繁,所以觉得有必要拿出来说一说。

immutability-helper使用方法

使用方法很简单,首先是安装依赖

npm install immutability-helper --save

然后是在有需要的地方引入即可

import update from 'immutability-helper';

这样我们可以使用update这个方法做我们想做的事情了。

那就有人问了,为什么它就能完美的解决问题呢?其实很简单,就那刚刚的列表数据来说,immutability-helper会输出一个全新的数组对象并且只会更新跟id=1有关的那条数据,剩下的通过 地址引用的方式引入到新数组中,这样会最大限度的使用数据和内存。

immutability-helper工作方式

immutability-helper的工作方式也很简单,通过在update方法在使用指令就可以实现对数据的修改。
immutability-helper的指令组成也很简单:$+关键字。而且这个关键字我们仍然很熟悉,接着往下看。它支持的指令一共有:

  • {$push: array} push() all the items in array on the target.
  • {$unshift: array} unshift() all the items in array on the target.
  • {$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item. Note: The items in the array are applied sequentially, so the order matters. The indices of the target may change during the operation.
  • {$set: any} replace the target entirely.
  • {$toggle: array of strings} toggles a list of boolean fields from the target object.
  • {$unset: array of strings} remove the list of keys in array from the target object.
  • {$merge: object} merge the keys of object with the target.
  • {$apply: function} passes in the current value to the function and updates it with the new returned value.
  • {$add: array of objects} add a value to a Map or Set. When adding to a Set you pass in an array of objects to add, when adding to a Map, you pass in [key, value] arrays like so: update(myMap, {$add: [['foo', 'bar'], ['baz', 'boo']]})
  • {$remove: array of strings} remove the list of keys in array from a Map or Set.

这些指令关键字是不是很熟悉?因为我们在日常开发中经常使用到这些。下面就开始一个一个介绍。

指令使用

$push

push,顾名思义和数组有关,其实就是向源数组中增加一个元素并且输出一个内容想通了的新数组。看例子:

var update = require("immutability-helper");
const state1 = ["x"];
const state2 = update(state1, { $push: ["y"] }); // ['x', 'y']

console.log(state1, state2);
console.log(`state1===state2: ${state1===state2}`);

输出结果:

immutability-helper 学习笔记 -1_第1张图片

同时也来熟悉下update方法的使用:update方法接受两个参数,第一个是源数据,这个很好理解;第二个是 操作线(笔者YY出来的),用来描述我们如何去操作源数据, key是指令,value是指令所需要的数据

$unshift

unshift的作用就是向源数组的开头批量添加元素

var update = require("immutability-helper");
const state1 = ["x"];
const state2 = update(state1, { $unshift: ["y","Z"] });

console.log(state1, state2);

输出结果:

immutability-helper 学习笔记 -1_第2张图片

$splice

splice的作用就是向源数组中添加/删除元素。参数接受多个数组,每个数组为一组操作。每一组与实际splice方法的参数相同。

var update = require("immutability-helper");
const state1 = [0, 1, 2, 4];
const state2 = update(state1, {
    $splice: [
        [3, 1, 3, 4, 5, 6, 7]
    ]
});

console.log(state1, state2);

输出结果:
immutability-helper 学习笔记 -1_第3张图片

$set

set指令被用来改成字面量对象中的某个key的值。

var update = require("immutability-helper");
const data = { 'id': 0, name: 'xiaoming' };
const data2 = update(data, { name: { $set: 'Miss Li' } });
console.log(data, data2);

输出结果:

immutability-helper 学习笔记 -1_第4张图片

$toggle

toggle意为切换,该方法是用来对Boolean对象进行切换,比如True切换为False

var update = require("immutability-helper");
const data = [true, false];
const data2 = update(data, { $toggle: [0] });
console.log(data, data2);

输出结果:
immutability-helper 学习笔记 -1_第5张图片
使用方法如上,针对数组中的第1个Boolean值做切换处理。

本篇先介绍这5个指令方法。剩下的5个我们下一篇继续。
接下来我们尝试从上面这5个指令中找出相应指令去解决我们前面提到的表格数据的问题:
首先是表格的数据:

[
    {'id':0,name:'xiaoming'},
    {'id':1,name:'lilei'},
    {'id':2,name:'hanmeimei'},
]

可以分析出我们需要对两中对象进行处理,一个是数组,一个是字面量对象。
然后假如我们修改了id=1的那条记录的‘name’属性,‘name=张伟’,该怎么做?
首先第一步我们要找到id=1的那条记录的index,这里是1;然后是确定需要更改的字段和更改后的值。

var update = require("immutability-helper");
const data = [
    { 'id': 0, name: 'xiaoming' },
    { 'id': 1, name: 'lilei' },
    { 'id': 2, name: 'hanmeimei' },
]
const data2 = update(data, { 1: { name: { $set: '张伟' } } });
console.log(data[1]['name'], data2[1]['name']);
console.log(`data===data2: ${data===data2}`);

输出结果:

clipboard.png

这样就实现了在源数据的基础上更改了值并且输出一个与之地址完全不同数组。

你可能感兴趣的