Redux vs Mobx系列(-):immutable vs mutable
**注意:**我會寫多篇文章來比較說明redux和mobx的不同,redux和mobx各有優缺點, 如果對React/Mobx/Redux都理解夠深刻,我個人推薦Mobx(逃跑。。。)
React社區的大方向是immutable, 不管是用immutable.js 還是函數式編程使用不可變數據結構。為什么React需要不可變數據結構呢? 考慮下面的一個應用
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會怎么樣呢? 結論是當點擊的時候 控制臺會打印:
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暴力比較算法已經非常高效了,這些無意義的計算也應該避免, 起碼可以節省計算機的電 --> 少用煤 --> 減少二氧化碳排放 --> 保護地球。 畢竟 蝴蝶效應!
ui = f(d) 相同的d得到相同的ui(設計組件的時候最好這樣)。例如我們上例的Dog,我們可以直接比較sh
class Dog extends Component {shouldComponentUpdate(nextProps) {return this.props.sh !== nextProps.sh}... } 復制代碼更加一般的情況, 我們怎么確定組件的props和state沒有變化呢? 不可變對象 ! 如果對象是不可變的, 那么當對象a !== a' 就代表這是2個對象,不相等。而在傳統可變的對象中 需要deepEqual(a, a')。 如果我們的React應用里面 props和state都是不可變對象, 那么:
class X extends Component {shouldComponentUpdate(nextProps, nextState) {return !( shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState))} } 復制代碼react也考慮到了一點 提供了PureComponent幫助我們默認做了這個shouldComponentUpdate
把 L1, Dog, L11 ... L122改為PureComponent, 再次點擊,打印:
// 沒有輸出。。。 復制代碼拯救了地球!
Redux
redux 每次action發生的時候,都會返回一個全新的state,�天生是immutable。 Redux + PureComponent 輕松開發出高效web應用
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 } = storereturn (<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。
這里暴露了兩個問題:
記住在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} = storereturn (<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.storereturn (<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正如前文所說 不管是發布為第三方庫, 還是使用第三方庫
開發效率
這里我們只說 immutable的開發效率,mutable的開發效率應該是最低的。 0. 結合對象展開浮, js裸寫。 也不難
結論
如果你能無痛的處理immutable, 那么Redux + PureComponent 很方便寫出高性能的應用。
如果你對Mobx掌握的足夠好, 那么Mobx絕對會迅速的提高開發效率。
本文代碼github地址
總結
以上是生活随笔為你收集整理的Redux vs Mobx系列(-):immutable vs mutable的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LAMP(4)Apach和php结合、A
- 下一篇: Windbg内核调试之一: Vista