乐趣区

React-redux进阶之Immutable.js

Immutable.js
Immutable 的优势
1. 保证不可变(每次通过 Immutable.js 操作的对象都会返回一个新的对象)
2. 丰富的 API
3. 性能好(通过字典树对数据结构的共享)
<br/>Immutable 的问题
1. 与原生 JS 交互不友好(通过 Immutable 生成的对象在操作上与原生 JS 不同,如访问属性,myObj.prop1.prop2.prop3 => myImmutableMap.getIn([‘prop1’,‘prop2’,‘prop3’])。另外其他的第三方库可能需要的是一个普通的对象)
2. Immutable 的依赖性极强(一旦在代码中引入使用,很容易传播整个代码库,并且很难在将来的版本中移除)
3. 不能使用解构和对象运算符(相对来说,代码的可读性差)
4. 不适合经常修改的简单对象(Immutable 的性能比原生慢,如果对象简单,并且经常修改,不适合用)
5. 难以调试(可以采用 Immutable.js Object Formatter 扩展程序协助)
6. 破坏 JS 原生对象的引用,造成性能低下(toJs 每次都会返回一个新对象)
<br/>
原生 Js 遇到的问题
原生 Js 遇到的问题
// 场景一
var obj = {a:1, b:{c:2}};
func(obj);
console.log(obj) // 输出什么??

// 场景二
var obj = ={a:1};
var obj2 = obj;
obj2.a = 2;
console.log(obj.a); // 2
console.log(obj2.a); // 2

代码来源:https://juejin.im/post/5948985ea0bb9f006bed7472
// ajax1
this.props.a = {
data: 1,
}
// ajax2
nextProps.a = {
data: 1,
}
//shouldComponentUpdate()
shallowEqual(this.props, nextProps) // false
// 数据相同但是因为引用不同而造成不必要的 re-rederning
由于 Js 中的对象是引用类型的,所以很多时候我们并不知道我们的对象在哪里被操作了什么,而在 Redux 中,因为 Reducer 是一个纯函数,每次返回的都是一个新的对象(重新生成对象占用时间及内存),再加上我们使用了 connect 这个高阶组件,官方文档中虽然说 react-redux 做了一些性能优化,但终究起来,react-redux 只是对传入的参数进行了一个浅比较来进行 re-redering(为什么不能在 mapStateToProps 中使用 toJs 的原因)。再进一步,假如我们的 state 中的属性嵌套了好几层(随着业务的发展),对于原来想要的数据追踪等都变得极为困难,更为重要的是,在这种情况下,我们一些没有必要的组件很可能重复渲染了多次。
<br/>
总结起来就是以下几点(问题虽少,但都是比较严重的):
1. 无法追踪 Js 对象
2. 项目复杂时,reducer 生成新对象性能低
3. 只做浅比较,有可能会造成 re-redering 不符合预期(多次渲染或不更新)
<br/>
为什么不使用深比较
或许有人会疑惑,为什么不使用深比较来解决 re-redering 的问题,答案很简单,因为消耗非常巨大~
想象一下,如果你的参数复杂且巨大,对每一个进行比较是多么消耗时间的一件事~

<br/>
使用 Immutable 解决问题
项目复杂后,追踪困难
使用 Immutable 之后,这个问题自然而然就解决了。所谓的追踪困难,无非就是因为对象是 mutable 的,我们无法确定它到底何时何处被改变,而 Immutable 每次都会保留原来的对象,重新生成一个对象,(与 redux 的纯函数概念一样)。但也要注意写代码时的习惯:
// javascript
const obj = {a: 1}
function (obj) {
obj.b = 2

}

// Immutable
const obj = Map({a : 1})
function (obj) {
const obj2 = obj.set({‘b’, 2})
}
<br/>
reducer 生成新对象性能差 当项目变得复杂时,每一次 action 对于生成的新 state 都会消耗一定的性能,而 Immutable.js 在这方面的优化就很好。或许你会疑惑为什么生成对象还能优化?请往下看~
在前面就讲到,Immutable 是通过字典树来做 == 结构共享 == 的
(图片来自网络)
这张图的意思就是
immutable 使用先进的 tries(字典树) 技术实现结构共享来解决性能问题,当我们对一个 Immutable 对象进行操作的时候,ImmutableJS 会只 clone 该节点以及它的祖先节点,其他保持不变,这样可以共享相同的部分,大大提高性能。
<br/>
re-rendering 不符合预期
其实解决这个问题是我们用 Immutable 的主要目的,先从浅比较说起 浅比较引起的问题在这之前已经讲过,事实上,即使 Immutable 之后,connect 所做的依然是浅比较,但因为 Immutable 每次生成的对象引用都不同,哪怕是修改的是很深层的东西,最后比较的结果也是不同的,所以在这里解决了第一个问题,==re-rendering 可能不会出现 ==。但是,我们还有第二个问题,== 没必要的 re-rendering==,想要解决这个问题,则需要我们再封装一个高阶组件,在这之前需要了解下 Immutable 的 is API
// is() 判断两个 immutable 对象是否相等
immutable.is(imA, imB);
这个 API 有什么不同,== 这个 API 比较的是值,而不是引用 ==,So:只要两个值是一样的,那么结果就是 true
const a = Immutable.fromJS({
a: {
data: 1,
},
b: {
newData: {
data: 1
}
}
})
const target1 = a.get(‘a’)
const target2 = a.getIn([‘b’, ‘newData’])
console.log(Immutable.is(target1, target2)) //is 比较的依据就是每个值的 hashcode
// 这个 hashcode 就相当于每个值的一个 ID, 不同的值肯定有不同的 ID,相同的 ID 对应着的就是相同的值。
也就是说,对于下面的这种情况,我们可以不用渲染
// ajax1
this.props.a = {
data: 1,
}
// ajax2
nextProps.a = {
data: 1,
}
//shouldComponentUpdate()
Immutable.is(this.props, nextProps) // true
最后,我们需要封装一个高阶组件来帮助我们统一处理是否需要 re-rendering 的情况
//baseComponent.js component 的基类方法

import React from ‘react’;
import {is} from ‘immutable’;

class BaseComponent extends React.Component {
constructor(props, context, updater) {
super(props, context, updater);
}

shouldComponentUpdate(nextProps, nextState) {
const thisProps = this.props || {};
const thisState = this.state || {};
nextState = nextState || {};
nextProps = nextProps || {};

if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
Object.keys(thisState).length !== Object.keys(nextState).length) {
return true;
}

for (const key in nextProps) {
if (!is(thisProps[key], nextProps[key])) {
return true;
}
}

for (const key in nextState) {
if (!is(thisState[key], nextState[key])) {
return true;
}
}
return false;
}
}

export default BaseComponent;

代码来源链接:https://juejin.im/post/5948985ea0bb9f006bed7472
<br/>
使用 Immutable 需要注意的点
使用 Immutable 需要注意的点
1. 不要混合普通的 JS 对象和 Immutable 对象(不要把 Imuutable 对象作为 Js 对象的属性,或者反过来)
2. 对整颗 Reudx 的 state 树作为 Immutable 对象
3. 除了展示组件以外,其他地方都应该使用 Immutable 对象(提高效率,而展示组件是纯组件,不应该使用)
4. 少用 toJS 方法(一个是因为否定了 Immutable,另外则是操作非常昂贵)
5. 你的 Selector 应该永远返回 Immutable 对象(即 mapStateToProps,因为 react-redux 中是通过浅比较来决定是否 re-redering,而使用 toJs 的话,每次都会返回一个新对象,即引用不同)
<br/>
通过高阶组件,将 Immutable 对象转为普通对象传给展示组件
1. 高阶组件返回一个新的组件,该组件接受 Immutable 参数,并在内部转为普通的 JS 对象
2. 转为普通对象后,新组件返回一个入参为普通对象的展示组件
import React from ‘react’
import {Iterable} from ‘immutable’

export const toJS = WrappedComponent => wrappedComponentProps => {
const KEY = 0
const VALUE = 1

const propsJS = Object.entries(wrappedComponentProps).reduce(
(newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(
wrappedComponentProp[VALUE]
)
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE]
return newProps
},
{}
)

return <WrappedComponent {…propsJS} />
}
import {connect} from ‘react-redux’

import {toJS} from ‘./to-js’
import DumbComponent from ‘./dumb.component’

const mapStateToProps = state => {
return {
// obj is an Immutable object in Smart Component, but it’s converted to a plain
// JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
// object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
// there is no issue with errant re-renderings.
obj: getImmutableObjectFromStateTree(state)
}
}
export default connect(mapStateToProps)(toJS(DumbComponent))
参考
<html>Immutable.js 以及在 react+redux 项目中的实践 <br/>Using Immutable.JS with Redux<br/> 不变应万变 -Immutable 优化 React<br/>React-Redux 分析 <br/></html>

退出移动版