前置き
大層なタイトルがついていますが、単純に筆者の学習録です。
公式を読む前の前座程度に考えてください。
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として定義します。
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の記述です。
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;
用意したコンポーネントを使ってみよう
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を用意します。
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がまるっきり出てこないのでとても綺麗。
import React from 'react';
import SampleComponent from './sample';
export default () => <SampleComponent />;
Storeを用いるレイヤーを用意
ここでinjectする。
this.props.sampleStoreが使えるはずなので、
Storeから値をとったり、更新したりする。
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
準備
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
"scripts": {
"dev": "webpack --mode development",
"watch": "webpack --mode development --watch",
"start": "node ./bin/www"
},
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の表示まで
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app.js';
ReactDOM.render(<App />, document.getElementById('root'));
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
AppComponent
</div>
);
}
}
export default App;
extends layout
block content
div#root
script(src='/main.js')
npm run dev
npm start
# localhost:3000にアクセス