React(TypeScript)で学ぶStateの型推論と型指定の違い!型安全なState管理を初心者向けにやさしく解説
生徒
「Reactでボタンを押した回数みたいな“状態”を持てるって聞きました。TypeScriptだと型ってどうなるんですか?」
先生
「ReactのStateは、TypeScriptだと“型推論”で自動的に型が決まることが多いです。ただし、場面によっては自分で“型指定”した方が安全になります。」
生徒
「型推論と型指定って、何が違うんですか?どっちを使えばいいのか迷います…」
先生
「違いは“最初の値だけで決めて良いか”と“これから入る値の種類を先に決めたいか”です。たとえば、最初は空っぽだけど後でデータが入るStateは、型指定がとても役に立ちます。」
生徒
「なるほど…!まずは、基本から順番に教えてください!」
1. ReactのStateとは?TypeScriptと一緒に考える
ReactのState(ステート)は、画面の中で変化する情報を入れておく箱です。たとえば「入力欄の文字」「チェックのオンオフ」「読み込み中かどうか」「ボタンを押した回数」など、ユーザーの操作や通信結果で変わるものをStateで管理します。
TypeScriptは型(かた)を使って「この箱には数字だけ」「この箱には文字だけ」のようにルールを決められます。型があると、間違った値を入れようとした瞬間に気づけるので、バグ(不具合)を減らせます。ReactとTypeScriptを組み合わせると、型安全(かたあんぜん)なState管理がしやすくなります。
2. Stateの型推論とは?useStateが“最初の値”から型を決める
ReactではStateを作るときにuseStateをよく使います。TypeScriptでは、useStateに入れる最初の値を見て、Stateの型を自動で決めることが多いです。これがStateの型推論です。
import React, { useState } from "react";
export default function App() {
// 最初の値が 0 なので、count は number(数字)だと推論される
const [count, setCount] = useState(0);
return (
回数:{count}
);
}
この例では、最初の値が数字の0なので、TypeScriptは「countは数字だ」と判断します。その結果、setCount("こんにちは")のように文字を入れようとすると、エラーとして教えてくれます。初心者にとっては、間違いが早めに見つかるのが大きな安心ポイントです。
3. Stateの型指定とは?“あとで入る値”まで見越して先に決める
一方で、最初の値だけでは型がうまく決まらないことがあります。たとえば「最初は何もない(null)けど、あとでユーザー情報が入る」Stateです。最初がnullだと、TypeScriptは「nullしか入らない箱」と勘違いしやすくなります。
null や空配列 [] を入れたせいで、あとから正しいデータを入れようとしても型エラーになることがあります。
import React, { useState } from "react";
type User = {
id: number;
name: string;
};
export default function App() {
// User か null が入る、と先に型指定する(ジェネリクス)
const [user, setUser] = useState<User | null>(null);
return (
{user ? こんにちは、{user.name}さん
: ユーザー未設定
}
);
}
ここで出てきたジェネリクスは、「型の入れ物に、あとから型を差し込む書き方」です。useState<User | null>のように書くと、「userにはUserかnullが入る」と宣言できます。こうしておくと、最初がnullでも、あとでUserを入れられます。
また、union型(ユニオンがた)は「AかBのどちらか」という意味です。ここでは「Userかnull」のどちらか、というルールになります。現実の例えだと、「席には人が座っているか、空席(null)か」のようなイメージです。
4. 型推論と型指定の“違い”を一言でいうと?
- 型推論:最初の値を見て、TypeScriptが「このStateはたぶんこれ!」と自動で決める
- 型指定:最初の値だけに頼らず、「このStateには将来これが入る」と先にルールを決める
型推論は書く量が少なくて楽です。ですが、アプリが少し大きくなると「最初は空」「あとで入る」「複数の形がある」といったStateが増えます。そんなとき、型指定を使うと未来の自分やチームを助ける説明書になります。
5. 初心者が迷いやすいパターン:空配列・空オブジェクトのState
特に初心者が混乱しやすいのが、配列(リスト)やオブジェクト(まとまり)のStateです。たとえば、最初はデータがないので空配列で始めたいことがあります。ここで型推論だけに頼ると、意図しない型になってしまうことがあります。
import React, { useState } from "react";
type Todo = {
id: number;
title: string;
done: boolean;
};
export default function App() {
// 配列に Todo が入る、と型指定しておくと安全
const [todos, setTodos] = useState<Todo[]>([]);
return (
件数:{todos.length}
);
}
このようにuseState<Todo[]>([])と書くと、「この配列にはTodoだけが入る」と決められます。ReactのState管理で配列を扱うときは、あとから追加・削除・更新がよく起きるので、型指定があると安心です。
6. “nullチェック”と型安全:安全に読み出す考え方
型指定でUser | nullのようにすると、読み出すときに「nullかもしれない」ことを忘れにくくなります。これがTypeScriptの大事なところです。もしnullのままuser.nameに触ろうとすると、画面が止まる原因になることがあります。
そこでReactでは、条件分岐(条件によって表示を変える)と組み合わせて安全に表示します。上の例のuser ? ... : ...は「userがあるならA、ないならB」という意味です。TypeScriptの型指定とReactの条件分岐は相性が良く、初心者でも“事故”を減らせます。
7. どんなときに型推論で十分?どんなときに型指定が必要?
目安として、次のように考えると選びやすいです。ReactのuseStateはどちらでも使えますが、アプリの意図をはっきりさせたいときは型指定が強い味方です。
- 最初から最後まで同じ種類の値が入る(数字、文字、真偽値など)
- 初期値がはっきりしていて、あとで形が変わらない
- 学習用の小さなコンポーネントで、まず動きを理解したい
- 最初は
nullや空で、あとでデータが入る(API通信、ログイン情報など) - 配列やオブジェクトで、中身の形を守りたい
- 「AかBか」のように複数パターンがあり、union型で表したい
特に「PropsとStateを型安全にしたい」「TypeScriptでReactのバグを減らしたい」と考えるなら、Stateの型指定は避けて通れません。小さなうちは型推論で書きやすく、少し複雑になったら型指定で守りを固める、という順番が自然です。
8. State型の設計で意識したいこと:入力フォームを例にする
入力フォームのStateは、初心者がReactのState管理を学ぶときに定番です。たとえば「名前」と「年齢」の入力をまとめて持つ場合、オブジェクトで管理します。ここでも型指定をしておくと、フィールド名の打ち間違いを防げます。
import React, { useState } from "react";
type FormState = {
name: string;
age: number;
};
export default function App() {
const [form, setForm] = useState<FormState>({ name: "", age: 0 });
return (
名前:{form.name}
年齢:{form.age}
);
}
このように型指定しておくと、たとえばageに文字を入れたり、存在しないプロパティ名を書いたりしたときに、TypeScriptが「それは違う」と止めてくれます。ReactのState管理は自由度が高い分、型安全で守ると安心して作業できます。
9. “型推論が強すぎる”ときの対処:リテラル型と広げたい型
TypeScriptはとても賢いので、時々「推論が細かすぎる」ことがあります。たとえば、Stateに入れる文字が「loading」「success」「error」のように決まっている場合は、むしろ細かい方が便利です。でも「あとでいろいろな文字が入る」なら、広い型にしたいこともあります。
こうした状態は、ステータス(状態の種類)としてよく出てきます。ここではunion型で安全にしつつ、意図をはっきりさせます。
import React, { useState } from "react";
type Status = "idle" | "loading" | "success" | "error";
export default function App() {
const [status, setStatus] = useState<Status>("idle");
return (
状態:{status}
);
}
このように型指定すると、間違って"loadding"のように打ち間違えてもエラーで気づけます。ReactのStateは画面の見た目や動きに直結するので、ステータス管理は型安全にしておくとトラブルが減ります。
10. setStateの型も一緒に決まる:更新方法が安全になるメリット
useStateは、State本体だけでなく、更新用の関数であるsetState(例:setCount、setUser)の型も自動で決めます。つまり、型推論や型指定が正しくできていると、更新の仕方まで安全になります。
たとえば数字のStateなら、setCount(count + 1)のように数字の計算が自然に書けますし、文字のStateなら文字の処理が中心になります。逆に、型があいまいだと「今入っているのは数字?文字?」と迷いやすくなり、コードも読みづらくなります。
またReactでは、Stateの更新に関数形式(前の値を受け取って次の値を返す)を使うことがあります。これは「連続でボタンを押したときでも、前の値を正しく元にして更新する」ための書き方です。TypeScriptの型が合っていれば、この関数形式でも引数と戻り値の型がそろい、更新ミスを防げます。