react-hooksでreduxを真剣にいじる機会に恵まれないので、練習がてらボタンをおしたら数字が増えるカウンターを作ってみました
。
環境構築
まずは環境構築。絶対に必要なredux関連と型情報をワンセットでインストールしましょう。
npx create-react-app hooks --template typescript
npm install react-redux
npm install @types/react-redux
npm install redux
npm install @types/redux
生成されたテンプレートのプロジェクトをいじっていきます。
action
まずはactionから定義をします。
今回実装するアクションは以下の通りです。
- カウントが増える
INCREMENT
- カウントが減る
DECREMENT
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
export function increment() {
return {
type: INCREMENT
}
}
export function decrement() {
return {
type: DECREMENT
}
}
reducer
次に実際に値を更新したり、編集したりするreducerを実装します。
import {INCREMENT, DECREMENT} from "./action";
const initialState = {
count: 0
}
function reducer(state = initialState, action: {type: string}) {
switch(action.type) {
case INCREMENT:
return {
count: state.count + 1
};
case DECREMENT:
return {
count: state.count - 1
};
default: {
return {
count: 0
}
}
}
}
export default reducer;
普通であれば、 combinereducer
で結合しますが、今回はreducerを一つしか作らないので、生のreducerをそのままexportしています。
store
値を格納するstoreです。
reducerを受け取ってstoreを生成していますが、このままだとstateの型が不定で後々のつなぎこみで不都合が生じるので、RootState
を定義しています。
import {createStore} from "redux";
import reducer from "./reducer";
export type RootState = ReturnType<typeof reducer>
export const store = createStore(reducer);
ReturnType
は関数の戻り値の型として生成して返します。今回であれば、reducerの戻り値(stateのproperty構造)を型として返してくれます。
つまり、今回のRootStateの中身はこのような感じ
type RootState = {
count:number
}
connect(component)
hooks を使ったものはこの部分が大きく違っています。いままでは、
mpaStateToProps
mapDispatchToProps
connect
を使ってましたが、
useSelector
: storeからstateを取得
useDispatch
: storeへ変更を伝えるためのdispatch関数を取得
↑の二つを使うだけでよくなります。
先程、作った RootState
は useSelector
に使います。 stateから情報を受け取るのですが、この時stateの情報がないと、型が解決できなくなるので、 RootState
を作成して型を確定させるのです。
import React from "react";
import { useSelector, useDispatch} from "react-redux";
import {RootState} from "./store";
import {increment, decrement} from "./action";
export const Counter: React.FC = () => {
const dispatcher = useDispatch();
const state = useSelector((state:RootState) => state);
return (
<div className="counter">
<h1> counter </h1>
<p>{state.count}</p>
<button onClick={() => dispatcher(increment())} >count up</button>
<button onClick={() => dispatcher(decrement())} >count down</button>
</div>
)
}
つなぎ込み
あとは、 Providerにstoreを渡して終了です。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {store} from "./store";
import {Provider} from "react-redux"
ReactDOM.render(<Provider store={store}> <App /> </Provider>, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
最後に
mpaStateToProps
mapDispatchToProps
connect
という黒魔術を使わなくてよくなり、且つhooksの文法をしっていれば処理の流れを追いやすくなったと思います。
というわけで、時間があれば昔のreactコードのリファクタをしたいと思います。
それでは、よいreactライフを