そういえば redux-thunk
の connect
周りいつも適当に型つけてたので、実際ちゃんとやろうと思うとどうなるかなと思って書いてみた。案外ちゃんと書ける。いうほど型アサーションの重複もない。reducer 内の payload や、コンポーネント内に渡される thunk の関数のも補完や型チェック効くので安全度は高い。記述量は多い。
記述量は多いが、redux の管理する state とその更新パターンである reducer はクリーンに保つことができるし、細かいロジックは thunk に分離できるし、プレゼンテーションコンテナに提供されるインターフェイスも connect の定義も型チェックの保護を受けるので、受け入れられるような気がしてきた。
import React, { useCallback } from "react";
import { connect, Provider } from "react-redux";
import { createStore, applyMiddleware, combineReducers } from "redux";
import ReduxThunk, { ThunkAction } from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
// state
type CounterState = number;
type State = { counter: CounterState };
// action creator
const actionCreators = {
add: (num: number) => ({ type: "ADD" as const, payload: num })
};
type ActionTypes = ReturnType<typeof actions[keyof typeof actionCreators ]>;
// async action (thunk)
type ThunkPromiseAction<T> = ThunkAction<
Promise<T>,
State,
undefined,
ActionTypes
>;
const thunks = {
add: (num: number = 1): ThunkPromiseAction<void> => async dispatch => {
dispatch(actionCreators.add(num));
}
};
// reducer
const counterReducer = (
state: CounterState = 0,
action: ActionTypes
): CounterState => {
switch (action.type) {
case "ADD":
return state + action.payload;
}
return state;
};
const store = createStore(
combineReducers({ counter: counterReducer }),
composeWithDevTools(applyMiddleware(ReduxThunk))
);
// component
const Index: React.FC<{
counter: number;
add: (num?: number) => Promise<void>;
}> = props => {
const handleAdd = useCallback(() => props.add(), [props.add]);
return (
<div>
<p>counter {props.counter}</p>
<button onClick={handleAdd}>+</button>
</div>
);
};
// redux connect
const ConnectedIndex = connect(
({ counter }: State) => ({
counter
}),
{
add: thunks.add
}
)(Index);
// next.js page
export default () => (
<Provider store={store}>
<ConnectedIndex />
</Provider>
);
サンプルコードは next.js で書いているけど特に依存はしていない。書いてる最中何度も thunk
を thank
と書き損じた。
記述量の話
よし、じゃあ「減らす」アクションを追加しよう
- アクションクリエーター sub を追加
- thunk の sub を追加
- reducer に SUB のパターンを追加
- コンポーネントの props で sub を受け取るように修正
- コンポーネントに
+
ボタンの横 に-
を増やし、subを呼ぶようにする - connect で thunks.sub をマッピング
thunk が増えたところで大したことないな!