更好的閱度體驗
javascript
Immutable的優點java
1. 保證不可變(每次經過Immutable.js操做的對象都會返回一個新的對象) 2. 豐富的API 3. 性能好 (經過字典樹對數據結構的共享)
Immutable的問題react
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每次都會返回一個新對象)
原生Js遇到的問題ajax
// 場景一 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中的屬性嵌套了好幾層(隨着業務的發展),對於原來想要的數據追蹤等都變得極爲困難,更爲重要的是,在這種狀況下,咱們一些沒有必要的組件極可能重複渲染了屢次。
總結起來就是如下幾點(問題雖少,但都是比較嚴重的):redux
1. 沒法追蹤Js對象 2. 項目複雜時,reducer生成新對象性能低 3. 只作淺比較,有可能會形成re-redering不符合預期(屢次渲染或不更新)
爲何不使用深比較性能優化
或許有人會疑惑,爲何不使用深比較來解決re-redering的問題,答案很簡單,由於消耗很是巨大~ 想象一下,若是你的參數複雜且巨大, 對每個進行比較是多麼消耗時間的一件事~
項目複雜後, 追蹤困難網絡
使用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 }) }
reducer生成新對象性能差
當項目變得複雜時,每一次action對於生成的新state都會消耗必定的性能,而Immutable.js在這方面的優化就很好。或許你會疑惑爲何生成對象還能優化?請往下看~app
在前面就講到,Immutable是經過字典樹來作==結構共享==的函數
(圖片來自網絡)
這張圖的意思就是
immutable使用先進的tries(字典樹)技術實現結構共享來解決性能問題,當咱們對一個Immutable對象進行操做的時候,ImmutableJS會只clone該節點以及它的祖先節點,其餘保持不變,這樣能夠共享相同的部分,大大提升性能。
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
使用Immutable須要注意的點
1. 不要混合普通的JS對象和Immutable對象 (不要把Imuutable對象做爲Js對象的屬性,或者反過來) 2. 對整顆Reudx的state樹做爲Immutable對象 3. 除了展現組件之外,其餘地方都應該使用Immutable對象 (提升效率,而展現組件是純組件,不該該使用) 4. 少用toJS方法 (一個是由於否認了Immutable,另外則是操做很是昂貴) 5. 你的Selector應該永遠返回Immutable對象 (即mapStateToProps,由於react-redux中是經過淺比較來決定是否re-redering,而使用toJs的話,每次都會返回一個新對象,即引用不一樣)
經過高階組件,將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))