LoginSignup
40
31

More than 5 years have passed since last update.

MobXを学ぶ(初級)

Posted at

前置き

大層なタイトルがついていますが、単純に筆者の学習録です。

公式を読む前の前座程度に考えてください。

MobXとは

Introductionを読んでも、イメージができなかったので、
下記の記事がとてもわかりやすかったです。

参考:
これからMobXをはじめる人へ

ReactiveなObservableな値を用意して、それを変更したら関連するものも全部更新されて・・と、そういういわゆるState管理の層だけをやるライブラリです。

mobx-reactというバインディングがあって、Observableな値を更新すると勝手にコンポーネントのrender()が走るみたいにできます。

読み方がわからない

読み方は・・これなんでしょうね・・。
Mob X。
モブクス、マブクス。

FluxやReduxが、〜ックスだから、
日本人だとモブックスなのかなぁ。

React + MobX

「とりあえず使う」ために抑えること

覚えるもの 説明 備考
mobx.observable State管理で監視したい値につけるもの
mobx.computed observableな値に依存する値 Excelのセル参照をイメージするといい
mobx.action observableな値を変更する関数にはactionをデコレート トランザクション等のメリットがある
mobx-react.observer Reactコンポーネントをリアクティブなコンポーネントに変換する mobx.observableな値を使うために必要、くらいのレベルで良さそう

参考:
これからMobXをはじめる人へ
MobXでReactのステート管理をする
MobXの observable と action について

何かを作るために意識しておくこと

[1]用意するものはStoreコンポーネント
[2]Storeには、表示に必要なものを全てぶち込む
例えば、

  • 表示したい値
  • 値を変更するための関数

[3]コンポーネントにStoreをぶち込む。

とりあえずやってみる

Storeを用意する

  • 数値
  • 2倍にした数値
  • 数値の入力

をStoreとして定義します。

src/stores/sample.js
import {observable, computed, action} from 'mobx';

export default class SampleStore {
    @observable num = 0;

    @computed get double() {
        return this.num * 2;
    }

    @action changeNum = (newNum) => this.num = Number(newNum);
}

Storeを使うコンポーネントを用意する

作成したStateをpropsで渡される想定です。

デコレーションでリアクティブコンポーネントに変換している以外は、
ただのReactの記述です。

src/components/sample.js
import React, {Component} from 'react';
import {observer} from 'mobx-react';

@observer
class SampleComponent extends Component {
    render() {
        return (
            <div>
                <input type="number"
                    value={this.props.store.num}
                    onChange={(e) => this.props.store.changeNum(e.target.value)}
                />
                <div>{this.props.store.num}</div>
                <div>{this.props.store.double}</div>
            </div>
        );
    };
}

export default SampleComponent;

用意したコンポーネントを使ってみよう

src/app.js
import React, { Component } from 'react';
import SampleStore from './stores/sample';
import SampleComponent from './components/sample';

const store = new SampleStore();

class App extends Component {
    render() {
        return (
            <div>
                <SampleComponent store={store} />
            </div>
        );
    }
}

export default App;

参考:
The gist of MobX

Storeをオシャレに取り出す

上記の記事を拝見していたら、injectなる言葉が。

どうやらmobx-reactに用意されているものっぽい。

そうか・・mobxの公式を網羅できれば極まるかと思ったが、そんなわけないか。

Provider is a component that can pass stores (or other stuff) using React's context mechanism to child components. This is useful if you have things that you don't want to pass through multiple layers of components explicitly.

inject can be used to pick up those stores. It is a higher order component that takes a list of strings and makes those stores available to the wrapped component.

ReactのContextの仕組みを使っているらしい。
※知らない人(主に私)は、先にこっちを読んだ方がイメージがつかみやすいかも。

Providerは、子コンポーネントにStoreを渡す仕組みで、
コンポーネントが階層構造になっているとき、Storeを意識させたくないときに便利。

injectは、使いたいコンポーネントでStoreを取得するのに使う。

って感じ・・?

ちょっとやってみましょう。

Providerとinjectを使ってみる

「とりあえずやってみる」で作成したコンポーネントを無駄にもう1階層深くしてみましょう。

ProviderにSampleStoreをセット

子コンポーネントがthis.props.sampleStoreを使えるように、
Providerを用意します。

src/app.js
import React, { Component } from 'react';
import {Provider} from 'mobx-react';
import SampleStore from './stores/sample';
import MiddleComponent from './components/middle';

const store = new SampleStore();

class App extends Component {
    render() {
        return (
            <div>
                <Provider
                    sampleStore={store}
                >
                    <MiddleComponent />
                </Provider>
            </div>
        );
    }
}

export default App;

Storeを用いないレイヤーを用意する

俺Store使わんしwww
というコンポーネントを用意。

Storeがまるっきり出てこないのでとても綺麗。

src/components/middle.js
import React from 'react';
import SampleComponent from './sample';

export default () => <SampleComponent />;

Storeを用いるレイヤーを用意

ここでinjectする。

this.props.sampleStoreが使えるはずなので、
Storeから値をとったり、更新したりする。

src/components/sample.js
import React, {Component} from 'react';
import {inject, observer} from 'mobx-react';

@inject('sampleStore')
@observer
class SampleComponent extends Component {
    render() {
        return (
            <div>
                <input type="number"
                    value={this.props.sampleStore.num}
                    onChange={(e) => this.props.sampleStore.changeNum(e.target.value)}
                />
                <div>{this.props.sampleStore.num}</div>
                <div>{this.props.sampleStore.double}</div>
            </div>
        );
    };
}

export default SampleComponent;

付録

学習環境

Node.js(express) + React + mobx
で想定。

参考:
How to (not) use decorators

準備

express
mkdir sample
express --view=pug sample
cd sample
npm install
必要なものを入れる
npm install -D \
@babel/cli \
@babel/core \
@babel/plugin-transform-runtime \
@babel/preset-env \
@babel/preset-react \
@babel/preset-stage-2 \
@babel/runtime \
webpack \
webpack-cli \
babel-loader@8.0.0-beta.0

npm install react react-dom mobx mobx-react --save
package.jsonのscriptsに以下を足しておく
  "scripts": {
    "dev": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "start": "node ./bin/www"
  },
webpack.config.babel.js
const path = require('path');

exports.default = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'public'),
        filename: 'main.js'
    },
    devtool: 'inline-source-map',
    module: {
        rules: [
            {
                test: /\.(js)$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            ['@babel/preset-env', { 'modules': false }],
                            '@babel/preset-react',
                            ['@babel/preset-stage-2', { 'loose': true, 'decoratorsLegacy': true }]
                        ]
                    }
                }
            }
        ]
    }
};

Reactの表示まで

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app.js';

ReactDOM.render(<App />, document.getElementById('root'));
src/app.js
import React, { Component } from 'react';

class App extends Component {
    render() {
        return (
            <div>
                AppComponent
            </div>
        );
    }
}

export default App;
index.pug
extends layout

block content
  div#root
  script(src='/main.js')
表示
npm run dev
npm start
# localhost:3000にアクセス
40
31
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
40
31