Posted at

MobXを学ぶ(初級)

More than 1 year has passed since last update.


前置き

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

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


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にアクセス