**注意:**我會寫多篇文章來比較說明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 } = 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
優化 - 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正如前文所說 不管是釋出為第三方庫, 還是使用第三方庫
- mobx寫的元件,釋出給其他應用使用比較困難,因為要不我們直接從全域性取資料渲染(context獲取 道理相同), 要不推遲引用值的獲取, 不管是哪一種,元件都沒有任何可讀性。
- mobx 使用第三方 例如BigComponent, 沒有那麼自然。
開發效率
這裡我們只說 immutable的開發效率,mutable的開發效率應該是最低的。 0. 結合物件展開浮, js裸寫。 也不難
- immutable.js 學習成本略高, 包大小也畢竟大
- 函數語言程式設計,專案組自己一個人 可以考慮
- immer 如果不考慮IE,強烈推薦, 強烈推薦 (作者是mobx的作者)。 immer和mutable的修改資料的方法是一摸一樣的, 最後會根據你的修改返回一個不可變的物件。 github地址
結論
如果你能無痛的處理immutable, 那麼Redux + PureComponent 很方便寫出高效能的應用。
如果你對Mobx掌握的足夠好, 那麼Mobx絕對會迅速的提高開發效率。
本文程式碼github地址