かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

TypeScript JSX + ReactでReduxなカウンターアプリ

Reduxなカウンターアプリを作ってみました。 TypeScript JSXで。

感想としては、この規模だとFluxより楽な気がします。規模が大きくなるとどうなるのか未知数。FluxのChatサンプルでも書き直してみたらわかるかなぁ。

Counterの作成

まずはクラスがないと始まりません。データの入れ物のCounterクラスです。

export class Counter {
    constructor(public count: number) {
    }
}

Actionの作成

続けてインクリメントとデクリメントのActionを作っていきましょう。 これはenum持っただけの簡単なものにしました。

export enum ActionTypes {
    Increment,
    Decrement
}

export interface Action {
    type: ActionTypes;
}

export function increment() {
    return {
        type: ActionTypes.Increment
    } as Action;
}

export function decrement() {
    return {
        type: ActionTypes.Decrement
    } as Action;
}

Reducerの作成

続けてReducerです。Actionを受け取ってCounterを変更して返します。

import * as Redux from 'redux';
import * as Models from './models';
import * as Actions from './actions';
import objectAssign = require('object-assign');

function counter(state = new Models.Counter(0), action: Actions.Action) {
    switch (action.type) {
        case Actions.ActionTypes.Increment:
            return objectAssign({}, state, { count: state.count + 1 } as Models.Counter);
        case Actions.ActionTypes.Decrement:
            return objectAssign({}, state, { count: state.count - 1 } as Models.Counter);
        default:
            return state;
    }
}

const counterApp = Redux.combineReducers({
    counter
});

export default counterApp;

コンポーネントの作成

ここら辺からreact-reduxの範疇になってきます。reduxに設定するアプリのプロパティは、dispatchというプロパティと、各種ステートを持ちます。今回のカウンターアプリの場合はCounterクラスを持たせるといいでしょう。

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as Redux from 'redux';
import {Provider, connect} from 'react-redux';
import todoApp from './reducers';
import * as Actions from './actions';
import * as Models from './models';


interface AppProps extends React.Props<{}> {
    counter?: Models.Counter;
    dispatch?: Redux.Dispatch;
}

class App extends React.Component<AppProps, {}> {
    render() {
        const { dispatch, counter } = this.props;
        return (
            <div>
                <p>{counter.count}</p>
                <button onClick={() => dispatch(Actions.increment())}>Inc</button>
                <button onClick={() => dispatch(Actions.decrement())}>Dec</button>
            </div>
        );
    }
}

アプリのステートをインターフェースとして定義しておきます。今回はcombineReducersでCounterを返すcounter関数を渡してるので、以下のような感じになります。 ここらへん、combineReducersとの同期を手動でとらないといけないのでいまいち感。

interface AppState {
    counter: Models.Counter;
}

そして、AppStateからAppPropsへ変換するselect関数を準備します。

function select(state: AppState): AppProps {
    return {
        counter: state.counter
    };
}

次に、このselect関数を使ってAppクラスをreact-reduxと繋ぎます。

const ReduxCounterApp = connect(select)(App);

そして、アプリのstateを作ります。

let store = Redux.createStore(todoApp);

最後に、ProviderというタグでAppをくるんだものをReactDOM.renderに渡します。(お約束)

ReactDOM.render(
    <Provider store={store}>
        <ReduxCounterApp />
    </Provider>,
    document.getElementById('content'));

実行して動作確認

ボタンを押してインクリメントデクリメントできるアプリが出来上がりました。

f:id:okazuki:20160109170042p:plain