1. 程式人生 > >強大的狀態管理工具-Mobx

強大的狀態管理工具-Mobx

前言

Mobx是一款精準的狀態管理工具庫,如果你在 React 和 React Native 應用中使用過 Redux ,那毫不猶豫地說,MobX 的簡單性將成為你狀態管理的不二之選,本文主要講的是關於Mobx在React-native下的使用和常見問題。

常見API

在使用Mobx之前,首先介紹幾個常見的API

1. observable

Mobx如此簡單的原因之一,就是使用了可觀察資料(observable Data),簡單來說,可觀察資料就是可以觀察到資料的讀取,寫入,並進行攔截
Mobx中提供了observable介面來定義可觀察資料,可觀察資料的型別可以使基本資料型別,object,array或者ES6中的Map型別,
注意:

陣列經過observable包裝之後,就不是Array型別了,而是Mobx中的一個特殊型別,observable型別。
雖然資料型別不一樣,但是使用方式和原來使用方式一致(原始資料型別除外)

const Array =  observable([1,2,3]);
const object =  observable({name: 'Jay'});
const Map =  observable(new Map([['name','ding']]));

console.log(Array[0])  // 1
console.log(object.name)  // Jay
console.log(Map.get('name'))  // ding

@observable

裝飾器可以再ES7或者TypeScript類屬性中使用,將其轉換成可觀察的。 @observable可以再欄位和屬性getter上使用,對於物件的哪部分需要成為可觀察物件,@observable 提供了細粒度的控制。

import { observable, computed } from "mobx";

class Order {
    @observable price = 0;
    @observable amount = 1;

    @computed get total() {
        return this.price * this.amount;
    }
}

observer

observer接收一個React-native元件作為引數,並將其轉換成響應式元件


@observer export default class App extends Component  {
    render() {
        return (
            <View></View>
        )
    }
}

響應式元件,即當且僅當元件依賴的可觀察物件資料發生改變時,元件才會自動相應並且重新渲染,而在傳統的react-native應用中,當狀態屬性變化後會先呼叫shouldComponentUpdate,該方法會深層對比前後狀態和屬性是否發生改變,再確定是否更新元件。

shouldComponentUpdate是很消耗效能的,Mobx通過可觀察資料,精確地知道元件是否需要更新,減少了利用shouldComponentUpdate這個方法,這是Mobx效能好的原因之一
陷阱:Mobx可以做很多事,但是它還是無法將原始資料型別轉換成可觀察的,所以值是不可觀察的,但是物件的屬性可以被觀察,這意味著 @observer 實際上是對間接引用(dereference)值的反應。

computed

計算值(computed values)是可以根據現有狀態或其它計算值衍生出的值,計算的耗費是不可低估的,computed儘可能幫你減少其中的耗費,它們是高度優化的。
computed values是自動幫你從你的狀態(state)值和其他計算輔助值來計算的。MobX做了很多的優化。當參與計算的值沒有發生改變,Computed是不會重新執行。如果參與計算的值沒有被使用,Computed values是暫停的。如果Computed values不再是觀察者(observed),那麼在UI上也會把它除掉,MobX能自動做垃圾回收,用法如下:

class foo {
    @observable length: 2,
    @computed get squared() {
        return this.length * this.length;
    }
}

Autorun

Autorun是用在一些你想要產生一個不用觀察者參與的被動呼叫函式裡面。當autorun被使用的時候,一旦依賴項發生變化,autorun提供的函式就會被執行。與之相反的是,computed提供的函式只會在他有自己的觀察員(observers)的時候才會評估是否重新執行,否則它的值被認為是無用的
綜上所述:如果你需要一個自動執行但確不會產生任何新的值的結果的函式,就可以使用Autorun,其他情況可以使用computed,Autorun只是作用於如果達到某個效果或者功能,而不是計算某些值
就像 @ observer 裝飾器/函式,autorun 只會觀察在執行提供的函式時所使用的資料。

var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));
var disposer = autorun(() => console.log(sum.get()));
// 輸出 '6'
numbers.push(4);
// 輸出 '10'

disposer();
numbers.push(5);
// 不會再輸出任何值。`sum` 不會再重新計算

action

  • 任何應用程式都有操作(action),action是任何改變狀態的事物,使用Mobx
    ,可以通過標記他們在你的程式碼中顯式的顯示你的操作(action),它會更好的組織你的程式碼,它們用於修改可觀察量或具有副作用的任何函式中。
    需要注意的是:action是用在strict mode 中的

    用法:

  • action(fn)
  • action(name, fn)
  • @action classMethod() {}
  • @action(name) classMethod () {}
  • @action boundClassMethod = (args) => { body }
  • @action(name) boundClassMethod = (args) => { body }
  • @action.bound classMethod() {}
  // 新增圖片
  @action addImg = () => {
    ImagePicker.openPicker({
      multiple: true,
      waitAnimationEnd: false,
      includeExif: true,
      forceJpg: true,
      maxFiles: 9 - this.imgs.length,
      compressImageQuality: 0.5,
    }).then((images) => {
      console.log(images)
    }).catch((err) => {
      console.warn(err)
    })
  }

2.Action僅僅作用於當前執行的函式,而不是作用於當前函式呼叫的函式,這意味著在一些定時器或者網路請求,非同步處理的情況下,它們的回撥函式無法對狀態改變,這些回撥函式都應該包裹在action裡面,但是,如果你使用了async / await的話,最好的方式應該是使用 runInAction 來讓它變得更加簡單

@action /*optional*/ updateDocument = async () => {
    const data = await fetchDataFromUrl();
    /* required in strict mode to be allowed to update state: */
    runInAction("update state after fetching data", () => {
        this.data.replace(data);
        this.isSaving = true;
    })
}

常見可觀察型別

Observable 物件

observable.object方法將物件變為可觀察的,它實際上是把物件的所有屬性轉換為可觀察的,存放到一個代理物件上,以減少對原物件的汙染,預設情況下,observable是遞迴應用的,所以如果物件的某個值是一個物件或陣列,那麼該值也將通過 observable 傳遞

import {observable, autorun, action} from "mobx"

var person = observable({
    name : 'jack',
    age:24,
    sex:'男'
})

Observable 陣列

與物件類似,可以使用Observable.array(array)或者將陣列傳給 observable,可以將陣列轉換成可觀察的,這也是遞迴的,所以陣列中的所有(未來的)值都會是可觀察的。

import {observable, autorun} from "mobx";
var todos = observable([
    { title: "Spoil tea", completed: true },
    { title: "Make coffee", completed: false }
]);
autorun(() => {
    console.log("Remaining:", todos
        .filter(todo => !todo.completed)
        .map(todo => todo.title)
        .join(", ")
    );
});
// 輸出: 'Remaining: Make coffee'

todos[0].completed = false;
// 輸出: 'Remaining: Spoil tea, Make coffee'

todos[2] = { title: 'Take a nap', completed: false };
// 輸出: 'Remaining: Spoil tea, Make coffee, Take a nap'

todos.shift();
// 輸出: 'Remaining: Make coffee, Take a nap'

除了可以使用所有的內建函式,observable陣列還提供了好多方法供我們使用

  • clear() -- 從陣列中刪除所有項
  • replace(newItems) -- 用新元素替換陣列中所有已存在的元素
  • remove(value) -- 通過值從陣列中移除一個單個的元素。

注意:

不同於sort和reverse函式的實現,observableArray.sort 和 observableArray.reverse 不會改變陣列本身,而只是返回一個排序過/反轉過的拷貝,在 MobX 5 及以上版本中會出現警告。推薦使用 array.slice().sort() 來替代。

Observable Map

與陣列的處理方式類似,Mobx也實現了一個ObservableMap類,不過只支援字串。數字或Bool值作為鍵,ObservableMap在可觀察物件的基礎上,還要使鍵的增刪可觀察。它可以看做兩個可觀察對映和一個可觀察陣列的組合:

import {observable, autorun} from "mobx"
const map = observable(new Map());

autorun(() => {
  console.log(map.get('key'));
});

map.set('key', 'value'); // 新增 key-value 鍵值對,輸出 value
map.set('key', 'anotherValue'); // 修改為 key-anotherValue,輸出 anotherValue
map.set('prop', 'value'); // 不輸出
map.delete('prop'); // 不輸出

優化React 元件

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

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

小元件

由於React的機制,Mobx只能在元件層發光發熱,對於元件內部就是無能為力了,所以大元件很容易卡死,小元件才能真正發揮Mobx的優勢。

在專用元件中渲染列表

React在渲染大型資料集合的時候處理的很不好,因為協調器必須評估每個集合變化的集合所產生的元件。因此,建議使用專門的元件來對映集合並渲染這個元件,且不再渲染其他元件:

官方提供的demo如下:

不妥的處理方式:

@observer class MyComponent extends Component {
    render() {
        const {todos, user} = this.props;
        return (<div>
            {user.name}
            <ul>
                {todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
            </ul>
        </div>)
    }
}

在示例中,當user.name發生變化時React 會不必要地協調所有的 TodoView 元件。儘管TodoView 元件不會重新渲染,但是協調的過程本身是非常昂貴的。

正確的處理方式:

@observer class MyComponent extends Component {
    render() {
        const {todos, user} = this.props;
        return (<div>
            {user.name}
            <TodosView todos={todos} />
        </div>)
    }
}

@observer class TodosView extends Component {
    render() {
        const {todos} = this.props;
        return <ul>
            {todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
        </ul>)
    }
}

在react-native使用Mobx常見的問題

observable 陣列的型別是物件

observable 陣列型別其實是個物件,所以它遵循propTypes.object ,如果使用propTypes.array 會報錯。mobx-react 為 observable 資料結構提供了明確的 PropTypes。

在 React Native 中渲染 ListView

React Native 的的DataSource只能接收真正的陣列,但是observable 陣列是個物件,所以在傳給ListView之前使用.slice方法,此外,ListView.DataSource 本身可以移到 store 之中並且使用 @computed 自動地更新,這步操作同樣可以在元件層完成

class ListStore {
  @observable list = [
    'Hello World!',
    'Hello React Native!',
    'Hello MobX!'
  ];

  ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });

  @computed get dataSource() {
    return this.ds.cloneWithRows(this.list.slice());
  }
}

const listStore = new ListStore();

@observer class List extends Component {
  render() {
    return (
      <ListView
        dataSource={listStore.dataSource}
        renderRow={row => <Text>{row}</Text>}
        enableEmptySections={true}
      />
    );
  }
}

原文http://techblog.sishuxuefu.com/atricle.html?5bb9f17a0b6160006f5988bb