1. 程式人生 > >React Native 搭配 MobX 使用心得

React Native 搭配 MobX 使用心得

MobX 是一款十分優秀的狀態管理庫,不但書寫簡潔還非常高效。當然這是我在使用之後才體會到的,當初試水上車的主要原因是響應式,考慮到可能會更符合 Vue 過來的思考方式。然而其實兩者除了響應式以外並沒有什麼相似之處joy

在使用過程中走了不少彎路,一部分是因為當時掃兩眼文件就動手,對 MobX 機制理解得不夠;其它原因是 MobX 終究只是一個庫,會受限於 React 機制,以及與其它非 MobX 管理元件的相容問題。當中很多情況在文件已經給出了說明(這裡這裡),我根據自己遇到的再做一番總結。

與非響應式元件相容問題

與非響應式的元件一起工作時,MobX 有時需要為它們提供一份非響應式的資料副本,以免 observable 被其它元件修改。

observable.ref

使用 React Navigation 導航時,如果要交由 MobX 管理,則需要手動配置導航狀態棧,此時用 @observable.ref “淺觀察”可避免狀態被 React Navigation 修改時觸發 MobX 警告。

當 Navigator 接受 navigation props 時代表導航狀態為手動管理。

import { addNavigationHelpers, StackNavigator } from 'react-navigation'
import { observable, action } from 'mobx'
import { Provider, observer } from
'mobx-react' import AppComp from './AppComp' const AppNavigator = StackNavigator({ App: { screen: AppComp }, // ... }, { initialRouteName: 'App', headerMode: 'none' }) @observer export default class AppNavigation extends Component { @observable.ref navigationState = { index: 0, routes
: [ { key: 'App', routeName: 'App' } ], } @action.bound dispatchNavigation = (action, stackNavState = true) => { const previousNavState = stackNavState ? this.navigationState : null this.navigationState = this.AppNavigator.router.getStateForAction(action, previousNavState) return this.navigationState } render () { return ( <Provider dispatchNavigation={this.dispatchNavigation} navigationState={this.navigationState} > <AppNavigator navigation={addNavigationHelpers({ dispatch: this.dispatchNavigation, state: this.navigationState, })} /> </Provider> ) } }

observable.shallowArray() 與 observable.shallowMap()

MobX 還提供其它方便的資料結構來存放非響應式資料。

比如使用 SectionList 的時候,我們要為其提供資料用於生成列表,由於 Native 官方的實現跟 MobX 不相容,這個資料不能是響應式的,不然 MobX 會報一堆警告。

MobX 有個 mobx.toJS() 方法可以匯出非響應式副本;如果結構不相同還可以使用 @computed 自動生成符合的資料。但這兩個方法每次新增專案都要全部遍歷一遍,可能會存在效能問題。

這時其實可以維護一個 observable.shallowArray,裡面只放 key 資料,只用於生成列表(像骨架一樣)。傳給 SectionList 的 sections props 時 slice 陣列複製副本(shallowArray 裡的資料非響應式,所以只需淺複製,複雜度遠小於上面兩種方式)。

然後 store 維護一個 observable.map 來存放每個項的資料,在項(item)元件中 inject store 進去,再利用 key 從 map 中獲取資料來填充。

通過 shallowArray 可以讓 MobX 識別列表長度變化自動更新列表,利用 map 維護項資料可以使每個項保持響應式卻互不影響,對長列表優化效果很明顯。

// store comp
class MyStore {
  @observable sections = observable.shallowArray()
  @observable itemData = observable.map()

  @action.bound appendSection (section) {
    const data = []
    section.items.forEach(action(item => {
      this.itemData.set(item.id, item)
      data.push({key: item.id})
    }))
    this.sections.push({
      key: section.id,
      data
    })
  }
}
// MyList comp
import { SectionList } from 'react-native'
@inject('myStore')
@observer
class MyList extends React.Component {
  _renderItem = ({item}) => <SectionItem id={item.key} />

  render () {
    return (
      <SectionList
        getItemLayout={this._getItemLayout}
        sections={this.props.myStore.sections.slice()}
        renderSectionHeader={this._renderSectionHeader}
        renderItem={this._renderItem}
      />
    )
  }
}
// SectionItem comp
@inject('myStore')
@observer
class SectionItem extends React.Component {
  render () {
    const {myStore, id} = this.props
    const itemData = myStore.itemData.get(id)
    return (
      <Text>{itemData.title}</Text>
    )
  }
}

computed

利用 @computed 快取資料可以做一些優化。

比如有一個響應式的陣列 arr,一個元件要根據 arr 是否為空更新。如果直接訪問 arr.length,那麼只要陣列長度發生變化,這個元件都要 render 一遍。

此時利用 computed 生成,元件只需要判斷 isArrEmpty 就可以減少不必要的更新:

@computed get isArrEmpty () {
  return this.arr.length <= 0
}

observable.map

因 JS 機制 MobX 不能檢測屬性的增刪,所以最好用 observable.map 取代簡單 {} 物件。另外 MobX 沒有提供 Set 支援,可以用 key 和 value 一樣的 Map 代替。

避免在父元件中訪問子元件的屬性

這條規則在文件也提到,原因很簡單,MobX 對於一個 observer 元件,是通過訪問屬性來記錄依賴的。所以哪怕父元件裡沒有用到這個屬性,只是為了作為 props 傳給子元件,MobX 還是算它依賴了這個屬性,於是會產生不必要的更新。最好的方式是將資料統一放在 store 中,子元件通過 inject store 方式獲取資料。

小元件

由於 React 的機制,MobX 只能在元件層面發光發熱,對於元件內部就無能為力了。所以大元件用 MobX 很容易卡死(用其它也會sweat_smile),小元件才能真正發揮 MobX 自動管理更新的優勢。