EY-Office ブログ

Reactステート管理ライブラリー MobX を試してみる

私は今までMobXを使った事はありませんでした。またReduxに比べ利用者は少ないようなので React用ステート管理2020 〜Recoilを試してみました〜でMobXは無視しましたが、気にはなっていたので試してみました。

MobX 6.0リリースおめでとうございます!

MobX

MobXのサンプルの解説

MobXはシンプルでスケーラブルなステート管理ライブラリーです、オブザーバー・パターン(Observer)を使ってステートの変化をViewに伝えいます。
今回はmobx-react-liteを使ってみました。従来のMobXは、クラスコンポーネントに@observer等のデコレータを使いMobXの設定を行うスタイルでしたが、mobx-react-liteはデコレータを使わず、Hooksを使う関数コンポーネント専用に作られたライブラリーです。

MobXのステートを共有する方法は MobX React integrationにあるように、いくつかの方法がありますが、今回のサンプルではReactのContextを使う事にしました。またMobx-React-Lite for Hooks in a nutshell with code sampleを参考にしました。

  • JyankenContext.tsxがMobX(mobx-react-lite)の核になるモジュールです
    • MobXのObservableオブジェクト(ステートやアクション)が格納さるJyankenContextの作成
    • JyankenProviderはJyankenContextのProviderの定義で、内部にuseLocalObservable APIで、Observableオブジェクトを定義しています
  • JyankenBox.tsxではContextでObservableオブジェクトを取得し、アクションを呼び出しています
  • ScoreBox.tsxはステートの変更で再表示するようにobserver APIでくくっています。またContextでObservableオブジェクトを取得しています

まとめ

Mobxは元祖Reduxに比べるとコード量が少なく使いやすいライブラリーと言われてきましたが、 Redux ToolkitやReactのuseReducerContextがリリースされた現在ではコード量は同程度であり、コードの構造も似たものになっています。

React用ステート管理2020 〜Recoilを試してみました〜を更新しMobXを追加しました。


サンプルコード

MobX

  • JyankenContext.tsx
import React, { createContext } from 'react'
import { useLocalObservable } from 'mobx-react-lite'

export enum Jjudgment { Draw = 0, Win, Lose }
export enum Te { Guu = 0, Choki, Paa}

export type ScoreType = {
  human: number,
  computer: number,
  judgment: Jjudgment
}

export type JyankenType = {
  scores: ScoreType[]
  pon: (human: Te) => void
}

export const JyankenContext = createContext({} as JyankenType)

export const JyankenProvider: React.FC = ({ children }) => {

  const store = useLocalObservable<JyankenType>(() => ({
    scores: [],
    pon(human: Te) {
      const computer:Te = Math.floor(Math.random() * 3)
      const judgment:Jjudgment = (computer - human + 3) % 3
      const score = {human: human, computer: computer, judgment: judgment}
      store.scores = [...store.scores, score]
    }
  }))

  return <JyankenContext.Provider value={store}>{children}</JyankenContext.Provider>
}
  • Jyanken.tsx
import React from 'react'
import JyankenBox from './JyankenBox'
import ScoreBox  from './ScoreBox'
import { JyankenProvider } from './JyankenContext'

const Jyanken = () => {
  return (
    <JyankenProvider>
      <h1>じゃんけん ポン!</h1>
      <JyankenBox />
      <ScoreBox />
    </JyankenProvider>
   )
}

export default Jyanken
  • JyankenBox.tsx
import React, { useContext } from 'react'
import { JyankenContext, Te } from './JyankenContext'

const JyankenBox: React.FC = () => {
  const divStyle: React.CSSProperties = {margin: "0 20px"}
  const buttonStyle: React.CSSProperties = {margin: "0 10px", padding: "3px 10px", fontSize: 14}

  const store = useContext(JyankenContext)
  return (
    <div style={divStyle}>
      <button onClick={() => store.pon(Te.Guu)} style={buttonStyle}>グー</button>
      <button onClick={() => store.pon(Te.Choki)} style={buttonStyle}>チョキ</button>
      <button onClick={() => store.pon(Te.Paa)} style={buttonStyle}>パー</button>
    </div>
  )
}

export default JyankenBox
  • ScoreBox.tsx
import React, { useContext } from 'react'
import { observer } from 'mobx-react-lite'
import { JyankenContext } from './JyankenContext'

const ScoreBox: React.FC = observer(() => {
  const teString = ["グー","チョキ", "パー"]
  const judgmentString = ["引き分け","勝ち", "負け"]

  const tableStyle: React.CSSProperties = {marginTop: 20, borderCollapse: "collapse"}
  const thStyle: React.CSSProperties = {border: "solid 1px #888", padding: "3px 15px"}
  const tdStyle: React.CSSProperties = {border: "solid 1px #888", padding: "3px 15px", textAlign : "center"}

  const scores = useContext(JyankenContext).scores
  return (
    <table style={tableStyle}>
      <thead>
        <tr>
          <th style={thStyle}>あなた</th>
          <th style={thStyle}>コンピュター</th>
          <th style={thStyle}>勝敗</th>
        </tr>
      </thead>
      <tbody>
        {scores.map((score, ix) =>
          <tr key={ix}>
            <td style={tdStyle}>{teString[score.human]}</td>
            <td style={tdStyle}>{teString[score.computer]}</td>
            <td style={tdStyle}>{judgmentString[score.judgment]}</td>
          </tr>
        )}
      </tbody>
    </table>
  )
})

export default ScoreBox
  • index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import Jyanken from './Jyanken'

ReactDOM.render(
  <React.StrictMode>
    <Jyanken />
  </React.StrictMode>,
  document.getElementById('root')
)

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ