react-redux
でも、Hooks を利用した API が普及しましたね。この API を利用するうえで、型定義注入方法にコツがありますので共有します。1つのプロジェクトにつき、1つの Store のはずなので、その前提で話を進めます。
export type StoreState = {
hoge: { hoge: 'hoge' }
fuga: { fuga: 'fuga' }
}
StoreState の型定義方法は数通りありますが、この様な型定義があることが前提です。
普通に書くとこうなる
この条件でuseSelector
を利用してみます。useSelector
の Generics に従い、都度StoreState
を import し、それを注入しています。なんだかあまりイケてません。
import { useSelector } from 'react-redux'
import { StoreState } from '../store'
const Container: React.FC = () => {
// const _hoge: "hoge"
const _hoge = useSelector<
StoreState,
StoreState['hoge']['hoge']
>(state => state.hoge.hoge)
return <Component _hoge={_hoge}>
}
求めている・実現できるもの
次の様にuseSelector
の Generics は指定しません。そして、StoreState
の import も不要です。それでいて、型推論がきちんと導かれている状態です。
import { useSelector } from 'react-redux'
const Container: React.FC = () => {
// const _hoge: "hoge"
const _hoge = useSelector(state => state.hoge.hoge)
return <Component _hoge={_hoge}>
}
これは、実現することができます。
Ambient Module 宣言で overload する
StoreState
はライブラリが知ることのできない、プロジェクト固有の定義ですね。DefaultRootState
という型定義が@types/react-redux
内に用意されていますので確認してください(v7.1.7)。これを次の様に interface overload すれば、DefaultRootState を参照している内部の API 型定義に StoreState が行き渡る様になります。
import 'react-redux'
import { StoreState } from '../store'
// ______________________________________________________
//
declare module 'react-redux' {
interface DefaultRootState extends StoreState {}
}
DefaultRootState の元定義は空っぽですが、この様な注入手法に向けてあらかじめ宣言されています。interface 宣言結合を利用したテクニックであり、様々なライブラリ型定義でも採用されています。この DefaultRootState も styled-components の DefaultTheme をインスパイアしたという旨のPRから生まれています。https://github.com/DefinitelyTyped/DefinitelyTyped/pull/41031
他の API にもプロジェクト固有の型定義を注入する
useDispatch
や useStore
を利用するたび、プロジェクト固有の型定義を import し Generics 注入するのはスマートではないので、こちらも対応します。
先のコードから新たにプロジェクト固有の型定義として追加しているのはActions
です。String Literal Type である type
プロパティで厳格に識別できるこの型は UnionTypes で表現されます。
export type Actions = { type: "INCREMENT" } | { type: "DECREMENT" }
これらも次の様に Ambient Module 宣言していれば、普段型定義を意識しなくとも、useDispatch
や useStore
に型推論が適用されます(例えば、プロジェクトに存在しない Action を dispatch することを防ぐなど)。以下は"@types/react-redux": "7.1.7"
時点で最適と思われる Ambient Module 宣言です。
import 'react-redux'
import { Store, Dispatch } from 'redux'
import { StoreState, Actions } from '../store'
// ______________________________________________________
//
declare module 'react-redux' {
interface DefaultRootState extends StoreState {}
export function useDispatch<TDispatch = Dispatch<Actions>>(): TDispatch
export function useStore<S = DefaultRootState>(): Store<S, Actions>
}
プロジェクトにおいて1つしか存在しえないインスタンスは、この様に Ambient Module 宣言を積極的に活用しましょう。