Fluxとは。
Reactで色々柔軟にできるけど、アプリ内のデータの流れを一方向に整理してみましたということみたいですね。Fluxはアーキテクチャであって、フレームワークやライブラリではないというけれど、一応公式で小さなライブラリがあります。
今回はこれを使ってみようと思います。
下準備
下準備としてライブラリと型定義を入れます。打ち込んだコマンドはこんな感じです。
npm install react react-dom flux --save
tsd install react react-dom flux
Dispatcherで投げられる型の定義
DispatcherでActionsからStoreへ投げる型を定義しておきます。とりあえずActionの識別子とデータが入る入れ物って感じのクラスにしました。
class Payload { constructor(public action: string, public data: any) { } }
アクションの定義
ここでWebAPIにアクセスしたりしてから、DispatcherでStoreにデータ投げるっぽいですが、今回はちょっとデータ加工してDispatcherに投げます。
class Actions { static greet(name: string) { AppDispatcher.dispatch(new Payload('greet', "Hello, " + name)); } }
AppDispatcherは後で定義するDispatcherの定数です。
ストアの作成
データの入れ物とビジネスロジックも書くらしいストアです。今回はデータの入れ物として。適当なクラスを用意しておいて、ReduceStoreっていうのを使うとお手軽っぽいです。getInitialStateで初期状態を返してreduceでPayloadを受け取って処理して結果を返すという感じです。
これだけで、変更通知もしてくれるというのでありがたいですね。
class State { constructor(public message: string) { } } class Store extends FluxUtils.ReduceStore<State> { constructor(d: Flux.Dispatcher<Payload>) { super(d); } getInitialState() { returnnew State(''); } reduce(state: State, action: Payload) { switch (action.action) { case'greet': returnnew State(action.data asstring); default: throw Error('invalid operation'); } } }
定数の定義
DispatcherとStoreはグローバルな感じで。割り切りなんですかね。
const AppDispatcher = new Flux.Dispatcher<Payload>(); const AppStore = new Store(AppDispatcher);
コンポーネントの作成
あとは、入力欄とボタンと出力を持った簡単なコンポーネントを作りました。
class App extends React.Component<{}, AppState> { private listenerSubscription: { remove: Function }; constructor(props: {}) { super(props); this.state = { name: '', message: '' } as AppState; } private getState(): AppState { return { name:this.state.name, message: AppStore.getState().message }; } private handleChange() { this.setState(this.getState()); } private handleClick() { Actions.greet(this.state.name); } private handleNameChange(e: React.SyntheticEvent) { var name = (e.target as HTMLInputElement).value; this.setState({ name: name } as AppState); } componentDidMount() { this.listenerSubscription = AppStore.addListener(this.handleChange.bind(this)); } componentWillUnmount() { this.listenerSubscription.remove(); } render() { return<div> <div>{this.state.message}</div> <input type='text' onChange={this.handleNameChange.bind(this)} /> <button onClick={this.handleClick.bind(this)}>Click</button> </div>; } }
動作確認
これを動かすと、こんな感じで名前入れてボタンを押したらメッセージ出ます。
コード全体
app.tsx全体を置いておきます。
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import * as Flux from 'flux'; import * as FluxUtils from 'flux/utils'; class Payload { constructor(public action: string, public data: any) { } } class Actions { static greet(name: string) { AppDispatcher.dispatch(new Payload('greet', "Hello, " + name)); } } class State { constructor(public message: string) { } } class Store extends FluxUtils.ReduceStore<State> { constructor(d: Flux.Dispatcher<Payload>) { super(d); } getInitialState() { returnnew State(''); } reduce(state: State, action: Payload) { switch (action.action) { case'greet': returnnew State(action.data asstring); default: throw Error('invalid operation'); } } } const AppDispatcher = new Flux.Dispatcher<Payload>(); const AppStore = new Store(AppDispatcher); interface AppState { message:string; name:string; } class App extends React.Component<{}, AppState> { private listenerSubscription: { remove: Function }; constructor(props: {}) { super(props); this.state = { name: '', message: '' } as AppState; } private getState(): AppState { return { name:this.state.name, message: AppStore.getState().message }; } private handleChange() { this.setState(this.getState()); } private handleClick() { Actions.greet(this.state.name); } private handleNameChange(e: React.SyntheticEvent) { var name = (e.target as HTMLInputElement).value; this.setState({ name: name } as AppState); } componentDidMount() { this.listenerSubscription = AppStore.addListener(this.handleChange.bind(this)); } componentWillUnmount() { this.listenerSubscription.remove(); } render() { return<div> <div>{this.state.message}</div> <input type='text' onChange={this.handleNameChange.bind(this)} /> <button onClick={this.handleClick.bind(this)}>Click</button> </div>; } } ReactDOM.render( <App />, document.getElementById('content'));