Redux vs Mobx系列(-):immutable vs mutable

**注意:**我會寫多篇文章來比較說明redux和mobx的不一樣,redux和mobx各有優缺點, 若是對React/Mobx/Redux都理解夠深入,我我的推薦Mobx(逃跑。。。)javascript

React社區的大方向是immutable, 不論是用immutable.js 仍是函數式編程使用不可變數據結構。爲何React須要不可變數據結構呢? 考慮下面的一個應用html

應用結構

class Root extends Component {
    state = {
        something: 'sh'
    }
    render() {
        return (
            <div> <div onClick={e => { // onClick setState 空對象 this.setState({}) }}>click me!!</div> <L1/> <Dog sh={this.state.something}/> </div> ) } } ... class L1 extends Component { render() { console.log('invoke L1') return ( <div> <L11/> <L12/> </div> ) } } ... class L122 extends Component { render() { console.log('invoke L122') return ( <div>L122</div> ) } } 複製代碼

當我點擊 Root上的 click me 的時候, 執行了this.setState({}),因而觸發Root更新, 這個時候L1, Dog會怎麼樣呢? 結論是當點擊的時候 控制檯會打印:java

invoke L1
invoke L11
invoke L111
invoke L112
invoke L12
invoke L121
invoke L122
invoke Dog
複製代碼

當一個組件須要跟新的時候,react並不知道哪裏會更新,在內部react會用object(存js對象)來表明dom結構, 當有更新的時候 react暴力比較先後object的差別,增量的處理更新的dom部分。 對於剛纔的這個例子, react暴力計算的結果就是沒有增量。。。雖然react暴力比較算法已經很是高效了,這些無心義的計算也應該避免, 起碼能夠節省計算機的電 --> 少用煤 --> 減小二氧化碳排放 --> 保護地球。 畢竟 蝴蝶效應!react

ui = f(d) 相同的d獲得相同的ui(設計組件的時候最好這樣)。例如咱們上例的Dog,咱們能夠直接比較shgit

class Dog extends Component {
    shouldComponentUpdate(nextProps) {
        return this.props.sh !== nextProps.sh
    }
    ...
}
複製代碼

更加通常的狀況, 咱們怎麼肯定組件的props和state沒有變化呢? 不可變對象 ! 若是對象是不可變的, 那麼當對象a !== a' 就表明這是2個對象,不相等。而在傳統可變的對象中 須要deepEqual(a, a')。 若是咱們的React應用裏面 props和state都是不可變對象, 那麼:github

class X extends Component {
     shouldComponentUpdate(nextProps, nextState) {
       return !( shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState))
    }
}
複製代碼

react也考慮到了一點 提供了PureComponent幫助咱們默認作了這個shouldComponentUpdateweb

把 L1, Dog, L11 ... L122改成PureComponent, 再次點擊,打印:算法

// 沒有輸出。。。
複製代碼

拯救了地球!編程

Redux

redux 每次action發生的時候,都會返回一個全新的state,�天生是immutable。 Redux + PureComponent 輕鬆開發出高效web應用redux

Mobx

Mobx恰好相反,它依賴反作用(so 全部組件不在繼承PureComponent), 那它是怎麼工做的呢?

mobx-react的 @observer經過收集組件 render函數依賴的狀態, 當狀態有修改的時候精確的控制組件的更新。

好比如今 Root組件依賴狀態 title, L122 依賴狀態x(Root傳遞x給L1,L1傳遞給L12, L12傳遞給L122)。 那麼應該:

const store = observable({
    x: 'x'
    title: 'title',
})

window.store = store
@observer
export default class MobxRoot extends Component {
    render() {
        console.log('invoke MobxRoot')
        const { title, x } = store
        return (
            <div>
                <div>{title}</div>
                <L1 x={x}/>
                <Dog/>
            </div>
        )
    }
}
class L1 extends Component {
    render() {
        console.log('invoke L1')
        return (
            <div>
                <L11/>
                <L12 x={this.props.x}/>
            </div>
        )
    }
}
class L12 extends Component {
     render() {
        console.log('invoke L12')
        return (
            <div>
                <L121/>
                <L122 x={this.props.x}/>
            </div>
        )
    }
}
@observer
class L122 extends Component {
     render() {
        console.log('invoke L122')
        return (
            <div>
                { this.props.x || 'L122'}
            </div>
        )
    }
}
複製代碼

這樣當title變化的時候, Mobx發現只有MobxRoot組件關心title,因而更新MobxRoot, 當x變化的時候 Mobx發現有MobxRoot, L122 依賴與x,因而更新MobxRoot,L122 。 工做很正常。

細想當title變化的時候,更新MobxRoot,因爲更新了MobxRoot進而致使L1,Dog的遞歸暴力diff計算,顯而易見的是無心義的計算。 當x變化的時候呢, 因爲MobxRoot,L122依賴了x, 會先更新MobxRoot,而後更新L122,然而在更新MobxRoot的時候又會遞歸的更新到L122, 這裏更加麻煩了(實際上React不會更新兩次L122)。

Mobx也在文檔裏指出了這個問題(晚一點使用間接引用值), 對應的解決方法是 L1 先傳遞store。。。最後在L122裏面從store裏面獲取x。

這裏暴露了兩個問題:

  1. 父組件的更新,會影響到子組件,因爲不是使用不可變數據,還不能簡單的經過PureComponent優化
  2. props傳遞的過程當中 不可避免的會提早使用引用值,致使某些組件無心義的更新, 狀態越多越複雜

記住在mobx應用裏, 應該把組件是否更新的絕對權徹底交給Mobx,徹底交給Mobx,徹底交給Mobx。 即便是父組件也不該該引發子組件的跟新。 因此全部的組件(沒有被@observer修飾)都應該繼承與PureComponent(這裏的PureComponent的做用已經不是原來的了, 這裏的做用是阻止更新行爲的傳遞)。 另一點, 因爲組件是否更新取決與Mobx, 組件更新的數據又取值與Mobx,因此還有必要props傳遞嗎? 基於這兩點代碼:

const store = observable({
    x: 'x'
    title: 'title',
})

window.store = store
@observer
export default class MobxRoot extends Component {
    render() {
        console.log('invoke MobxRoot')
        const { title} = store
        return (
            <div> <div>{title}</div> <L1/> <Dog/> </div>
        )
    }
}
class L1 extends PureComponent {
    render() {
        console.log('invoke L1')
        return (
            <div> <L11/> <L12/> </div>
        )
    }
}
class L12 extends PureComponent {
     render() {
        console.log('invoke L12')
        return (
            <div> <L121/> <L122/> </div>
        )
    }
}
@observer
class L122 extends Component {
     render() {
        console.log('invoke L122')
        const x = window.store // 直接從Mobx獲取
        return (
            <div> { x || 'L122'} </div>
        )
    }
}
複製代碼

這樣當title改變的時候, 只有MobxRoot會跟新, 當x改變的時候只有L122 會更新。 如今咱們能夠把應用裏面的全部組件分爲兩類: 關注狀態的@observer組件, 其餘PureComponent組件。這樣每當有狀態改變的時候, Mobx精確控制須要更新的@observer組件(最小的更新集合),其餘PureComponent阻止無心義的更新。 問題的關鍵是開發者必定要搞清楚 哪些組件須要 @observer。 這個問題先放一下, 咱們在看一個mobx的問題

假設L122複用了一個第三方庫提供的組件(代表咱們不能修改這個組件)

@observer
class L122 extends Component {
     render() {
        console.log('invoke L122')
        const x = window.store // 直接從Mobx獲取
        return (
            <div> <BigComponent x={x}/> </div> ) } } 複製代碼

組件 BigComponent 正如其名 是一個很‘大’的組件,他接收一個props對象 x,x結構以下:

x = {
   name: 'n'
   addr: '',
}
複製代碼

此時當咱們執行: window.store.x.name = 'fcdcd' 的時候, 咱們期待的是BigComponent按照咱們的意願,根據改變後的x從新渲染, 其實不會。 由於在這裏沒有任何組件 依賴name, 爲了讓L122 正常工做, 咱們必須:

@observer
class L122 extends Component {
     render() {
        console.log('invoke L122')
        const x = window.store.x 
        const nx = {
            name: x.name,
            addr: x.addr
        }
        
        return (
            <div> <BigComponent x={nx}/> </div> ) } } 複製代碼

若是不明白mobx的原理, 可能會很疑惑,疑惑這裏爲何要這麼寫, 疑惑哪裏爲啥不更新, 疑惑哪裏爲啥莫名其妙更新了。。。

什麼組件須要@observer? 當一個render方法裏,出現咱們不能控制的組件(包括原生標籤, 第三方庫組件)依賴於狀態的時候, 咱們應該使用@observer, 其餘組件應該繼承PureComponent。 這樣咱們的應用在狀態發送改變的時候,更新的集合最小,性能最高。

除此以外,Mobx還有一個性能隱患,但願mobx的擁護者可以清楚的認知到,假設如今 L122 不只也依賴title, 還依賴狀態a, b, c, d, e, f, g, h:

class L122 extends Component {
     render() {
         console.log('invoke L122')
       const { title, a, b, c, d, e, f, g, h } = window.store
        
        return (
            <div> <span>{title}</span> <span>{a}</span> <span>{b}</span> ... <span>{h}</span> </div>
        )
    }
}

function changeValue() {
    window.store.title = 't'
    window.store.a = 'a1'
    window.store.b = 'b1'
    window.store.c = 'c1'
}
複製代碼

當執行 changeValue()的時候 會發生什麼呢?控制檯會打印:

invoke MobxRoot
invoke L122
invoke L122
invoke L122
invoke L122
複製代碼

一身冷汗!!得好好想一想這裏的數據層設計, 是否把這幾個屬性組成一個對象,狀態愈來愈複雜的時候可能不是那麼簡單。

第三方庫結合

redux與第三方庫結合沒有好說的,工做的很好。 不少庫如今已經假定了 傳人的狀態是 不可變的。

mobx正如前文所說 不論是發佈爲第三方庫, 仍是使用第三方庫

  1. mobx寫的組件,發佈給其餘應用使用比較困難,由於要不咱們直接從全局取數據渲染(context獲取 道理相同), 要不推遲引用值的獲取, 不論是哪種,組件都沒有任何可讀性。
  2. mobx 使用第三方 例如BigComponent, 沒有那麼天然。

開發效率

這裏咱們只說 immutable的開發效率,mutable的開發效率應該是最低的。 0. 結合對象展開浮, js裸寫。 也不難

  1. immutable.js 學習成本略高, 包大小也畢竟大
  2. 函數式編程,項目組本身一我的 能夠考慮
  3. immer 若是不考慮IE,強烈推薦, 強烈推薦 (做者是mobx的做者)。 immer和mutable的修改數據的方法是一摸同樣的, 最後會根據你的修改返回一個不可變的對象。 github地址

結論

若是你能無痛的處理immutable, 那麼Redux + PureComponent 很方便寫出高性能的應用。

若是你對Mobx掌握的足夠好, 那麼Mobx絕對會迅速的提升開發效率。

本文代碼github地址

相關文章
相關標籤/搜索