-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React 栈(六):Immutable #185
Comments
React.js Conf 2015 - Immutable Data and React with Lee Byron: https://www.youtube.com/watch?v=I7IdS-PbEgI how - persistent(remain previous input immutable) immutable(never changed once created) data structures - performance - interfaces & implmentations - dag(Directed Acyclic Graph) algorithm - Trie for arrays and objects that're not primitives j.c.r licklider - trie, alan kay - smalltalk, phil bagwell - basic hash array, rich hickey - simple made easy simplicity is hard work why - |
实践了一段时间以后,目前觉得,seamless-immutable 是最佳方案。为什么呢? 先看一下我们要什么,我们要的是 redux reducer 的 immutability(不可变能力)。这种能力,无非就是满足以下两点:
reducer 是个纯函数,相当于我们只是在 而 ImmutableJS 这样的库完全违反了这一点,一旦引入,应用处处都要知道和学习它的存在。就像 redux 一样,一旦引入,很难替换,是个很有风险的技术选型。而 seamless-immutable 做到了,只在 reducer 层加一层 immutable 的能力,而应用的其他部分无感知。 侵入性这个东西基本是不能忍的,不可能为了获得一个小的(虽然极为重要的) immutable 能力,耦合上整个应用的设计。综上,就凭这一点,可以断言重量级的 ImmutableJS 等库不是合理选择。它应该被以简单的方式实现。 |
咦? 为啥只搜到了 React 栈(六)和(二),其他部分还没建issue吗🤣 |
学习了,感觉用了Immutable就想用了一种另外的语言……它给人带来的不适感真是值得斟酌。 |
Immutability
Immutability 并不局限于 JavaScript,只不过 Redux 的一个基石就是 immutability。Redux 的程序必须同时具备 Immutability,否则就是逻辑错误的。 文档已经回答了很多问题,基本上只有下面「为什么没有 immutable 的 redux 是逻辑错误的」一节是自己的思考过程,其余可悉数参考官方文档。非常完整的文档,非常专业。
Immutable 是什么?
原始定义可能比较难找了。大概的意思是两点:
遵循 immutable 的设计,数据之间会呈现一个关系:引用的对比结果等同于值的对比结果。我们下面会看到,为什么这个推论对于 redux 来说是必须要有的。
为什么没有 immutable 的 redux 是逻辑错误的?
先废几句话再进正题:redux 设计过程使用了浅对比是正确的,这个决定恰如其分。如果用了深对比,大数据量下一定出性能问题,这样库也不可能被广泛采用了。
再来问,为什么说 Redux 一定需要数据的 immutability,没有就一定是逻辑错误呢?因为 Redux 出于性能考虑使用了浅对比算法(shallow equality check)。浅对比进行的是引用对比,不比对值变化,因此需要 Redux 的用户自行建立「引用对比」和「值对比」之间的对应关系。这种关系事实上就是 immutability 的表述:
也就是说,immutability 是一种关系,它保证了
dataEqualityCheck()
(deepEqualityCheck()
) 和referenceEqualityCheck()
(shallowEqualityCheck()
) 的结果总是相同的。Redux 需要这种关系,而这是需要开发者去保证的。为什么说浅对比没有性能问题
与浅对比(引用对比)相对应的是深对比(值对比)。深对比能检测到对象是否深度值「相等」,但算法复杂度更高。这两种算法并没有必然说要使用哪一种,Redux 选择了使用浅对比,这就是框架本身的取舍与选择。这个取舍是美的,我认为恰如其分。如果使用了深对比,处理起真实项目上的大型对象一定会有性能问题。
为什么 Redux 一定要进行这样的「对比」
因为 Redux 需要在「数据发生变化」的时候,通知其监听者进行相应的动作。数据发生变化,既包含数据结构的变化,也包含数据中任何一层对象(或基本类型)值的变化。也即是说,要了解「数据是否发生变化」,Redux 必须进行精确的值对比。
然而如上所说,直接进行值对比的代价太高,因此 Redux 做了这样的妥协:只进行引用对比,通过强制 引用变化 和 值变化 建立一一联系的方式,间接地进行值对比。
引用比对结果 与 值比对结果 必须等价,这个关系就是 immutability 的表述。这也是我为什么讲,Immutability 是 Redux 的必然推论。否则整个 Redux 就是逻辑错误的一个系统。
这个推论,其实也是被官方论证了的,见 这里。
有兴趣的同学,可以看下面这段代码。「Redux 需要预设 Immutability」这个事情,更具体地说,是发生在
redux
的combineReducers()
这个方法中的:节选自
redux/src/combineReducers.js
。2017-08-07 执行npm install -g redux
取得的 redux 版本。如何使用 Immutable?不同方案的对比?
综上所述,Immutable 其实是一种「关系」,一个「引用比对结果与值比对结果必须一致」的关系,即这个等式必须恒成立:
dataEqualityCheck() === referenceEqualityCheck()
。只要你的 Redux reducer 中返回的新 state 都满足这个关系,那么就等于说你实现了 Immutability。至于如果做到这点,大概有两种方式:http://redux.js.org/docs/faq/ImmutableData.html#what-approaches-are-there-for-handling-data-immutably-do-i-have-to-use-immutablejs
原生 JS 优点是轻量、无需任何依赖,但缺点也突出:容易遗漏、容易产生冗余代码、大数量级数据下会有性能问题。
使用库则解决了原生 JS 的缺憾:引入后自动获得 Immutable 能力(由库�保证)、丰富强大的 API、大数据量下性能优秀、使得数据版本历史等变得简单。但毛病同样突出,以 ImmutableJS 这个库为例(我也不知道为什么官方要举这个库为例子)。以下部分缺憾可能是针对这个库而特有的。
ImmutableJS 的缺点
toJS()
方法每次都会创建新对象,即便传入的值对象是相同的,两次调用生成的对象引用还是会不一样。这违反了 Immutable 定律的第一条:值不改变时引用也不能变。因此,在任何涉及 redux 的地方不恰当使用都可能造成逻辑错误,最典型例子就是在react-redux
库的mapStateToProps()
中使用了toJS()
但又没改变对象值,此时会导致组件的非必要渲染toJS()
方法toJS()
方法能转成普通 JS 对象,但性能稍慢,而且反过来也无法跟 Immutable 对象一起玩了teacher.students.henry
与teacher.getIn(['students', 'henry'])
综上,ImmutableJS 用不用呢?官方认为,值得,因为「在 redux reducer 中修改了原对象可能导致的 bug,发现、调试成本极高」,而 Immutable 解决了这个问题(然后带出了其他3、4个问题)。这个成本我是同意的,组件不该渲染时可能重复渲染,可能该渲染时不渲染,而且你还不能确定是不是 mutated 数据带来的问题。
其他实现 immutable 的方案?
https://github.com/facebook/immutable-js: 所有章节都在讨论这个选择的可行性,应该是目前最主流的选择了。优点是性能好,API 强大;缺点是 API 不够友好,侵入性强,需要团队约束与背景知识,潜在移除成本高。
https://github.com/gajus/redux-immutable: 让 ImmutableJS 与 redux 一起工作的库。
https://github.com/rtfeldman/seamless-immutable: 解决了 ImmutableJS API 不够友好和侵入性强的问题。同时引入了两个小问题:在原生对象上加了扩展,以及仅支持较主流浏览器的高版本,因为依赖于浏览器实现。感觉上,这个库我会更倾向,因为既有丰富而友好的 API,又能享受性能上的优势,还对应用没有侵入性;那两个毛病稍小,在原生对象上加扩展这个事比较不能忍,但也没其他方法,能忍。
Redux + ImmutableJS 最佳实践
综上,作者认为,redux 的 immutability 必须有,虽然 ImmutableJS 诸多缺点,但还是要用滴。只不过用起来不易,于是给凡人们总结了一个最佳实践,主要就是解决上面提到能解决的大部分痛点,应慎读之:
get()
方法,这样就不 pure 了,维护、测试成本都变大toJS()
违反 Immutable 定律第一条mapStateToProps()
中用toJS()
update
、merge
和set
方法时,确保要更新的对象已经使用fromJS()
包装过综上,诸多缺点中,多数都可以用(复杂的)最佳实践和项目规范来避免,尽管有一定的学习成本。基本无法解决的问题只有一个:一旦引入,去除掉可能就需要巨大的代价。
我的结论
总而言之,我得出的几点结论:
侵入性很强,主要是指很多地方需要「知道」ImmutableJS 的存在以及「如何使用它」,比如 reducer 的
initState
、container component 的mapStateToProps
、HOC 的组件中要做个中间层把 Smart Component 中的 ImmutableJStoJS()
到 Dumb Component 中,等;推荐的解决方案是,把「如何使用」,操作具体数据结构的这部分从业务逻辑中解耦出去,让业务逻辑对数据的获取方式无感知。虽然解决了这个问题,但需要从设计上迁就这个库的引入,说是更强的侵入性也未可知。团队约束与沟通成本,主要是指即便用了库,要保证最佳实践,团队依然需要掌握一些知识,比如不能在
mapStateToProps()
中使用toJS()
、Container Component -> HOC -> Pure function Component 的层次和数据流动方向(state ->toJS()
-> JS)需要了解等。Immutable 在其他语言中
以下内容尚未整理。
意义:
缺点:
然而,这个来自函数式编程世界的概念,对于编程和解决业务问题又有什么意义呢?怎么映射过来呢?
所以,数据有两种属性:值和属性。数据又分为两种:基本类型和可嵌套类型(如数组、对象)。数据的「对比」,就有了两种对比方法:值对比和引用对比。
引用可以认为是一个复杂数据的「identity」,你内部的「数值」可以改变,但你的「标识」——就是这个引用——是不变的。而对于基本类型来说,只存在值对比,比如数字,3 和 4 不相等就是不相等,不存在数值不等,但「标识」相等的情况。这个「标识」是不存在的,基本类型只有值,只存在值对比。又比如布尔类型,真就是真,假就是假;比如字符串,这在不同语言中多也设计成为不可变的基本类型,因此只有值对比。
可嵌套类型的对比,就可以有不同的对比方式了。可以对比「标识」也即引用,也可以比对每一项属性「值」,看你的上下文在乎的是哪一个。对比「标识」的话,则没有办法反映到具体值的变化;对比「值」的话,比较彻底,性能一定会成问题。
于是,在「复杂类型对象对比」这个上下文中,如果你既想检测结果能反映具体值的变化,又想具有常数复杂度的优秀性能,你就需要其他规则的介入了。Immutability 就是这样的一条规则:
这样一来,值变化 与 引用变化就建立了等价关系,你只需要比较引用,就可以以常数复杂度等价检测到「值是否变化」。
这个事情不知道怎么讲比较清楚呢。另外,所以是有什么意义呢?
The text was updated successfully, but these errors were encountered: