最近いまさらMobxを触り始めて、控えめに言って最高だったので、nextjs
+ TypeScript
+ mobx
の環境構築方法をメモしておく、TypeScriptとの相性も良いし、reducerだの書かなくてよいし、Vueで使えるならそっちでも使いたいと思えるぐらい良かった。
まだ触り始めたばっかりなので、これから色々拡張していく気がするけど、とりあえず最低限動かせるところまでメモしておく
構築
nextjs + TypeScript環境の構築
公式サイトにexampleがあるので、簡単のためそれを使う
$ npx create-next-app --example with-typescript my-app
mobxの導入
$ npm install mobx mobx-react
現時点(2019/08/12)では@babel/plugin-proposal-class-properties
と @babel/plugin-proposal-decorators
を入れないと、nextjs
環境で mobx
のデコレータをうまく使えないので以下をインストール
$ npm install --save-dev @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators
さらに.babelrc
を以下のように追加する
$ touch .babelrc
{
"presets": [
"next/babel"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
]
}
また、TypeScript環境だと、experimentalDecorators
オプションを有効化しないと警告が出るので、tsconfigを以下のように書き換える
{
"compilerOptions": {
... 略
"experimentalDecorators": true, // <= 追加
....略
},
}
動作確認
Storeの作成
まだ触り始めたばっかりなので、mobxでどういうstore設計にするのが良いのかはよくわかっていない。
とりあえず現時点で動いているものをメモする
とりあえずStoreを作る。プロジェクト直下にstore
ディレクトリを作り、以下の2つのファイルを作る
今回作るのはチュートリアルでよく使われるカウンターアプリ、ストアの中には、counter
という変数と、そのカウンターを増加させるincrement()
関数をとりあえず作る
import { observable, action } from 'mobx'
export class CounterStore{
@observable counter = 0
@action
increment(){
this.counter++
}
}
@observable
というデコレータがクラス変数や関数につけられていることがわかる。@observable
を付与された変数はmobxの変更監視対象となり、この値を参照しているコンポーネントがあれば、値の変更に応じて、適切にコンポーネントを再レンダリングしてくれるようになる。
加えて、Storeのインスタンスを保持するstore.ts
を作る。ここで作成されたStoreをコンポーネントから呼び出すことで、Storeの状態や関数にアクセスできる
import { CounterStore } from "./CounterStore";
import { configure } from "mobx";
// actionを経由せずに@observableをつけたプロパティを更新しようとするとエラーになるように設定
configure({ enforceActions: 'always' });
export const counterStore = new CounterStore()
ストアの記述はこれだけ、とても簡単。実際のところmobxはStoreを作るためのライブラリというよりは、単に「JSインスタンスの変数の変更をコンポーネント側に通知するライブラリ」って感じっぽい。
この「一つのことをうまくやる」感じはとても良い。Reduxはヘビーすぎるねん・・・
ページコンポーネントの作成
page
ディレクトリの直下に以下のファイルを作ってStoreがうまく使えるか試してみる
import * as React from 'react'
import Layout from '../components/Layout'
import { counterStore } from '../store/store';
import { observer } from 'mobx-react'
@observer
class CounterPage extends React.Component {
render() {
return (
<Layout>
<p>counter: {counterStore.counter}</p>
<button onClick={() => counterStore.increment()}>+</button>
</Layout>
)
}
}
export default CounterPage
@observer
というデコレータをclassコンポーネントの前につけると、このコンポーネントがStoreの中身の変更を監視してくれるようになる。
最終的なフォルダ構成は以下
├── pages
│ ├── about.tsx
│ ├── counter.tsx // 追加
│ ├── detail.tsx
│ ├── index.tsx
│ └── initial-props.tsx
├── store
│ ├── CounterStore.ts // 追加
│ └── store.ts // 追加
ここまでできたらnpm run dev
を実行し、以下のURLにアクセスして、今回作ったコンポーネントがうまく動くかテストする
+ボタンをポチポチしてカウンターが増えていればOK
以上。
余談
公式サイトにnextjs + mobxのサンプルも落ちており、そちらだと、_app.js
の中でstoreのインスタンスを持ち、@inject
を使って、propsにstoreインスタンスを差し込むような設計になっている。
propsが汚れそうなのがちょっと気持ち悪いけど、こっちのほうが良いのだろうか?
https://github.com/zeit/next.js/tree/canary/examples/with-mobx