1. 程式人生 > >mobx 在ReactJS專案中的運用

mobx 在ReactJS專案中的運用

mobx 能幹什麼

使用 react 寫小型應用,資料、業務邏輯和檢視的模組劃分不是很細是沒有問題的。在這個階段,引入任何狀態管理庫,都算是奢侈的。但是隨著頁面邏輯的複雜度提升,在中大型應用中,資料、業務邏輯和檢視,如果不能很好的劃分,就很有可能出現維護難、效能低下的問題。

業內比較成熟的解決方案有 redux,但是 redux 使用過程中,感覺較複雜和繁瑣。那麼為什麼不簡單一點呢?mobx 的核心理念是 簡單、可擴充套件的狀態管理庫。這可能正是你想要的。

react 關注的狀態(state)到檢視(view)的問題。而 mobx 關注的是狀態倉庫(store)到的狀態(state)的問題。

核心的概念1

mobx 最最核心的概念只有2個。 @observable 和 @observer ,它們分別對應的是被觀察者和觀察者。這是大家常見的觀察者模式,不過這裡使用了,ES7 中的 裝飾器。

使用 @observable 可以觀察類的值。

這裡使用 @observable 將 Store 的 todos 變為一個被觀察的值。

observable

倉庫

// 這裡引入的是 mobx
import {observable} from 'mobx';

class Store {
  @observable todos = [{
    title: "todo標題"
, done: false, }]; }

observer

mobx 元件

然後再使用 @observer ,將元件變為觀察者,響應 todos 狀態變化。
當狀態變化時,元件也會做相應的更新。

// 這裡引入的是 mobx-react
import {observer} from 'mobx-react';

@observer
class TodoBox extends Component  {
  render() {
    return (
      <ul>
        {this.props.store.todos.map(todo => <li>{todo.title}</li>)}
      </ul>
    )
  }
}

完整的 demo 如下。

import React, {Component} from 'react';
import { render } from 'react-dom';
import {observable} from 'mobx';
import {observer} from 'mobx-react';

// 最簡單的 mobx 就是一個觀察者模式

class Store {
  // 被觀察者
  @observable todos = [{
    title: "完成 Mobx 翻譯",
    done: false,
  }];
}

// 觀察者
@observer
class TodoBox extends Component  {
  render() {
    return (
      <ul>
        {this.props.store.todos.map(todo => <li>{todo.title}</li>)}
      </ul>
    )
  }
}


const store = new Store();

render(
  <TodoBox store={store} />,
  document.getElementById('root')
);

通過以上的簡單的例子,展現了 mobx 分離資料、檢視的能力

核心概念2

這一小節要介紹的兩個概念雖然也是核心概念,但是是可選的。

前面例子,只講了狀態的讀取,那麼狀態應該如何寫入呢?

答案是直接寫入!

@observer
class TodoBox extends Component  {
  render() {
    return (
      <div>
        <ul>
          {this.props.store.todos.map(
            (todo,index) => <li key={index}>{todo.title}</li>
          )}
        </ul>
        <div>
          <input type="button" onClick={() => {
            // 直接修改倉庫中的狀態值
            this.props.store.todos[0].title = "修改後的todo標題"
          }} value="點我"/>
        </div>
      </div>
    )
  }
}

細心的朋友一定發現了奇怪的地方,react 官方說過 props 值不能直接修改,但是引入 mobx 後 props 可以直接修改了,這太奇怪了!

解決辦法就是 mobx 的下一個概念 action。

actions
首先在 Store 中,定義一個 action。

class Store {
  @observable todos = [{
    title: "todo標題",
    done: false,
  }];
  @action changeTodoTitle({index,title}){
    this.todos[index].title = title
  }
}

在 Component 中呼叫,這樣通過 action 的方法,就避免了直接修改 props 的問題。

<input type="button" onClick={() => {
  this.props.store.changeTodoTitle({index:0,title:"修改後的todo標題"});
}} value="點我"/>

可以通過引入 mobx 定義的嚴格模式,強制使用 action 來修改狀態。

import {useStrict} from 'mobx';

useStrict(true);

computed values

在有些時候,state 並不一定是我們需要的最終資料。例如,所有的 todo 都放在 store.todos 中,而已經完成的 todos 的值(store.unfinishedTodos),可以由 store.todos 衍生而來。

對此,mobx 提供了 computed 裝飾器,用於獲取由基礎 state 衍生出來的值。如果基礎值沒有變,獲取衍生值時就會走快取,這樣就不會引起虛擬 DOM 的重新渲染。

通過 @computed + getter 函式來定義衍生值(computed values)。

import { computed } from 'mobx';

class Store {
  @observable todos = [{
    title: "todo標題",
    done: false,
  },{
    title: "已經完成 todo 的標題",
    done: true,
  }];

  @action changeTodoTitle({index,title}){
    this.todos[index].title = title
  }

  @computed get finishedTodos () {
    return  this.todos.filter((todo) => todo.done)
  }
}

mobx 有一套機制,如果衍生值(computed values)所依賴的基礎狀態(state)沒有發生改變,獲取衍生值時,不會重新計算,而是走的快取。因此 mobx 不會引起過度渲染,從而保障了效能。

當渲染的值為 finishedTodos ,點選修改標題,不會在控制檯列印 “render”;
換成 todos,就會列印 “render”.
這是由於已完成的 todos 值沒有改變,所以不會重新計算,而是走的快取。因此不會呼叫 render 方法。

完整 demo 如下

import React, {Component} from 'react';
import { render } from 'react-dom';
import {observable, action, computed,useStrict} from 'mobx';
import {observer} from 'mobx-react';

useStrict(true);


class Store {
  @observable todos = [{
    title: "todo標題",
    done: false,
  },{
    title: "已經完成 todo 的標題",
    done: true,
  }];

  @action changeTodoTitle({index,title}){
    this.todos[index].title = title
  }

  @computed get unfinishedTodos () {
    return  this.todos.filter((todo) => todo.done)
  }
}


@observer
class TodoBox extends Component  {

  render() {
    console.log('render');
    return (
      <div>
        <ul>
          { /* 把 unfinishedTodos 換成 todos,點選修改標題就會在控制檯列印 "render".*/ }
          {this.props.store.unfinishedTodos.map(
            (todo,index) => <li key={index}>{todo.title}</li>
          )}
        </ul>
        <div>
          <input type="button" onClick={() => {
            this.props.store.changeTodoTitle({index:0,title:"修改後的todo標題"});
          }} value="修改標題"/>
        </div>
      </div>
    )
  }
}

const store = new Store();

render(
  <TodoBox store={store} />,
  document.getElementById('root')
);

小結
翻譯了官網的一段文章,就拿過來做小結了。

mobx 是一個的簡單、可擴充套件的狀態管理庫。它背後的哲學非常簡單:

應用程式 state 是最基礎的資料。任何可以從 state 中衍生出來的資料,都應該自動的被衍生出。

actions 是唯一能夠改變 state 的方法。

state 是最基礎的資料,它不應該包含冗餘的和派生的資料。

computed values 派生值是通過純函式從 state 中派生而來的。當派生值依賴的狀態發生變化了,Mobx 將會自動更新派生值。如果依賴的狀態沒有改變,mobx 會做優化處理。

reactions 也是派生資料,是從 state 中派生而來的。它的副作用是自動更新 UI。(注:mobx 有一個 reaction 介面,當 state 改變時,就會呼叫它的回撥。UI 是通過 reaction 更新的。)

React 和 MobX 是非常強大的組合。React 提供了將應用狀態對映為可渲染的元件樹的機制。MobX 提供儲存和更新應用狀態的機制,供 React 使用。

React 和 MobX 提供了開發過程中常見問題的解決方案。 React 通過使用虛擬 DOM,減少了對瀏覽器 DOM 的操作。MobX 通過使用了響應式虛擬依賴狀態圖(reactive virtual dependency state graph) ,提供了應用程式狀態與 React 元件同步的機制,這樣 state 只會在需要時更新才會更新。(譯者注:這段有點難理解,大概的意思是 Mobx 關注的是 store 到 state 的過程,React 關注的是 state 到 view 的過程)。