Stateとreducer、Contextの型付け
useStateとuseReducerでコンポーネントのstateを管理し、安全なカスタムフックを備えた型付きContextへstateを持ち上げます
「Stateとreducer、Contextの型付け」はCoddyKit上の無料TypeScript Academyレッスンです。 これはレッスン2/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはTypeScript Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 TypeScript Academyコースには全3レッスンが含まれています。
導入
目標: ローカルな状態(useState)から始め、型付きの reducer(useReducer)へ進み、安全なカスタムフックを備えた型付き Contextへ状態を引き上げます。
- 状態とアクションを明示的にモデル化する
- 判別可能な union を優先する
- Context へのアクセスを保護する
useState の型付け
状態に具体的な型(例: number)を付けます。更新が短時間に連続する場合に古いクロージャーを避けるため、アップデーター形式を使ってください。
import React, { useState } from "react"
export default function Counter() {
const [count, setCount] = useState<number>(0)
const inc = () => setCount(c => c + 1)
const dec = () => setCount(c => c - 1)
return (
<div style={{ display: "grid", gap: 8 }}>
<div>Count: {count}</div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={inc}>+1</button>
<button onClick={dec}>-1</button>
</div>
</div>
)
}useReducer の型付け
Action を判別可能な union としてモデル化します。switch は網羅的なままになり、payload は分岐ごとに型付けされます。
import React, { useReducer } from "react"
type State = { count: number }
type Action =
| { type: "inc" }
| { type: "add"; by: number }
| { type: "reset" }
function reducer(s: State, a: Action): State {
switch (a.type) {
case "inc": return { count: s.count + 1 }
case "add": return { count: s.count + a.by }
case "reset": return { count: 0 }
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div style={{ display: "grid", gap: 8 }}>
<div>Count: {state.count}</div>
<button onClick={() => dispatch({ type: "inc" })}>+1</button>
<button onClick={() => dispatch({ type: "add", by: 5 })}>+5</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
)
}Context とフック
Context<T | null> を作成し、null の場合に例外を投げる useX を公開します。利用側はシンプルで、適切に型付けされたままになります。
import React, { createContext, useContext, useReducer } from "react"
type State = { count: number }
type Action = { type: "inc" } | { type: "add"; by: number } | { type: "reset" }
function reducer(s: State, a: Action): State {
switch (a.type) {
case "inc": return { count: s.count + 1 }
case "add": return { count: s.count + a.by }
case "reset": return { count: 0 }
}
}
type Ctx = { state: State; dispatch: React.Dispatch<Action> }
const CounterCtx = createContext<Ctx | null>(null)
export function useCounter() {
const ctx = useContext(CounterCtx)
if (!ctx) throw new Error("useCounter must be used within CounterProvider")
return ctx
}
export function CounterProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return <CounterCtx.Provider value={{ state, dispatch }}>{children}</CounterCtx.Provider>
}
export default function App() {
return (
<CounterProvider>
<Inner />
</CounterProvider>
)
}
function Inner() {
const { state, dispatch } = useCounter()
return (
<div style={{ display: "grid", gap: 8 }}>
<div>Count: {state.count}</div>
<button onClick={() => dispatch({ type: "inc" })}>+1</button>
</div>
)
}Context と reducer のパターン
コンポーネント間で読み取りと dispatch を分けます。Provider が children をラップし、型付きの state と dispatch を提供します。
import React from "react"
import { CounterProvider, useCounter } from "./ctx"
function Controls() {
const { dispatch } = useCounter()
return (
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => dispatch({ type: "inc" })}>+1</button>
<button onClick={() => dispatch({ type: "add", by: 10 })}>+10</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
)
}
function View() {
const { state } = useCounter()
return <div>Count: {state.count}</div>
}
export default function App() {
return (
<CounterProvider>
<div style={{ display: "grid", gap: 8 }}>
<View />
<Controls />
</div>
</CounterProvider>
)
}ベストプラクティス
ベストプラクティス:
- 状態は最小限に保ち、残りはそこから導出してください。
- アクションには 判別可能な union を優先してください。
- カスタムフックで Context へのアクセスを保護してください。
Context の型付けの確認
クイックチェック: React Context を安全に型付けする方法はどれですか。
まとめ
まとめ: useState から始め、useReducer で状態の遷移をモデル化し、カスタムフックで保護した型付き Context に共有状態を引き上げます。
よくある質問
「Stateとreducer、Contextの型付け」レッスンは無料ですか?
はい。「Stateとreducer、Contextの型付け」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、TypeScript Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 TypeScript Academyコースには全3レッスンが含まれています。
「Stateとreducer、Contextの型付け」で何を学びますか?
useStateとuseReducerでコンポーネントのstateを管理し、安全なカスタムフックを備えた型付きContextへstateを持ち上げます ブラウザで直接実行するハンズオンコードでTypeScript Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
TypeScript Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのTypeScript Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/3です。
「Stateとreducer、Contextの型付け」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このTypeScript Academyレッスンでコードを書いて実行できますか?
はい。すべてのTypeScript Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Propsとchildren、イベントハンドラー、refs
- Stateとreducer、Contextの型付け
- コンポーネントのジェネリクス — 入門