第3回
useState・useRef・useReducerの型定義:TypeScriptで正しく型をつける
TypeScriptでuseState・useRef・useReducerに型をつける方法を解説。型推論が効くケースと明示が必要なケース、useRefのnull初期値問題まで実例付きで説明します。
制作・運営:React Guide 運営者
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} />React 19の型定義では useRef<HTMLInputElement>(null) の戻り値は RefObject<HTMLInputElement | null> で、current は書き込み可能です。以前の「null初期値なら読み取り専用」という説明はReact 18時代の型定義と混同していました。
DOMのrefはReactが要素の取り付け・取り外しに応じて更新します。型として代入できても、自分で別のDOMを代入して管理を混ぜる必要はありません。
値を保持する場合もnullを使える
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const stopTimer = () => {
if (timerRef.current !== null) {
clearTimeout(timerRef.current)
timerRef.current = null
}
}
const startTimer = () => {
stopTimer() // 連打しても古いタイマーを残さない
timerRef.current = setTimeout(() => {
timerRef.current = null
console.log('完了')
}, 1000)
}
useEffect(() => () => {
if (timerRef.current !== null) clearTimeout(timerRef.current)
}, [])コンポーネント内の抜粋です。useRef と useEffect のimportが必要です。画面表示を変えたい値にはstate、表示と無関係なタイマーIDにはrefを使います。React 19では useRef() のような引数省略もせず、初期値を渡します。
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 |
useReducer |
ActionをDiscriminated Unionで定義すると型安全になる |
useCallback・useMemo |
基本は型推論に任せる |
useContext |
undefined を含むユニオン型+カスタムフックで安全に使う |
確認してみる
タイマーを2回開始して1つだけ残るか確認する。DOM refのnullチェックを外したときの型エラーを読み、非null断言だけで隠さない。
同じテーマの教材
useRefの使い方:DOM参照・前回値の保持・レンダリングを起こさない値
ReactのuseRefを解説します。DOM要素への直接参照、前回値の保持、レンダリングをトリガーしない値の管理など、useRefの3つの主要ユースケースを実例付きで学べます。
useReducerでの状態管理:複雑なstateをスッキリ整理する
ReactのuseReducerを解説します。action・reducer・dispatchの概念、TypeScriptでの型安全な実装、useStateとの使い分け基準を、ToDoアプリを例に実践的に学べます。
useStateの完全ガイド:関数型更新・配列・オブジェクトの正しい扱い方
ReactのuseStateを完全解説します。型推論・関数型更新・オブジェクト・配列のイミュータブルな更新方法・複数stateの使い分けまで、実務で使えるパターンを実例付きで学べます。