React+Redux+TypeScriptを始めるにあたってなかなか簡単なサンプルが見つからなかったので、簡単にまとめようと思います。
とりあえず動かすことが目的なので、React+Reduxの詳細については他をあたったほうが良いと思います。
前準備
まずはReactのプロジェクトの骨組み生成のためにcreate-react-appをインストールします。
npm install -g create-react-app
そしたら適当なディレクトリ内でcreate-react-app
を使ってredux-sample
という名前のReact+TypeScriptのプロジェクトの骨組みを生成
create-react-app redux-sample --scripts-version=react-scripts-ts
必要な依存関係を追加します。
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();