详细解释不变和反应中的实践
有人说不可变可以给React应用带来几十倍的提升,也有人说不可变的引入是最近JavaScript中的一大发明,因为React在同期太火了,它的光芒被掩盖了。这些至少说明了不可变是很有价值的,让我们来看看。
JavaScript中的对象通常是可变的,因为使用了引用赋值,新对象只是引用了原始对象。更改新对象将影响原始对象。例如,foo={ a : 1 };bar=fooBar.a=2,你会发现foo.a也变成了2。虽然这样可以节省内存,但是在应用复杂的时候,会造成很大的隐患,Mutable带来的优势也是得不偿失的。为了解决这个问题,通常的做法是使用浅层复制或深层复制来避免被修改,但这样做会导致CPU和内存的浪费。不可变可以很好地解决这些问题。
什么是不可变数据
不可变数据是一旦创建就不能再更改的数据。对不可变对象的任何修改、添加或删除都将返回一个新的不可变对象。不可变实现的原则是持久数据结构,即在使用旧数据创建新数据时,旧数据应该是可用的,同时保持不变。同时,为了避免deepCopy一次复制所有节点带来的性能损失,不可变使用Structural Sharing,即如果对象树中某个节点发生变化,只会修改该节点及其受影响的父节点,其他节点将被共享。请参见以下动画:
擦拭,做一个Gif动画花了很大的功夫,但是被迫变成了静态图片。请步行http://img.alicdn.com/TPS/I2/tb1zzi _ kxxxxctxfxb 8 ovxx-613-575 . gif观看两个流行的不可变库:
1 .不可变的. js
脸书工程师李拜伦花了三年时间构建,它与React同时出现,但默认情况下没有放入React工具集中(React提供了简化的Helper)。它实现了一套完整的持久数据结构和许多易于使用的数据类型。集合,列表,映射,集合,记录,序列。有非常全面的功能操作方法的地图,过滤,分组和减少查找。同时,API尽可能类似于对象或数组。
有三种最重要的数据结构需要解释:(Java程序员应该最熟悉它们)
映射:一组键值对,对应于对象。ES6还有一个特殊的Map对象列表:有序且可重复的列表,对应ArraySet:无序且不可重复的列表
2 .无缝-不可变
与不可变的学术风格不同,无缝不可变没有实现完整的持久数据结构。而是使用Object.defineProperty实现(所以只能在IE9及以上版本中使用),扩展了JavaScript的Array和Object Objects。它只支持数组和对象两种数据类型,API基于与数组和对象相同的操作。代码基数很小,压缩下载只有2K。而不可变. js是用16K压缩下载的。
下面是感受两者区别的代码:
//原写法是让foo={ a: { b: 1 } }让bar=foobar . a . b=2;console . log(foo . a . b);//print 2 console . log(foo===bar);//print true//使用不可变. js后,从‘不可变’导入不可变;foo=Inverable . Fromjs({ a : { b : 1 } });bar=foo.setIn(['a ',' b'],2);//使用setIn分配console.log (foo.getin (['a ',' b ']));//使用getIn获取值并打印1 console . log(foo===bar);//print false//使用无缝-不可变. js后,从'无缝-不可变'导入s不可变;foo=simble({ a : { b : 1 } })bar=foo . merge({ a : { b : 2 } })//使用merge来分配console . log(foo . a . b);//取native Object这样的值,打印1 console . log(foo===bar);//打印不可变优势
1.不可变减少了可变带来的复杂性
可变数据与时间和价值的概念相结合,这使得数据难以追溯。
例如,以下代码:
函数TouchAndLog(TouchFn){ let data={ key : ' value ' };touchFn(数据);console . log(data . key);//猜猜会打印什么?}不看touchFn的代码,因为你不确定它对数据做了什么,你无法知道它会打印什么(这不是废话吗)。但是如果数据是不可变的,你可以肯定地知道打印是有价值的。
2.节省内存
不可变. js使用结构共享来尽可能地重用内存。未被引用的对象将被垃圾收集。
从“不可变”导入{ Map };让a=Map({ select: 'users ',filter : Map({ name : ' Cam ' })})让b=a.set('select ',' people ');a===b;//false a . get(' filter ')==b . get(' filter ');//true以上,a和b共享未更改的筛选器节点。
3.撤消/重做、复制/粘贴,甚至时间旅行都是小菜一碟
因为每次的数据都是不一样的,只要把数据放在一个数组中存储,想什么时候回去都可以取出对应的数据。很容易开发撤销和重做功能。
稍后我将提供一个通量撤销的例子。
4.并行安全性
传统并发非常困难,因为要处理各种数据不一致,所以“聪明人”发明了各种锁来解决。但是在使用不可变之后,数据本质上是不可变的,并发锁是不必要的。
但是,现在已经没用了,因为JavaScript还在单线程中运行。但未来可能会加入。提前解决未来的问题不是很好吗?
接受函数式编程
不可变本身是函数式编程中的一个概念,纯函数式编程比面向对象更适合前端开发。因为只要输入一致,输出必然一致,所以开发出来的组件更容易调试和组装。
函数式编程语言如ClojureScript和Elm中的数据类型本质上是不可变的,这就是为什么基于ClojureScript中React的框架OM的性能比React好的原因。
使用不可变的缺点
需要学习新的API
没有评论
增加资源文件的大小
没有评论
容易与原生对象混淆
这是我们使用不可变的. js时最大的问题,编写代码需要改变思维。
虽然不可变. js尽量让API设计的原生对象尽可能相似,但有时很难区分是不可变对象还是原生对象,容易混淆操作。
不可变中的映射和列表对应于本机对象和数组,但是它们的操作非常不同。例如,应该使用map.get('key ')代替map.key,使用array.get(0)代替array[0]。另外,不可变每次修改都会返回一个新对象,很容易忘记赋值。
使用外部库时,一般需要使用本机对象,容易忘记转换。
以下是避免类似问题的一些方法:
使用带有静态类型检查的工具,如流或类型脚本
约定变量命名规则:例如,所有不可变类型对象都以$ $ $开头。
使用不可变的fromJS代替不可变的。映射或不可变。列表来创建对象,这样可以避免不可变对象和本机对象的混合。
了解更多
1 .不可改变
两个不可变对象可以用===进行比较,这是内存地址的直接比较,性能最好。但是,即使两个对象的值相同,它也会返回false:
让map1=不可变。Map({a:1,b:1,c :1 });让map2=不可变。Map({a:1,b:1,c :1 });map1===map2//false为了直接比较对象的值,invoke . js提供了invoke . is进行“值比较”,结果如下:
不可变的. is(map1,map 2);//TrueMultiVe . is比较两个对象的hashCode或valueOf(对于JavaScript对象)。因为不可变使用Trie数据结构在内部存储,只要两个对象的hashCode相等,值就相同。该算法避免了深度遍历比较,性能非常好。
不可变的. is将在以后使用,以减少重复渲染并提高性能。
此外,还有桑葚、桑白皮等。因为它们相似,所以就不介绍了。
2.不同于对象。冻结和常量
ES6中的Object.freeze和新添加的const都可以实现防止对象被篡改的功能,但都是浅层拷贝。当对象层次较深时,需要特殊处理。
3.光标的概念
这个游标与数据库中的游标是完全不同的概念。
不可变数据一般是深度嵌套的,所以Cursor提供了一个可以直接访问深度数据的引用。
从“不可变”导入不可变;从“不可变/贡献/游标”导入游标;让数据=Unvariable . Fromjs({ a : { b : { c : 1 } });//让光标指向{c: 1}让光标=光标。当游标或其子游标执行update时,from (data,['a ',' b'],new data={//调用console . log(new data);});cursor . get(' c ');//1cursor=cursor.update('c ',x=x ^ 1);cursor . get(' c ');//2练习
与反应、纯渲染一起使用
熟悉React的人都知道,React在优化性能的时候有一个避免重复渲染的大招,那就是使用shouldComponentUpdate(),但是默认返回true,也就是总是会执行render()方法,然后对比Virtual DOM,看看是否有必要更新真实的DOM,这样往往会带来很多不必要的渲染,成为性能瓶颈。
当然,我们也可以在shouldComponentUpdate()中使用deepcopy和deepCopy来避免不必要的渲染(),但是deepCopy和deepCompare通常非常消耗性能。
不可变提供了一种简单有效的方法来判断数据是否发生变化。您只需要将====与比较就可以知道是否需要执行render()了,而且这个操作几乎是零成本的,所以可以大大提高性能。修改后的shouldComponentUpdate是这样的:
从“不可变”导入{ is };should componentupdate:(next props={ },next state={ })={ const this props=this . props | | { },this state=this . state | | { };if (Object.keys(thisProps)。长度!==Object.keys(下一个道具)。长度||对象.键(此状态)。长度!==对象.键(下一状态)。长度){返回true} for(next props中的const key){ if(this props[key]!==nextProps[key] ||!is(thisProps[key],next props[key]){ return true;} } for(nextState中的const key){ if(thiststate[key]!==nextState[key] ||!is(thisState[key],next state[key]){ return true;} }返回false}使用不可变后,如下图所示,当红色节点的状态发生变化时,不会渲染树中的所有节点,只会渲染图中的绿色部分:
还可以使用支持类语法的React.addons.PureRenderMixin或pure-render-decorator。
设定状态的技巧
React暗示这个. state应该被认为是不可变的,所以在修改之前做一个deepCopy是很麻烦的:
从“lodash”导入“_ ”;const Component=react . create class({ getInitialState(){ return { data : { times : { 0 } } }),handleAdd() {let data=_。clone deep(this . state . data);data . times=data . times 1;this . setstate({ data : data });//如果上面没有做cloneDeep,下面打印的结果就是加1后的值。console . log(this . state . data . times);}}使用不可变后:
getInitialState(){ return { data : Map({ times : { 0 })},handleAdd(){ this . setstate({ data : this . state . data . update(' times ',v=v ^ 1)});//此时,times不会更改console.log(这。state . data . get(' times ');}上面的“handleAdd”可以缩写为:
handleAdd(){ this . setstate(({ data })=({ data : data . update(' times ',v=v ^ 1)})});}2.与焊剂一起使用
因为Flux不限制存储中的数据类型,所以使用不可变非常简单。
现在是时候实现一个具有添加和取消功能的类似商店了:
从“不可变”导入{ Map,ordered Map };let todos=ordered map();让历史=[];//普通数组,存储每次操作后生成的数据。让todo store=create store({ getall(){ return to dos;}});dispatcher . register(action={ if(action . action type==' create '){ let id=CreateGuid();history . push(todos);//记录当前操作前的数据,方便撤销todos=todos.set (id,map ({id: id,complete: false,text 3360 action . text . trim()});toostore . emitchange();} else if (action。action type==' undo '){//这里是undo函数的实现,//只需从history数组if(history . length 0){ todos=history . pop();} ToDoostore . emitChange();}});3.与Redux一起使用
Redux是一个流行的Flux衍生库。它简化了Flux中多个store的概念,只有一个store,通过Reducer实现数据操作;同时,它提供了更简单、更清晰的视图-动作-中间件-减速器,更容易开发同构应用。目前,它已在我们的项目中得到广泛应用。
Redux中内置的CombineReducers和reducer中的initialState都是本机对象,因此它们不能与不可变本机对象一起使用。
幸运的是,Redux并不排除不可变的使用,它可以通过重写combineReducers或使用Redux-Immutable ejs来支持。
如上所述,Cursor可以很容易地检索和更新具有深层层次的数据,但是由于Redux中使用select进行检索,使用Action进行数据更新,因此Cursor在这里没有任何用处。
摘要
不可变可以给应用带来很大的性能提升,但是否使用取决于项目情况。由于侵入性强,容易引入新项目,需要对旧项目的迁移进行评估。对于一些提供给外部使用的公共组件,最好不要将不可变对象直接公开给外部接口。
被称为React API终结者的sebastian markbge有一个建议,如果JS的原生不可变类型太漂亮的话。能否通过还不确定。但是可以肯定的是,不可变将被越来越多的项目使用。
版权声明:详细解释不变和反应中的实践是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。