Redux Container を TypeScript で型チェックする
この間の記事で Redux + TypeScript を試したのだが Container における mapStateToProps
と mapDispatchToProps
でプロパティーの過不足や名前のタイポが検出されず、なんどか事故った。そのため今回はこれらの戻り値を型チェックする方法を検討してみる。
props の型を分割定義
これらはそれぞれ connect
対象コンポーネントの props におけるパラメータを返す。mapStateToProps
が状態を示す値で mapDispatchToProps
はコールバック関数を担当。なのでコンポーネントの props が以下のように定義されているなら
type Props = {
message: string
windowIds: number[]
sendMessage?: (targetWindowId: number, message: string) => void
createNewWindow?: () => void
}
const App: React.FC<Props> = (/* ...中略... */)
これらの内訳を分割して定義。命名は一考の余地あり。props の内訳で関係は密だから 〜ByProps
としたけれど Of
や On
、At
のほうが適切かもしれない。
export type StateByProps = {
message: string
windowIds: number[]
}
export type DispatchByProps = {
sendMessage?: (targetWindowId: number, message: string) => void
createNewWindow?: () => void
}
type Props = StateByProps & DispatchByProps
const App: React.FC<Props> = (/* ...中略... */)
コンポーネントはこれらを組み合わせた交差型を props として参照。Type alias ではなく interface
にしたければ交差型のかわりに継承を用いる。今回の例では型定義にどちらを選んでも使用感は変わらないから、お好みで。
型チェック
分割定義された型を Container 側の mapStateToProps
と mapDispatchToProps
の戻り値型として定義。
import { connect } from 'react-redux'
import { sendMessage, createNewWindow } from '../actions/'
import { Dispatch } from 'redux'
import { AppState } from '../Types'
import App, { StateByProps, DispatchByProps } from '../components/App'
const mapStateToProps = (state: AppState): StateByProps => ({
message: state.message,
windowIds: state.windowIds
})
const mapDispatchToProps = (dispatch: Dispatch<any>): DispatchByProps => ({
sendMessage: (targetWindowId: number, message: string) => {
dispatch(sendMessage(targetWindowId, message))
},
createNewWindow: () => {
dispatch(createNewWindow())
}
})
const Container = connect(
mapStateToProps,
mapDispatchToProps
)(App)
export default Container
わざと戻り値のプロパティーを過不足のある状態にしたり名前を間違えてみよう。すると TypeScript としてエラーになることを確認できるはず。エディターが vscode ならリアルタイムに警告してくれるうえ、メッセージも親切だ。
なお mapStateToProps
については戻り値のプロパティー定義と一致するものが引数の型に含まれているなら、異なる型でも互換性を持つためそのまま返せる。しかし記述上の型は別だから、この暗黙的な互換への依存はコード読者を混乱させるかもしれない。
また mapStateToProps
の引数は Store の state であるためコンポーネントに見せたくない値を含む可能性もある。それらを晒すのは設計的に好ましくなかろう。よって互換があっても明示的に戻り値の型にあわせて展開する方法を採用した。
Optional なプロパティーについて
Optional となっているプロパティーだと名前の間違いは検出できても定義漏れはエラーにならない。Optional なので省略可能だから当たり前の話ではあるけど注意が必要。私はこの間の記事に書いた「react-redux ルートの props 警告」を回避するためにコールバック関数 Optional にしているので、この問題に遭遇する可能性が高い。
今のところ確実な対策はなさそう。運用として mapStateToProps
と mapDispatchToProps
を実装する際は、戻り値の定義からプロパティーをすべてコピペして網羅されてる状態から始めるぐらいか。しかし新たに Optional なプロパティーを追加したときの漏れは防げない。