本来想把immutability-helper的博文放在一起写在immutability-helper的系列博文中,但是考虑到该插件不仅仅在React中有用,所以就把把它分开出来,分成两个阶段写。发现问题immutability意味着不变性、不变性、永恒性。至于这个插件能做什么,我想它的作者已经明确标示为mutateacopyofdatawithoutchangingtheoriginalsource,意思是:在不改变原始来源的情况下改变数据的副本。同时,笔者在这里推荐另一个类似的插件,就是Facebook出品的immutable-js(我们可以把immutability-helper看成是immutable-js的终极简化版)。Facebook对immutable-js的注解也很明确:ImmutablepersistentdatacollectionsforJavascriptwhichincreasedefficiencyandsimplicity,意思是:让Javascript中的持久化数据集合保持不可变,从而提高效率和简洁性。仔细阅读他们对两个plugingrids的描述,我们可以得到一个关键词,就是immutable。为什么不可变?这里我觉得从React的角度来解释可能会更清楚。我们都知道React组件本身会包含N个状态,而这些状态会通过setState方法进行更新,也就是说React组件的状态是可变的,实际上就是这样。但是作为React初学者,您可能会误解状态是可变的这句话。怎么说?因为对于React组件来说,state的变化是State的变化而不是State值的变化,什么?无法阅读?那我们继续吧。如果我们初始化这样一个state:this.state={name:'xiaoming',hobbies:['qq','wx']}现在来说明什么叫State变化,而不是State值变化。如果我们先这样做:this.setState({name:'hanmeimei'})在这个操作之后,name对应的DOM节点将被更新,如果我们这样做:functionchangeHobby(){lethobbies=this.state.爱好;hobbies.push('编码');this.setState({hobbies:hobbies})}提出了一个问题,运行这个方法后会发生什么?什么都没发生,但为什么呢?这个问题经常发生在React初学者身上,因为React在检测状态变化时属于浅层检测。也就是说,它只关注name或者hobbies这两个key对应的value是否发生了变化。如果有变化则重新渲染,否则什么都不做。如何检测值是否发生变化,其实是根据变量的地址。我们都知道变量在内存中有唯一的地址。就拿刚才的两个例子来说,为什么更新名字和更新兴趣爱好这两种状态会出现两种不同的结果呢?那是因为在JavaScript中,字符串被设计为不可变的,而数组是可变的。也就是说,我们不能在保持字符串的内存地址不变的情况下改变字符串,但是我们可以保证在内存地址不变的情况下增加或删除数组的一个元素。所以这就是为什么当我们更新爱好的状态时组件没有刷新的原因。我们用一个例子来证明数组的特性:lethobbies=['qq','wx'];lethobbies_2=hobbies;hobbies_2.push('Coding');console.log(爱好===hobbies_2);可以看出这两个变量其实是一样的。再举个例子,在日常开发中肯定会遇到。如果要将后台的数据渲染成列表,数据如下:[{'id':0,name:'xiaoming'},{'id':1,name:'lilei'},{'id':2,name:'hanmeimei'},]这是一个很简单的操作。然后我们做了一个update函数。当我们将id为1的itemname修改为'MissLi'时,如何保证list组件在setState后重新渲染?估计大家心里马上有两个方案,一个是重新获取后台数据,然后重新渲染,一个是重新复制一份数据,把对应的地方改一下,然后重新设置成一个某些状态完成重新渲染。但是我们可以看到,这两个操作的最终效果仍然是创建另一个与初始数组内容完全相同的数组,相对繁琐但有效。然而,这两种解决方案都有其自身的缺点。比如第一种方案需要额外的网络请求,第二种方案如果数据量太大会浪费内存。在这种情况下,有什么更好的解决方案?至此,相信大家已经明白这篇博客的用意了,因为这个immutability-helper是笔者最近项目中使用频率最高的,所以觉得有必要说一下。如何使用immutability-helper使用方法非常简单。首先,安装依赖项npminstallimmutability-helper--save,然后在必要时从'immutability-helper'导入更新;这样我们就可以使用更新方法来做我们现在想做的事情了。那么有人要问了,为什么能完美解决问题呢?其实很简单。对于刚才的列表数据,immutability-helper会输出一个全新的数组对象,并且只更新id=1相关的数据,其余的通过地址引用引入到新的数组中,最大化数据和内存使用情况。immutability-helper的工作方式immutability-helper的工作方式也很简单。通过使用更新方法中的指令,可以修改数据。immutability-helper的命令组成也很简单:$+keyword。而我们对这个关键字还是非常熟悉的,下面我们继续往下看。它支持的命令共有:{$push:array}push()目标数组中的所有项目。{$unshift:array}unshift()目标数组中的所有项目。{$splice:数组数组}对于数组中的每个项目,使用项目提供的参数在目标上调用splice()。注意:数组中的项目是按顺序应用的,因此顺序很重要。目标的索引可能会在操作期间发生变化。{$set:any}完全替换目标。{$toggle:arrayofstrings}从目标对象切换布尔字段列表。{$unset:arrayofstrings}remove目标对象数组中的键列表。{$merge:object}将对象的键与目标合并。{$apply:function}将当前值传递给函数并用新的返回值更新它。{$add:arrayofobjects}向Map或Set添加一个值。当添加到Set时,你传入要添加的对象数组,当添加到Map时,你传入[key,value]数组,如下所示:update(myMap,{$add:[['foo','bar'],['baz','boo']]}){$remove:arrayofstrings}从Map或Set中删除数组中的键列表。这些指令关键字是不是很熟悉?因为我们在日常开发中经常会用到这些,所以就一一介绍吧。该指令使用$pushpush,顾名思义,它与数组有关。事实上,它向源数组添加一个元素并输出一个包含已计算出内容的新数组。参见示例:varupdate=require("immutability-helper");conststate1=["x"];conststate2=update(state1,{$push:["y"]});//['x','y']console.log(state1,state2);console.log(`state1===state2:${state1===state2}`);输出结果:同样熟悉update方法的使用:update方法接受两个参数,第一个是源数据,这个比较容易理解;第二个是操作线(来自作者YY),用来描述我们如何操作源数据,key是指令,value是指令需要的数据。$unshiftunshift的作用是批量向源数组的开头添加元素varupdate=require("immutability-helper");conststate1=["x"];conststate2=update(state1,{$unshift:["y","Z"]});console.log(state1,state2);输出:$splicesplice的作用是向源数组添加/删除元素。该参数接受多个数组,每个数组是一组操作。每组与实际拼接方法的参数相同。varupdate=require("不变性助手");conststate1=[0,1,2,4];conststate2=update(state1,{$splice:[[3,1,3,4,5,6,7]]});控制台日志(state1,state2);输出结果:$setset命令用于改变文字对象中某个键的值。varupdate=require("不变性助手");constdata={'id':0,name:'小明'};constdata2=update(data,{name:{$set:'李小姐'}});console.log(data,data2);输出结果:$toggletoggle表示切换,该方法用于切换布尔对象,如从True切换到Falsevarupdate=require("immutability-helper");constdata=[true,false];constdata2=update(data,{$toggle:[0]});console.log(data,data2);输出结果:同上方法,将数组中的第一个布尔值进行switch。本文首先介绍这5种指导方式。我们将在下一篇文章中继续介绍剩余的5个。接下来,我们尝试从以上5条指令中找出对应的指令来解决我们前面提到的表格数据的问题:首先,表格数据:[{'id':0,name:'xiaoming'},{'id':1,name:'lilei'},{'id':2,name:'hanmeimei'},]可以分析出我们需要处理两个对象,一个是数组,一个是字面量对象。那如果我们修改id=1的记录的'name'属性,'name=张伟',怎么办呢?第一步是找到id=1的记录的索引,这里是1;然后确定需要更改的字段和更改的值。varupdate=require("不变性助手");constdata=[{'id':0,name:'小明'},{'id':1,name:'lilei'},{'id':2,name:'hanmeimei'},]constdata2=update(data,{1:{name:{$set:'张伟'}}});console.log(data[1]['name'],data2[1]['name']);console.log(`data===data2:${data===data2}`);输出结果:这样,根据源数据改变了值,输出了一个完全不同的数组,其地址。
