1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ミニプロジェクト10連発 | [第1回]: 電卓アプリを作ってみよう

Last updated at Posted at 2025-06-24

🧮【ミニプロジェクト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 👍 やコメントをお願いします!
また、みなさんが作った電卓アプリもシェアしてくれたら嬉しいです!

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?