**注意:**我會寫多篇文章來比較說明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
幫助咱們默認作了這個shouldComponentUpdate
web
把 L1, Dog, L11 ... L122改成PureComponent
, 再次點擊,打印:算法
// 沒有輸出。。。
複製代碼
拯救了地球!編程
redux 每次action發生的時候,都會返回一個全新的state,�天生是immutable。 Redux + PureComponent 輕鬆開發出高效web應用redux
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。
這裏暴露了兩個問題:
PureComponent
優化記住在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正如前文所說 不論是發佈爲第三方庫, 仍是使用第三方庫
這裏咱們只說 immutable的開發效率,mutable的開發效率應該是最低的。 0. 結合對象展開浮, js裸寫。 也不難
若是你能無痛的處理immutable, 那麼Redux + PureComponent 很方便寫出高性能的應用。
若是你對Mobx掌握的足夠好, 那麼Mobx絕對會迅速的提升開發效率。
本文代碼github地址