第38章:練習:カウンターアプリ (型も意識して!)
この章では、Reactの超定番「カウンターアプリ」を TypeScript 付きで作ってみます 🎉
useState とイベントハンドラを実際に使いながら、「型」がどう効いているのかも一緒に確認していきます。
38-1 🎯 この章のゴール
この章が終わるころには、こんなことができるようになります。
- ボタンを押すと数字が増えたり減ったりするカウンターを作れる ✨
useState<number>で 状態に型を付ける 感覚がつかめるPropsとuseStateを一緒に使って、 「最初の値(初期値)」を外から渡せるようになるsetCount(prev => prev + 1)みたいな “前の値から更新する書き方” に慣れる (codefinity.com)
React 19 でも、こういう基本的な useState はそのまま大事なフックとして使われています 💪(react.dev)
38-2 👀 完成イメージ
画面イメージはだいたいこんな感じです:
- 上にタイトル「カウンターアプリ」
- 真ん中に「現在のカウント: 0」
- その下に
+1ボタン、-1ボタン、リセットボタン
ボタンをポチポチすると数字が増えたり減ったりしてくれます 🧮✨
38-3 🚀 準備の確認(サクッと)
この章は、すでに以下が終わっている前提で進めます。
- Vite で
react-tsプロジェクトを作成済み npm install済み- 開発サーバーを動かせる状態
開発サーバーは、プロジェクトのフォルダで:
npm run dev
ブラウザで http://localhost:5173(Viteのデフォルト)を開いて、
画面が表示されることだけサクッと確認しておいてください 🖥️
38-4 🧩 Counter コンポーネントを作る
まずはカウンター専用の部品を作ります。
src フォルダの中に、Counter.tsx を新しく作りましょう。
38-4-1 Counter.tsx を作成
src/Counter.tsx に、いったん 完成形のコード を書いてしまいます。
// src/Counter.tsx
import { useState } from "react";
type CounterProps = {
/** 最初のカウント値(省略OK) */
initialCount?: number;
};
export function Counter({ initialCount = 0 }: CounterProps) {
// 👇 useState に <number> と型を付けているのがポイント!
const [count, setCount] = useState<number>(initialCount);
const handleIncrement = () => {
// 前の値から +1 する「関数形式」の更新
setCount((prev) => prev + 1);
};
const handleDecrement = () => {
setCount((prev) => prev - 1);
};
const handleReset = () => {
setCount(initialCount);
};
return (
<div
style={{
padding: "16px",
borderRadius: "12px",
border: "1px solid #ddd",
textAlign: "center",
maxWidth: "260px",
margin: "0 auto",
}}
>
<p style={{ fontSize: "20px", marginBottom: "12px" }}>
現在のカウント: <strong>{count}</strong>
</p>
<div style={{ display: "flex", gap: "8px", justifyContent: "center" }}>
<button type="button" onClick={handleIncrement}>
+1
</button>
<button type="button" onClick={handleDecrement}>
-1
</button>
<button type="button" onClick={handleReset}>
リセット
</button>
</div>
</div>
);
}
ここでの型ポイント 🧠
-
type CounterProps = { initialCount?: number }initialCountは オプショナル(あってもなくてもOK) なnumberinitialCount = 0というデフォルト値を付けているので、<Counter />と書いても<Counter initialCount={0} />と同じ意味になります。
-
useState<number>(initialCount)<number>の部分が 状態の型 です。- これにより、
countは必ずnumber型になり、setCount("あいうえお")みたいな変な代入はコンパイルエラーになります 💥(react.dev)
-
setCount((prev) => prev + 1)- 新しい値が「前の count から計算される」ときは、 関数形式のアップデート を使うのが React のおすすめスタイルです。(codefinity.com)
38-5 🧱 App.tsx から使ってみる
次に、この Counter を画面に出してあげましょう。
src/App.tsx を開いて、シンプルな内容に書き換えます。
// src/App.tsx
import "./App.css";
import { Counter } from "./Counter";
function App() {
return (
<main className="app">
<h1>カウンターアプリ 🎉</h1>
<p>ボタンを押して、数字がどう変わるか試してみよう!</p>
{/* 初期値 5 でスタートしてみる例 */}
<Counter initialCount={5} />
</main>
);
}
export default App;
npm run dev をしている状態なら、ブラウザをリロードすると
カウンターが表示されるはずです 👀
38-6 🎨 かんたんスタイルを当ててみる(おまけ)
見た目をちょっとだけ整えたい場合は、src/App.css を軽くこんな感じにしてもOKです(完全にお好み)。
/* src/App.css */
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
justify-content: center;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
}
button {
padding: 6px 12px;
border-radius: 999px;
border: 1px solid #ccc;
background: white;
cursor: pointer;
}
button:hover {
background: #f5f5f5;
}
ふわっとでいいので、「あ、なんかちゃんとアプリっぽい!」くらいになれば十分です 🌈
38-7 🧠 型の流れを図でイメージしてみよう(Mermaid)
ボタンをクリックしてから、数字が変わるまでの流れを Mermaid で図にしてみます 🖊️
この中で state の型はずっと number のまま流れていきます。
count:numberprev(setCountの中の引数):numberprev + 1: もちろんnumber
どこかで型を間違えると、コンパイル時に止めてくれる のが TypeScript の安心ポイントです 🔒
38-8 ✍️ ちょっとだけ発展練習(やってみよう!)
時間があれば、次のような “プラスアルファ練習” もやってみてください。
お題1:2倍ボタンを追加する
×2ボタンを追加して、- クリックしたら
countを 2倍 にしてみてください。
ヒント:
const handleDouble = () => {
setCount((prev) => prev * 2);
};
お題2:0より小さくならないようにする
-1ボタンを押しても、countが 0 より小さくならないようにしてみましょう。
ヒント:
const handleDecrement = () => {
setCount((prev) => {
const next = prev - 1;
return next < 0 ? 0 : next;
});
};
お題3:複数のカウンターを並べてみる
App.tsx で、<Counter /> を2つ並べてみましょう。
<Counter initialCount={0} />
<Counter initialCount={10} />
- それぞれがちゃんと別々の state を持って動いているか、 ボタンを押しながら確認してみてください 👀
38-9 🌟 まとめ
この章では…
typeで Props(initialCount)の型を定義したuseState<number>で 状態の型 をしっかり指定したsetCount((prev) => prev + 1)のように 「前の値から更新する」関数形式のアップデートを使った- カウンターアプリを実際に動かしながら、 型がちゃんと守ってくれている感覚をつかんだ
次の章からも、「まず型をどうする?」 という視点を忘れずに、 少しずつ複雑なUIにチャレンジしていきましょう 💪✨