第3回
useState・useRef・useReducerの型定義:TypeScriptで正しく型をつける
TypeScriptでReact HooksのuseState・useRef・useReducerに型をつける方法を解説。型推論が効くケース・明示が必要なケース・useRefのnull初期値問題・useReducerのaction型定義まで実例付きで説明します。
·10分で読める
useState の型定義
型推論が効くケース(型を書かなくていい)
初期値から型を推論できる場合は型引数を省略できます。
const [count, setCount] = useState(0) // number と推論される
const [name, setName] = useState('') // string と推論される
const [flag, setFlag] = useState(false) // boolean と推論される
const [items, setItems] = useState<string[]>([]) // 空配列は明示が必要型引数を明示すべきケース
// null や undefined になりうる場合
const [user, setUser] = useState<User | null>(null)
// 初期値が空配列・空オブジェクトの場合
const [tags, setTags] = useState<string[]>([])
const [errors, setErrors] = useState<Record<string, string>>({})
// ユニオン型
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle')オブジェクトのstate
type FormState = {
name: string
email: string
age: number
}
const [form, setForm] = useState<FormState>({
name: '',
email: '',
age: 0,
})
// 部分的に更新するときはスプレッド構文
const handleNameChange = (name: string) => {
setForm(prev => ({ ...prev, name }))
}useRef の型定義
useRef は使い方によって型が変わるため注意が必要です。
DOM要素を参照する場合:初期値は null
import { useRef } from 'react'
const inputRef = useRef<HTMLInputElement>(null)
// アクセスするときはnullチェックが必要
const handleFocus = () => {
inputRef.current?.focus()
}
<input ref={inputRef} />useRef<HTMLInputElement>(null) と書くと RefObject<HTMLInputElement> 型になり、current は読み取り専用になります。これがDOM参照の正しい形です。
値を保持する場合:初期値はnull以外
// タイマーIDを保持する例
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const startTimer = () => {
timerRef.current = setTimeout(() => {
console.log('完了')
}, 1000)
}
const stopTimer = () => {
if (timerRef.current !== null) {
clearTimeout(timerRef.current)
}
}// 前回の値を保持する例
const prevValueRef = useRef<string>('') // null以外の初期値
useEffect(() => {
prevValueRef.current = value // 書き込み可能(MutableRefObject型)
}, [value])初期値 null と null以外の違い
// null を渡す → RefObject(currentが読み取り専用)
const ref1 = useRef<HTMLInputElement>(null)
// ref1.current = something // ❌ 型エラー
// null以外を渡す → MutableRefObject(currentが書き込み可能)
const ref2 = useRef<number>(0)
ref2.current = 42 // ✅ OKDOM要素の参照には null を渡す、値の保持には初期値を渡すと覚えておけばOKです。
useReducer の型定義
状態管理が複雑になったときに useReducer を使います。
// Stateの型
type State = {
count: number
status: 'idle' | 'loading' | 'error'
error: string | null
}
// Actionの型:Discriminated Union で定義する
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'setError'; payload: string }
// Reducer関数
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 }
case 'decrement':
return { ...state, count: state.count - 1 }
case 'reset':
return { count: 0, status: 'idle', error: null }
case 'setError':
return { ...state, status: 'error', error: action.payload }
default:
return state
}
}
const initialState: State = { count: 0, status: 'idle', error: null }
// コンポーネントで使う
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>リセット</button>
{state.error && <p className="error">{state.error}</p>}
</div>
)
}dispatch に渡す action の type が間違っていると型エラーになるため、バグを事前に防げます。
useCallback・useMemo の型
型推論が効くので、基本的に型引数を書く必要はありません。
// 戻り値の型は推論される
const handleClick = useCallback(() => {
setCount(c => c + 1)
}, []) // () => void と推論
const doubled = useMemo(() => count * 2, [count]) // number と推論
// 明示したい場合
const getValue = useCallback<() => number>(() => count, [count])useContext の型
type Theme = 'light' | 'dark'
type ThemeContextType = {
theme: Theme
setTheme: (theme: Theme) => void
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
// カスタムフックで安全にアクセス
const useTheme = (): ThemeContextType => {
const ctx = useContext(ThemeContext)
if (ctx === undefined) {
throw new Error('useTheme は ThemeProvider 内で使ってください')
}
return ctx
}まとめ
| Hook | 型定義のポイント |
|---|---|
useState |
推論が効かない場合のみ <型> を明示。null になりうる場合は <T | null> |
useRef(DOM) |
useRef<HTMLInputElement>(null) — 初期値は必ず null |
useRef(値) |
useRef<number>(0) — null以外の初期値で書き込み可能に |
useReducer |
ActionをDiscriminated Unionで定義すると型安全になる |
useCallback・useMemo |
基本は型推論に任せる |
useContext |
undefined を含むユニオン型+カスタムフックで安全に使う |