🧮【ミニプロジェクト10連発 #1】本気で作る電卓アプリ:設計・実装・運用まで!
1. はじめに 〜 なぜ今「電卓アプリ」なのか?
「電卓アプリって、ただの足し算と引き算でしょ?」
そう思った方、ちょっと待ってください。
このシンプルなプロジェクトは、実はUI設計・状態管理・ロジック分離・自動テスト・CI/CDなど、プロダクト開発に必要なエッセンスが全部詰まっています。
この記事では、「電卓アプリ」を題材にしながら、フロントエンドからテスト、デプロイまで一気に体験できる構成で進めていきます。
2. 技術スタックと全体構成
🧰 使用技術
カテゴリ | 技術 |
---|---|
フロントエンド | React + TypeScript |
状態管理 | useReducer |
テスト | Jest + React Testing Library |
CI/CD | GitHub Actions + Vercel |
📐 アーキテクチャ概要
├── components/
│ └── Calculator.tsx … UIコンポーネント
├── logic/
│ └── calculate.ts … 計算ロジック(テスト可能)
├── tests/
│ └── calculate.test.ts
├── .github/workflows/deploy.yml … CI/CD設定
3. 実装してみよう!
3.1 UI: シンプルで直感的なデザイン
// components/Calculator.tsx
import React, { useReducer } from "react";
import { calculate, Action, initialState, reducer } from "../logic/calculate";
export const Calculator = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="calculator">
<div className="display">{state.display}</div>
<div className="buttons">
{"123+456-789*0=/C".split("").map((char) => (
<button key={char} onClick={() => dispatch({ type: "INPUT", payload: char })}>
{char}
</button>
))}
</div>
</div>
);
};
3.2 ロジックをUIから切り離す
// logic/calculate.ts
export type State = {
display: string;
};
export const initialState: State = {
display: "",
};
export type Action =
| { type: "INPUT"; payload: string }
| { type: "CLEAR" };
export const reducer = (state: State, action: Action): State => {
if (action.type === "INPUT") {
if (action.payload === "=") {
try {
// 危険なevalは避け、別ライブラリを使うのが理想
const result = Function(`return (${state.display})`)();
return { display: result.toString() };
} catch {
return { display: "Error" };
}
}
if (action.payload === "C") return initialState;
return { display: state.display + action.payload };
}
return state;
};
3.3 単体テストを書いて品質を担保する
// tests/calculate.test.ts
import { reducer, initialState } from "../logic/calculate";
test("1+1=", () => {
let state = initialState;
state = reducer(state, { type: "INPUT", payload: "1" });
state = reducer(state, { type: "INPUT", payload: "+" });
state = reducer(state, { type: "INPUT", payload: "1" });
state = reducer(state, { type: "INPUT", payload: "=" });
expect(state.display).toBe("2");
});
4. 実務で役立つTips & ハマりポイント
✅ Tips
-
useReducer
で状態とロジックを明確に分離 - 計算処理は副作用なしの関数に切り出すことでテスト容易性UP
- 計算式の解釈は
eval
を避け、math.jsなどのライブラリ利用が推奨
❌ よくある失敗
誤り例 | 修正案 |
---|---|
eval に直接ユーザー入力を渡す |
安全な数式パーサーを使う |
計算ロジックがコンポーネント内にある |
logic/ として分離 & テスト対象に |
エラーハンドリングがない |
try-catch で例外処理を明示 |
5. 発展編:CI/CD で継続的に改善できる仕組みを構築!
GitHub Actionsで自動テスト → Vercelで即デプロイ
# .github/workflows/deploy.yml
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm test
Vercel に接続しておけば、自動的にテスト → デプロイまで完了します。
6. まとめ:この電卓で得られるスキルセットとは?
項目 | 習得できるスキル |
---|---|
UI設計 | Reactで状態管理と入力処理を設計 |
モジュール分離 | ロジックの切り出しとテスト設計 |
品質保証 | Jestによるユニットテストの導入 |
DevOps視点 | GitHub Actions + VercelによるCI/CD |
🔮 今後の展望
- 入力履歴ログを保存する機能(localStorageやDB活用)
- 複雑な計算式への対応(
math.js
導入) - スマホ対応UIへの改善
✅ 次回予告:「ToDoリストアプリ」
次は、状態管理と永続化(localStorage / Firebase)を学べるToDoアプリ開発編に進みます!
もしこの記事が役に立ったら、ぜひLGTM 👍 やコメントをお願いします!
また、みなさんが作った電卓アプリもシェアしてくれたら嬉しいです!