LoginSignup
3
0

More than 3 years have passed since last update.

【React / React Native】ReduxからMST(mobx-state-tree)へ

Posted at

MSTの概要

ポストReduxとして注目を浴びているのが、Mobxの拡張であるMST(Mobx-state-tree)です。Mobxはシンプルで扱いやすい一方で、いろいろな問題が起きやすいため、タイプ情報を定義することによって、それらを極めてラディカルに解決しようというのがこのMSTになります。
今回はMobX-State-Treeの基本的な仕組みと書き方をメモします。

MSTとは何か

Mobx単体との違い

主な参考記事:
https://medium.com/react-native-training/state-management-with-mobx-state-tree-373f9f2dc68a

何が違うか:

  • Opinionated(独善的): データがどのように構造化されるべきか、または更新されるべきかがルール化されている。
  • Typed — 独自のtypes APIがある
  • Models — 複数モデルのツリーが生成される

observable(観察可能)であること、mutable(高可変性)であることは同じです。

哲学

公式サイトより翻訳(意訳あり)
https://mobx-state-tree.js.org/intro/philosophy

MST(mobx-state-tree)は、 可変データの「シンプルさ」と「扱いやすさ」、不変データの「Reactivity」、「トレーサビリティ」、そして「観測可能データのパフォーマンス」を備えたStateのコンテナ(container)です。

簡単にいうと、MSTは、状態管理に対する不変性(取引のしやすさ、トレーサビリティ、構成)と可変性(検出のしやすさ、配列法、カプセル化)の両方のアプローチの最良の特徴を組み合わせることを目的としています。

Mobx単体とは違って、MSTはデータをどのように構造化し、更新すべきか独自のルールに従って決めることで、Mobxでよく起こる問題をすぐに解決できます。

MSTの中心にあるのは、生きた木(ツリー)という概念です。このツリーは、変更可能ですが厳密に保護されたオブジェクトで構成され、実行時タイプ情報が付加されます。つまり、各ツリーには形状(タイプ情報)状態(データ)があります。

この生きた木から、普遍的な、共通のスナップショットが自動的に生成されます。

サンプルコード

以下は、公式サイトにある簡易的なサンプルコードを少し修正したものです。

MSTのサンプルコード
import { types, onSnapshot } from "mobx-state-tree"

// typesを使ってmodel構造とactionsを定義
const Todo = types
    .model("Todo", {
        title: types.string,
        subTitle: types.string,
        done: false
    })
    .views(self => ({
        fullTitle() {
            return self.title + self.subTitle
        }
    }))
    .actions(self => ({
        toggle() {
            self.done = !self.done
        }
    }))

// StoreでTodoモデルをラップ
const Store = types.model("Store", {
    todos: types.array(Todo)
})

// Storeからインスタンスを作成
const store = Store.create({
    todos: [
        {
            title: "Get coffee"
        }
    ]
})

// 新しいsnapshotの作成をlisten
onSnapshot(store, snapshot => {
    console.dir(snapshot)
})

// toggleアクションを呼んでみる
store.todos[0].toggle()

// doneのtrue/falseが切り替わる
// onSnapshotが発火し、コンソールログに`{ todos: [{ title: "Get coffee", done: true }]}`が表示される

// viewsからフルタイトルを取得する
store.todos[0].title()

modelファイルはmodels/配下に置くのが一般的なようです。

Reduxに比べてめちゃくちゃシンプルですが、グローバルなState/Action管理という目的では特に過不足ないかと思います。

Example

公式でExampleが用意されています。

オブジェクトのネストと配列

例えば、以下のようなネストされたオブジェクトや配列を管理したい場合について解説します。

{
    "id": 1,
    "location": {
        "latitude": 135.22313,
        "longitude":  41.2335,
    },
    "tags": [{name: "Male"}, {name: "Premium"},{name: "Approved"}],
    "addresses": [
        {
            "id": 2,
            "zip_code": "1234567",
            "prefectures": "東京都",
            "city": "渋谷区",
            "street_address": "渋谷",
            "street_address_optional": "1-23-4",
        }
    ]
}

オブジェクトの形式のデータをネストする場合は、modelを作成してそれをネストします。

const locationModel = types.model({
  latitude: types.number,
  selected: types.number
});

const userModel = types.model({
  id: 1,
  location: locationModel,
  ...
});

配列の場合は、types.arrayを使用します。

const tagModel = types.model({
  name: types.string
});

const userModel = types.model({
  id: 1,
  tags: types.array(tagModel),
  ...
});

Viewとの組み合わせ方

observeはmobx-reactで提供されているモジュールを使います。

import { observer } from "mobx-react";

const App = observer(props => (
    <div>
        <button onClick={e => props.store.addTodo(randomId(), "New Task")}>Add Task</button>
        {values(props.store.todos).map(todo => (
            <div>
                <input type="checkbox" checked={todo.done} onChange={e => todo.toggle()} />
                <input type="text" value={todo.name} onChange={e => todo.setName(e.target.value)} />
            </div>
        ))}
    </div>
))

まとめ

自社のReact Nativeアプリで導入しましたが特に大きな問題はなさそうです。
Reduxのように洗練されたベストプラクティスがないため、Storeの設計などには苦労しそうです。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0