アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

Redux Container を TypeScript で型チェックする

March 04, 2019開発TypeScript, Redux

この間の記事で Redux + TypeScript を試したのだが Container における mapStateToPropsmapDispatchToProps でプロパティーの過不足や名前のタイポが検出されず、なんどか事故った。そのため今回はこれらの戻り値を型チェックする方法を検討してみる。

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 としたけれど OfOnAt のほうが適切かもしれない。

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 側の mapStateToPropsmapDispatchToProps の戻り値型として定義。

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 ならリアルタイムに警告してくれるうえ、メッセージも親切だ。

vscode の警告例

なお mapStateToProps については戻り値のプロパティー定義と一致するものが引数の型に含まれているなら、異なる型でも互換性を持つためそのまま返せる。しかし記述上の型は別だから、この暗黙的な互換への依存はコード読者を混乱させるかもしれない。

また mapStateToProps の引数は Store の state であるためコンポーネントに見せたくない値を含む可能性もある。それらを晒すのは設計的に好ましくなかろう。よって互換があっても明示的に戻り値の型にあわせて展開する方法を採用した。

Optional なプロパティーについて

Optional となっているプロパティーだと名前の間違いは検出できても定義漏れはエラーにならない。Optional なので省略可能だから当たり前の話ではあるけど注意が必要。私はこの間の記事に書いた「react-redux ルートの props 警告」を回避するためにコールバック関数 Optional にしているので、この問題に遭遇する可能性が高い。

今のところ確実な対策はなさそう。運用として mapStateToPropsmapDispatchToProps を実装する際は、戻り値の定義からプロパティーをすべてコピペして網羅されてる状態から始めるぐらいか。しかし新たに Optional なプロパティーを追加したときの漏れは防げない。