読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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

TypeScript 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