React + Redux + TypeScriptのシンプルなサンプル

React+Redux+TypeScriptを始めるにあたってなかなか簡単なサンプルが見つからなかったので、簡単にまとめようと思います。
とりあえず動かすことが目的なので、React+Reduxの詳細については他をあたったほうが良いと思います。

## 前準備

まずはReactのプロジェクトの骨組み生成のためにcreate-react-appをインストールします。

```sh
npm install -g create-react-app
```

そしたら適当なディレクトリ内で`create-react-app`を使って`redux-sample`という名前のReact+TypeScriptのプロジェクトの骨組みを生成

```sh
create-react-app redux-sample --scripts-version=react-scripts-ts
```

必要な依存関係を追加します。

```sh
cd redux-sample
yarn add redux react-redux @types/react-redux
yarn add typescript-fsa typescript-fsa-reducers
```

## コーディング

まずActionを定義します

```src/actions/sampleAction.tsx
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory();

export const actions = {
    updateValue: actionCreator<string>('ACTIONS_UPDATE_VALUE')
};
```
次にStateと現在のStateから新しいStateを生成するためのReducerを定義します。

```src/states/sampleState.tsx
import {reducerWithInitialState} from 'typescript-fsa-reducers';
import {actions} from '../actions/sampleAction';

export interface State {
    value: string;
}

const initialState: State = {
    value: '',
};

export const reducer = reducerWithInitialState(initialState)
    .case(actions.updateValue, (state, value) => {
        return Object.assign({}, state, {value});
    });

```

次にすべてのStateを管理するstoreを定義します。

```src/store.tsx
import {combineReducers, createStore} from 'redux';
import {reducer, State} from './states/sampleState';

export type AppState = {
    state: State
};

const store = createStore(
    combineReducers<AppState>({
        state: reducer
    })
);

export default store;
```

次にデータを表示したりするためのコンポーネントと、それに具体的なコールバック関数などを注入するためのコンテナを定義します。
ここではvalueを入力したらそれを出力するだけのを作ります。

```src/components/sampleComponent.tsx
import * as React from 'react';
import {Actions} from '../containers/sampleContainer';
import {State} from '../states/sampleState';


type Props = State & Actions;

export const Component: React.SFC<Props> = (props: Props) => {
    return (
        <div>
            <input
                type="text"
                placeholder="value"
                value={props.value}
                onChange={(e) => props.updateValue(e.target.value)}/><br/>
            {props.value}<br/>
        </div>
    );
};
```

```src/containers/sampleContainer.tsx
import {connect} from 'react-redux';
import {Dispatch} from 'redux';
import {Action} from 'typescript-fsa';
import {actions} from '../actions/sampleAction';
import {Component} from '../components/sampleComponent';
import {AppState} from '../store';

export interface Actions {
    updateValue: (v: string) => Action<string>;
}

function mapStateToProps(appState: AppState) {
    return Object.assign({}, appState.state);
}

function mapDispatchToProps(dispatch: Dispatch<Action<any>>) {
    return {
        updateValue: (v: string) => dispatch(actions.updateValue(v)),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(Component);
```

そしたら最後にindex.tsxとApp.tsxを書き換えて終わり

```src/app.tsx
import * as React from 'react';
import Container from './containers/sampleContainer';
import './App.css';


class App extends React.Component {
    public render() {
        return (
            <div className="App">
                <Container/>
            </div>
        );
    }
}

export default App;
```

```src/index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import App from './App';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
import store from './store';

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root') as HTMLElement
);
registerServiceWorker();
```