Reactを使用したシンプルな計算機アプリの作成 | 初心者向け四則演算対応

Reactを使用したシンプルな計算機アプリの作成 | 初心者向け四則演算対応

はじめに

この記事では、Reactを使用してシンプルな計算機アプリを構築する手法を紹介します。

このアプリは、iOSiPhone電卓アプリに似たデザインを持っており、特に初心者に向けたプロジェクトです。

今回作成するReactベースの計算機アプリは、使いやすさを重視し、iOSの電卓アプリに見られるような操作感を提供します。コードはシンプルで分かりやすく保守性が高くなるように設計しています。

以下のデモを通じて、この記事で紹介する計算機アプリの動作を確認できます。

See the Pen React フックでのイベントハンドラー by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


このデモに示されている通り、この計算機アプリは加算、減算、乗算、除算などの基本的な数値演算を行うことができます。基本的な四則演算をサポートすることで、計算を手軽に行える環境を提供します。

アプリの実装には、Create-React-App(CRA)を使用し、既存のApp.jsファイルとApp.cssファイルを活用します。これにより、プロジェクトの構築がスムーズに進み、効率的な開発が可能です。

記事の後半では、コードの可読性と保守性を向上させるためのリファクタリング手法についても詳しく解説します。これにより、プロジェクトの拡張や修正が容易になり、長期的な開発においても優れた品質を維持できるでしょう。

最終的に、この記事を通じてReactを使用したシンプルな計算機アプリの構築方法を学び、より洗練された開発スキルを身につける手助けができることでしょう。

プロジェクトの作成

CRAはグローバルにインストールされていると想定します。

プロジェクトのフォルダを作成して移動し、以下のコマンドを実行してReactプロジェクトを作成します。

npx create-react-app quick-math

上記の例では、quick-mathという名前のプロジェクトが作成されます。適切な名前に変更してください。

プロジェクトが作成されたら、作成されたディレクトリに移動します。

cd quick-math

以下のコマンドを使用して、Reactアプリケーションをローカルサーバーで起動します。

npm start

ブラウザで http://localhost:3000にアクセスすると、新しいReactアプリケーションが表示されるはずです。

これで、Create React Appを使用してReact.jsプロジェクトを作成し、実行する準備が整いました。

補足として、筆記時点でのReactバージョンは以下となっています。

// package.json

"dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "cra-template": "1.2.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },

状態管理とリアルタイム表示の実装

既存のApp.jsファイル内のデフォルトコードを削除してください。

まず、アプリの主要な機能はuseStateフックを利用して状態を管理することです。

これにより、ユーザーの入力と計算結果を追跡し、リアルタイムで画面に反映させることができます。

// App.js

import React, { useState } from "react";
import "./App.css";

function App() {
  // Stateの設定
  const [input, setInput] = useState("0"); // 現在の入力値
  const [result, setResult] = useState(0); // 計算結果
  ...
}

上記のコードでは、useStateフックを使用してinputとresultという2つの状態変数を宣言しています。inputはユーザーが入力した値を表し、初期値は"0"です。一方、resultは計算結果を格納するための状態変数で、初期値は0です。

数字ボタン処理

次に、数字ボタンがクリックされたときに呼び出されるhandleNumberClick関数の実装です。

// 数字のボタンがクリックされた時の処理
  const handleNumberClick = (value) => {
    setInput((prevInput) => (prevInput === "0" ? value : prevInput + value));
  };

この関数は、setInput関数を使用してinput状態変数を更新します。

また、valueという名前の引数として受け取った数字が、入力値に追加されます。

prevInput === "0"では、現在のinputの値が"0"と等しいかどうかをチェックします。"0"と等しい場合、つまり入力値が"0"の状態である場合、valueで指定された数字が新しい入力値として設定されます。

prevInput + valueでは、入力値が"0"でない場合、つまり既に数字や演算子が含まれている場合、valueで指定された数字が既存の入力値の末尾に追加されます。

この関数では、クリックされた数値(value)を現在の入力値(prevInput)に追加しています。ただし、入力が"0"の場合はそのまま置き換えられます。これにより、最初に0が表示された後に連続した数字の入力がスムーズに行えるようになっています。

四則演算子ボタン処理

以下は、四則演算子のボタンがクリックされたときに呼び出されるhandleOperatorClick関数の実装です。

// 四則演算子のボタンがクリックされた時の処理
const handleOperatorClick = (operator) => {
  if (input !== "0") {
    setInput((prevInput) => prevInput + operator);
  }
};

この関数では、setInput関数を使用してinput状態変数を更新します。

また、演算子(operatorという名前の引数として受け取る)が指定されている場合にのみ、入力値に演算子を追加します。

つまり、この関数を実行すると、入力が"0"でない場合にのみ、クリックされた演算子(operator)を現在の入力値に追加しています。これにより、最初に演算子が入力される前に数字を入力できるようになっています。

小数点ボタン処理と重複防止の実装

以下は、小数点のボタンがクリックされた際に呼び出される関数です。

// 小数点のボタンがクリックされた時の処理
const handleDecimalClick = () => {
  setInput((prevInput) => (prevInput.includes(".") ? prevInput : prevInput + "."));
};

この関数は、setInput関数を使用してinput状態変数を更新します。

(prevInput.includes(".") ? prevInput : prevInput + ".")では、現在のinput値に小数点が含まれているかどうかをチェックし、含まれていなければ小数点を追加します。

この部分の動作を具体的に説明します。

prevInput.includes(".")は、現在のinput値に小数点( . )が含まれているかどうかをチェックします。includesメソッドは、指定した文字列が対象の文字列に含まれている場合にtrueを返し、含まれていない場合にfalseを返します。

prevInput + "."は、小数点が含まれていない場合、現在のinput値に小数点を追加します。

これにより、新しい小数点が追加されたinputの値が得られます。

つまり、このhandleDecimalClick関数を実行することで、現在の入力値に小数点を追加しますが、既に小数点が含まれている場合は追加されません。

バックスペースボタン処理と入力値の編集機能の実装

以下は、バックスペースのボタンがクリックされたときに呼び出される関数の実装です。

// バックスペースのボタンがクリックされた時の処理
const handleBackspace = () => {
  setInput((prevInput) => prevInput.slice(0, prevInput.length - 1));
};

この関数は、setInput関数を使用してinput状態変数を更新します。

setInput: input状態変数を更新するための関数です。

prevInput: 現在のinputの値を表す引数です。

prevInputという名前は任意であり、実際にはどんな名前でも構いませんが、一般的に前の状態を示すprevまたはprevを付けることがよくあります。

prevInput.slice(0, prevInput.length - 1): 現在のinput値の末尾から1文字を削除する処理を行います。

sliceメソッドは、指定したインデックスの範囲の要素を取得するメソッドです。ここでは、文字列の先頭から末尾の1文字手前までの範囲を取得しています。つまり、末尾の1文字が削除されます。

この関数を実行することで、入力値の最後の文字が1文字削除されます。

例えば、入力値が"12345"だった場合、handleBackspace関数を実行するとinputの値は"1234"になります。

クリアボタン処理とリセット機能の実装

以下のhandleClearは、クリアのボタンがクリックされたときに呼び出される関数の実装です。

// クリアのボタンがクリックされた時の処理
const handleClear = () => {
  setResult(0);
  setInput("0");
};

この関数は2つの状態変数を初期値に戻すための処理を行っています。

setResult(0): result状態変数を0に設定しています。

setInput("0"): input状態変数を"0"に設定しています。

これにより、クリアボタンが押されると、入力値が"0"にリセットされ、計算結果も0にリセットされます。

計算ボタン処理とエラーハンドリング

以下は、計算ボタンがクリックされたときに呼び出されるhandleCalculate関数の処理となります。

// 計算のボタンがクリックされた時の処理
const handleCalculate = () => {
  try {
    const calculatedResult = evaluateExpression(input);
    setInput(calculatedResult.toString());
    setResult(calculatedResult);
  } catch (error) {
    setInput("エラー:" + error.message);
    setResult("エラー");
  }
};

上記では、計算のボタンがクリックされた際に呼び出される関数の処理です。

evaluateExpression関数を使用して入力された式を評価し、結果を表示します。

また、エラーが発生した場合はエラーメッセージを表示します。

ただし、エラー処理とエラーメッセージ表示には改善の余地があります。

現在の実装では、エラーメッセージを入力フィールドと結果フィールドの文字列として直接設定しますが、これはエラーを表示する最もユーザーフレンドリーな方法ではない可能性があります。

代わりに、エラー処理を強化して、特定の使用例や要件に基づいてエラーメッセージをカスタマイズしてください。

例を示しておきます。

const handleCalculate = () => {
  try {
    const calculatedResult = evaluateExpression(input);
    setInput(calculatedResult.toString());
    setResult(calculatedResult);
  } catch (error) {
    let errorMessage = "エラーが発生しました。";
    if (error instanceof SyntaxError) {
      errorMessage = "入力が無効です:式に間違いがないか確認してください。";
    } else if (error instanceof TypeError) {
      errorMessage = "無効な操作です:式が正しいことを確認してください。";
    }
    setInput("エラー:" + errorMessage);
    setResult("エラー");
  }
};

上記のように、エラー処理を強化して、より明確で有益なメッセージをユーザーに提供できます。

安全な四則演算の評価

以下は、自前で四則演算を評価するための関数の処理です。

// 自前で四則演算を評価する関数
const evaluateExpression = (expression) => {
  // 不正な文字を取り除く
  const sanitizedExpression = expression.replace(/[^-()\d/*+.]/g, '');

  // 評価
 /* eslint-disable no-eval */
  const result = Function(`"use strict";return (${sanitizedExpression})`)();
 /* eslint-enable no-eval */
  if (Number.isInteger(result)) {
    return result; // 整数の場合は ".0" を表示せずにそのまま返す
  } else {
    return result.toFixed(2); // 小数点以下2桁で表示
  }
};

上記のevaluateExpression関数は自前で四則演算を評価するための関数です。与えられた式から不正な文字を取り除き、Functionコンストラクタを使用して式を評価します。

Functionコンストラクタを使用して、新しい関数を作成する場合、その関数は通常のコードとは異なるコンテキストで評価されます。

そのため、厳格モード(strict mode)を明示的に指定する必要があります。

評価結果が整数の場合はそのまま返し、小数の場合は小数点以下2桁で表示されます。

自前の評価関数は、安全性を確保するために特定の演算子と数字以外の文字を取り除いたり、厳格な制御を行ったりすることができます。

Number.isInteger()は、与えられた値が整数かどうかを判定するJavaScriptの組み込み関数です。

この関数を使って、計算結果が整数であるかどうかを確認しています。

ESLintの無効化と有効化によるコメントアウトについて心配する必要はありません。これは当はてなブログテーマの仕様により、特定のコメントアウトを有効にしてコードの表示を正しく行うための手段です。

ただし、今回のReactコードでは問題ないため、no-evalルールに違反する箇所は存在しません。そのため、これらの特定のルールを無効化する必要はありませんし、コメントアウトを削除しても構いません。

安心してコメントアウトを削除してコードを整理してください。

evaluateExpression関数は、安全で便利な四則演算の評価を行いたい場合に役立つでしょう。使い方をマスターすれば、計算結果を簡単に取得できるので、是非試してみてください。

表示部分の実装

最後に、計算機の外観と動作は、複数のdiv要素とbutton要素を使用して構築しています。

// JSX
return (
    <div className="container">
      <div className="calculator">
        <h1>計算機</h1>
        <div className="input-container">
          <input type="text" value={input} readOnly />
          <button className="backspace-button" onClick={handleBackspace}>
            &#9003;
          </button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("1")}>1</button>
          <button onClick={() => handleNumberClick("2")}>2</button>
          <button onClick={() => handleNumberClick("3")}>3</button>
          <button className="operator-button" onClick={() => handleOperatorClick("+")}>+</button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("4")}>4</button>
          <button onClick={() => handleNumberClick("5")}>5</button>
          <button onClick={() => handleNumberClick("6")}>6</button>
          <button className="operator-button" onClick={() => handleOperatorClick("-")}>-</button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("7")}>7</button>
          <button onClick={() => handleNumberClick("8")}>8</button>
          <button onClick={() => handleNumberClick("9")}>9</button>
          <button className="operator-button" onClick={() => handleOperatorClick("*")}>*</button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("0")}>0</button>
          <button className="clear-button" onClick={handleClear}>
            C
          </button>
          <button className="operator-button" onClick={handleDecimalClick}>.</button>
          <button className="operator-button" onClick={handleCalculate}>=</button>
        </div>
        <div>
          <p>結果: {result}</p>
        </div>
      </div>
    </div>
  );

全体のコンテナとしての<div className="container">要素には、計算機の見た目全体を定義する要素が含まれます。

計算機のタイトルが<h1>計算機</h1>で表示されます。

入力フィールドを表す<input>要素は、<div className="input-container">内に配置し、ユーザーの入力を受け付ける部分です。

value属性には変数inputの値が表示され、readOnly属性により直接入力はできず、コード内の関数によって制御された入力が行われます。

計算機の数字ボタンは4つの行に分けて表示され、各行には1つの演算子ボタンがあります。

数字ボタンはそれぞれhandleNumberClick関数を呼び出し、対応する数字を入力フィールドに追加します。演算子ボタンはhandleOperatorClick関数を呼び出し、対応する演算子を入力フィールドに追加します。

計算機には他にも、バックスペース機能、小数点の追加、入力のクリア、計算結果の表示などの機能が備わっています。これらの機能は各ボタンがクリックされたときに対応する関数(handleBackspace、handleDecimalClick、handleClear、handleCalculateなど)が実行されることによって動作します。

計算結果は<p>結果: {result}</p>で表示され、結果は変数resultによって反映されます。

ユーザーが数字や演算子ボタンをクリックすると、入力フィールドにそれらの値が反映され、計算ボタンをクリックすると計算が実行されて結果が表示されます。

バックスペースボタンを使用して入力を修正したり、クリアボタンを使用して入力をリセットしたりすることができます。

全体コードは以下です。

// App.js

import React, { useState } from "react";
import "./App.css";

function App() {
  const [input, setInput] = useState("0"); // 現在の入力値
  const [result, setResult] = useState(0); // 計算結果

  // 数字のボタンがクリックされた時の処理
  const handleNumberClick = (value) => {
    setInput((prevInput) => (prevInput === "0" ? value : prevInput + value));
  };

  // 四則演算子のボタンがクリックされた時の処理
  const handleOperatorClick = (operator) => {
    if (input !== "0") {
      setInput((prevInput) => prevInput + operator);
    }
  };

  // 小数点のボタンがクリックされた時の処理
  const handleDecimalClick = () => {
    setInput((prevInput) => (prevInput.includes(".") ? prevInput : prevInput + "."));
  };

  // バックスペースのボタンがクリックされた時の処理
  const handleBackspace = () => {
    setInput((prevInput) => prevInput.slice(0, prevInput.length - 1));
  };

  // クリアのボタンがクリックされた時の処理
  const handleClear = () => {
    setResult(0);
    setInput("0");
  };

  // 計算のボタンがクリックされた時の処理
  const handleCalculate = () => {
    try {
      const calculatedResult = evaluateExpression(input);
      setInput(calculatedResult.toString());
      setResult(calculatedResult);
    } catch (error) {
      setInput("エラー:" + error.message);
      setResult("エラー");
    }
  };

  // 自前で四則演算を評価する関数
  const evaluateExpression = (expression) => {
    // 不正な文字を取り除く
    const sanitizedExpression = expression.replace(/[^-()\d/*+.]/g, '');

    // 評価
   /* eslint-disable no-eval */
    const result = Function(`"use strict";return (${sanitizedExpression})`)();
  /* eslint-enable no-eval */
    if (Number.isInteger(result)) {
      return result; // 整数の場合は ".0" を表示せずにそのまま返す
    } else {
      return result.toFixed(2); // 小数点以下2桁で表示
    }
  };

  return (
    <div className="container">
      <div className="calculator">
        <h1>計算機</h1>
        <div className="input-container">
          <input type="text" value={input} readOnly />
          <button className="backspace-button" onClick={handleBackspace}>
            &#9003;
          </button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("1")}>1</button>
          <button onClick={() => handleNumberClick("2")}>2</button>
          <button onClick={() => handleNumberClick("3")}>3</button>
          <button className="operator-button" onClick={() => handleOperatorClick("+")}>+</button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("4")}>4</button>
          <button onClick={() => handleNumberClick("5")}>5</button>
          <button onClick={() => handleNumberClick("6")}>6</button>
          <button className="operator-button" onClick={() => handleOperatorClick("-")}>-</button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("7")}>7</button>
          <button onClick={() => handleNumberClick("8")}>8</button>
          <button onClick={() => handleNumberClick("9")}>9</button>
          <button className="operator-button" onClick={() => handleOperatorClick("*")}>*</button>
        </div>
        <div>
          <button onClick={() => handleNumberClick("0")}>0</button>
          <button className="clear-button" onClick={handleClear}>
            C
          </button>
          <button className="operator-button" onClick={handleDecimalClick}>.</button>
          <button className="operator-button" onClick={handleCalculate}>=</button>
        </div>
        <div>
          <p>結果: {result}</p>
        </div>
      </div>
    </div>
  );
}

export default App;

初心者向けのスタイリング例

CSSでは、計算機の見た目と操作性を向上させるために使用しています。

アプリのスタイリングはApp.cssファイルを使用します。

/* App.css */
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

h1 {
  color: #fff;
}

p {
  color: #fff;
}

.backspace-button {
  background-color: #ff9800;
  color: white;
  position: absolute;
  top: 85px;
  left: 200px;
  opacity: 0.7;
}

.calculator .clear-button {
  width: 50px;
  /* テキストに合わせて適切な幅を指定 */
  height: 50px;
  margin: 5px;
  font-size: 20px;
  cursor: pointer;
  background-color: #ff9800;
}

.calculator {
  border: 1px solid #ccc;
  padding: 20px;
  border-radius: 10px;
  width: 250px;
  position: relative;
  background-color: black;
}

.calculator h1 {
  margin-bottom: 10px;
}

.calculator input {
  width: 100%;
  margin-bottom: 10px;
  padding: 5px;
  border: none;
  /* 枠を削除 */
  outline: none;
  /* フォーカス時のアウトラインを削除 */
  font-weight: bold;
  font-size: 25px;
  background-color: black;
  color: #fff;
}

.calculator button {
  width: 50px;
  height: 50px;
  margin: 5px;
  font-size: 20px;
  cursor: pointer;
  border-radius: 50%;
  /* ボタンを丸くする */
  border: none;
  /* ボーダーを削除して丸い形を保つ */

}

.operator-button {
  background-color: #ff9800;
  /* 演算子の背景色を統一 */
}

.calculator button:hover {
  background-color: #ccc;
}

以下にスタイルの概要を解説します。

計算機全体の外枠は.containerクラスで定義しました、display: flexを使用して子要素を縦方向に中央に配置しています。これにより、画面全体の高さ(100vh)いっぱいにコンテナを広げることができます。

計算機のタイトルはh1タグで表示し、テキストの色は白に指定しています。

ボタンは数字ボタン、演算子ボタン、小数点ボタン、クリアボタン、バックスペースボタンの5種類です。これらのボタンは.calculatorクラスでまとめ、ボタンのスタイルが共通で適用されます。

各ボタンのスタイルはwidthやheight、marginなどのサイズや余白を指定し、フォントサイズは20pxに設定しています。

cursor: pointerを設定して、ボタンにカーソルを合わせるとポインターが変わるようにしました。また、ボタンを丸くするためにborder-radius: 50%を使用しています。

演算子ボタンの背景色はオレンジ(#ff9800)に指定し、ホバー時のスタイルは薄いグレー(#ccc)に変化します。

クリアボタンとバックスペースボタンにもオレンジの背景色を指定し、位置は絶対位置で設定しています。

計算機のコンテンツ部分(計算結果表示部分)はinputタグで実装し、幅は親要素いっぱいに広がるようにwidth: 100%が指定しています。フォントサイズは25pxです。テキストは太字に表示されます。

背景色は黒に設定し、テキストの色は白となっています。

これらのスタイルにより、計算機アプリは見た目がシンプルで使いやすく、各ボタンが直感的に操作できるインターフェースが提供されます。

しかし、CSSのスタイリングは個々のプロジェクトや好みに合わせて自由に編集してください。これはあくまでも私がスタイリングした初心者向けの例です。

ボタンのデザインや配置、結果表示のスタイリングなど、UI/UXを改善する余地があります。ユーザビリティを向上させるために、見やすさや使いやすさを考慮することが重要です。

このテンプレを元に、計算機の外観や操作性を自分好みにカスタマイズして、より使いやすいアプリケーションを作成してください。

以上が、このReactアプリの主要な部分の解説です。

このアプリはユーザーが数字・演算子をクリックして計算式を入力し、計算結果を表示するシンプルな計算機となります。

コード保守性向上のリファクタリング

元のコードをよりシンプルで洗練された形に再構築し、可読性と保守性を向上させることを目指しました。

以下がリファクタリング後のコードです。

import React, { useState } from 'react';
import "./App.css";

function App() {
  // ステート変数の定義
  const [input, setInput] = useState('0'); // 入力された式や数字を保持するステート変数
  const [result, setResult] = useState(0); // 計算結果を保持するステート変数

  // 数字や演算子のボタンがクリックされたときの処理
  const handleButtonClick = (value) => {
    setInput((prevInput) => {
      if (isOperator(prevInput[prevInput.length - 1]) && isOperator(value)) {
        // 直前の入力が演算子であり、新しい入力も演算子の場合は何もしない
        return prevInput;
      } else if (prevInput === '0' && !isOperator(value)) {
        // '0'の場合かつvalueが演算子でない場合
        return value;
      } else {
        return prevInput + value;
      }
    });
  };

  // バックスペースボタンがクリックされたときの処理
  const handleBackspace = () => {
    setInput((prevInput) => prevInput.slice(0, prevInput.length - 1));
  };

  // クリアボタンがクリックされたときの処理
  const handleClear = () => {
    setResult(0);
    setInput('0');
  };

  // '='ボタンがクリックされたときの処理
  const handleCalculate = () => {
    try {
      const calculatedResult = evaluateExpression(input);
      setInput(calculatedResult.toString());
      setResult(calculatedResult);
    } catch (error) {
      setInput('エラー:' + error.message);
      setResult('エラー');
    }
  };

  // 小数点ボタンがクリックされたときの処理
  const handleDecimalClick = () => {
    setInput((prevInput) => (prevInput.includes('.') ? prevInput : prevInput + '.'));
  };

  // 演算子ボタンがクリックされたときの処理
  const handleOperatorClick = (operator) => {
    if (!isOperator(input[input.length - 1])) {
      setInput((prevInput) => prevInput + operator);
    }
  };

  // 式を評価する関数
  const evaluateExpression = (expression) => {
    const sanitizedExpression = expression.replace(/[^-()\d/*+.]/g, ''); // 数字、演算子、括弧、および小数点以外の文字を削除する
    /* eslint-disable no-eval */
    const result = Function(`"use strict";return (${sanitizedExpression})`)(); // Functionコンストラクタを使って式を評価する
  /* eslint-enable no-eval */ 
    return Number.isInteger(result) ? result : result.toFixed(2); // 結果が整数ならそのまま、小数なら小数点以下2桁に丸める
  };

  // 演算子を含むかどうかを判定する関数
  const isOperator = (value) => {
    return ['+', '-', '*', '=', 'C', '.'].includes(value);
  };

  // ボタンの配置を定義
  const buttonConfig = [
    ['1', '2', '3', '+'],
    ['4', '5', '6', '-'],
    ['7', '8', '9', '*'],
    ['0', 'C', '.', '='],
  ];

  return (
    <div className="container">
      <div className="calculator">
        <h1>計算機</h1>
        <div className="input-container">
          <input type="text" value={input} readOnly />
          <button className="backspace-button" onClick={handleBackspace}>
            &#9003;
          </button>
        </div>
        {/* ボタンを配置 */}
        {buttonConfig.map((row, rowIndex) => (
          <div key={rowIndex}>
            {row.map((value) => (
              <button
                key={value}
                className={`calculator-button ${value === 'C' ? 'clear-button' : ''} ${isOperator(value) ? 'operator' : ''}`}
                onClick={() =>
                  value === 'C'
                    ? handleClear()
                    : value === '='
                    ? handleCalculate()
                    : value === '.'
                    ? handleDecimalClick()
                    : handleButtonClick(value)
                }
              >
                {value}
              </button>
            ))}
          </div>
        ))}
        <div>
          <p>結果: {result}</p>
        </div>
      </div>
    </div>
  );
}

export default App;

CSSも変更点があります。

/* App.css */
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

h1 {
  color: #fff;
}

p {
  color: #fff;
}

.backspace-button {
  background-color: #ff9800;
  color: white;
  position: absolute;
  top: 85px;
  left: 200px;
  opacity: 0.7;
}

.calculator .clear-button {
  width: 50px;
  /* テキストに合わせて適切な幅を指定 */
  height: 50px;
  margin: 5px;
  font-size: 20px;
  cursor: pointer;
  background-color: #fff; /* クリアボタンの背景色を白色に設定 */
  color: #000; /* クリアボタンの文字色を黒色に設定 */
}

.calculator {
  border: 1px solid #ccc;
  padding: 20px;
  border-radius: 10px;
  width: 250px;
  position: relative;
  background-color: black;
}

.calculator h1 {
  margin-bottom: 10px;
}

.calculator input {
  width: 100%;
  margin-bottom: 10px;
  padding: 5px;
  border: none;
  /* 枠を削除 */
  outline: none;
  /* フォーカス時のアウトラインを削除 */
  font-weight: bold;
  font-size: 25px;
  background-color: black;
  color: #fff;
}

.calculator button {
  width: 50px;
  height: 50px;
  margin: 5px;
  font-size: 20px;
  cursor: pointer;
  border-radius: 50%;
  /* ボタンを丸くする */
  border: none;
  /* ボーダーを削除して丸い形を保つ */
}

.calculator-button:hover {
  background-color: #ccc;
  /* ボタンの共通スタイル */
}

.calculator-button.clear-button {
  /* Cボタンのスタイル */
}

.calculator-button.operator {
  background-color: #ff9800;
  /* 演算子ボタンの背景色をオレンジ色に設定 */
}

.calculator-button.operator:hover {
  background-color: #ccc;
  /* 演算子ボタンをホバーした際の背景色を灰色に設定 */
}

See the Pen React シンプルな計算機 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


元のコードと比較して、リファクタリング後はコード行が増えたものの、その代わりに可読性が向上し、保守性が高まったことに注目してください。

それでは、より良いコードを具体的に見ていきましょう。

ボタン配置の最適化

ボタンの配置をbuttonConfigという2次元配列を使って定義しました。

以下の行でのリファクタリングにより、ボタンの配置に関する改善を行いました。

この変更により、コードの構造と可読性が向上し、保守性と拡張性が強化されました。

 // ボタンの配置を定義
  const buttonConfig = [
    ['1', '2', '3', '+'],
    ['4', '5', '6', '-'],
    ['7', '8', '9', '*'],
    ['0', 'C', '.', '='],
  ];

リファクタリング前のコードでは、各ボタンが個別に定義され、重複したコードが存在していました。

しかし、リファクタリング後のコードでは、ボタンの配置を2次元配列で定義することで、同じ処理を反復する必要がなくなりました。これにより、コードの構造が洗練され、整理されました。

さらに、2次元配列内の要素をループを使用して処理することで、コードの簡潔さと可読性が向上しました。新しいボタンを追加する際も、変更が迅速かつ容易に行えるようになりました。

2次元配列の導入により、ボタンの配置が視覚的に明確になり、どのボタンがどの位置に配置されているかが一目で理解できます。これにより、コードの意図が明確に伝わり、理解が容易になりました。

さらに、2次元配列を使用することで、新しいボタンを追加する際の手間が削減されました。要素を配列に追加するだけで新機能を実装できるため、拡張性が向上しました。

総じて、ボタンの配置に関するリファクタリングは、コードの構造化と整理に成功し、読みやすさ、保守性、拡張性を向上させる効果的な変更と言えます。

isOperator関数の導入

演算子を含むかどうかを判定するためのisOperator関数を新たに導入しました。

// 演算子を含むかどうかを判定する関数
  const isOperator = (value) => {
    return ['+', '-', '*', '=', 'C', '.'].includes(value);
  };

この関数の導入により、リファクタリング後のコードの可読性が向上し、計算ボタンがクリックされた際の処理や数値・演算子ボタンのクリック処理の中で、より具体的でわかりやすい条件を使用することができるようになりました。

また、演算子のリストが変更される場合でも、isOperator関数の中身を修正するだけで対応できるという柔軟性もございます。

handleButtonClick関数の改善

この行では、いくつかの改善点と変更を行いました。

まず、リファクタリング前のコードでは、計算ボタンがクリックされた際の処理がhandleCalculate関数に集約されていましたが、これがリファクタリング後のコードには含まれません。

代わりに、数字や演算子のボタンがクリックされたときの処理を担当するhandleButtonClick関数を新たに導入し、数値ボタンと演算子ボタンのクリック処理を同じ関数で処理するようにしました。

 // 数字や演算子のボタンがクリックされたときの処理
  const handleButtonClick = (value) => {
    setInput((prevInput) => {
      if (isOperator(prevInput[prevInput.length - 1]) && isOperator(value)) {
        // 直前の入力が演算子であり、新しい入力も演算子の場合は何もしない
        return prevInput;
      } else if (prevInput === '0' && !isOperator(value)) {
        // '0'の場合かつvalueが演算子でない場合
        return value;
      } else {
        return prevInput + value;
      }
    });
  };

この変更により、コードの構造がより分かりやすくなり、処理の共通化によってコードの再利用性と保守性が向上しました。

条件分岐をシンプルにするため、直前の入力が演算子であり、新しい入力も演算子の場合は何もしないような条件を追加しました。これにより、連続した演算子の入力や、不要な"0"の入力を防ぐことができます。

機能の適切な分割と責任の明確化

evaluateExpression関数内のリファクタリング後のコードは、リファクタリング前のコードと機能的に同じですが、いくつか改善されたポイントがあります。

// 式を評価する関数
  const evaluateExpression = (expression) => {
    const sanitizedExpression = expression.replace(/[^-()\d/*+.]/g, ''); // 数字、演算子、括弧、および小数点以外の文字を削除する
    /* eslint-disable no-eval */
    const result = Function(`"use strict";return (${sanitizedExpression})`)(); // Functionコンストラクタを使って式を評価する
  /* eslint-enable no-eval */ 
    return Number.isInteger(result) ? result : result.toFixed(2); // 結果が整数ならそのまま、小数なら小数点以下2桁に丸める
  };

まず、機能の分割です。

リファクタリング後のコードでは、不正な文字を削除する部分、式を評価する部分、結果を整形する部分が明確に分かれるようにしました。

それにより、各部分が単一の責任を持つようになっており、コードの保守性が向上しています。

さらに、関数名を「evaluateExpression」と変更し、その関数がどのような機能を持っているかが明確になっています。これにより、コードを読む人が関数の目的をより容易に理解できます。

また、コメントを適切に整理しました。

全体的に、コードの可読性が向上し、コメントが適切に整理され、機能の分割が行われていることで、コードの保守性や理解がより容易になっています。

結論

リファクタリング後のコードは、リファクタリング前と比較してコード行が増えたものの、その代わりに保守性と可読性が向上しています。

リファクタリング後のコードでは、ボタンの配置と処理が効率的に管理されており、同様の操作に対してコードが繰り返し記述されることがなくなりました。

これによりコードが整理され、保守性が向上しています。また、拡張性も高まっており、新しいボタンを追加する際にはbuttonConfigに要素を追加するだけで済みます。

リファクタリングによってコードの可読性が向上し、同じ処理を共有する関数の導入によって効率が向上しました。このような変更により、コードの管理や拡張が容易になるといったメリットが生まれました。

最後に

自前の評価関数を実装する際に注意点があります。

数式の評価は複雑な問題であり、バグや誤った評価を防ぐためにテストケースを充実させることが重要です。また、セキュリティに対する注意と慎重な入力検証も必要です。

自前の評価関数を実装する場合は、セキュリティと正確性に十分な配慮をすることが重要です。

また、このアプリケーションはシンプルな計算機であり、四則演算に特化したコードです、高度な数学的機能(平方根三角関数など)やメモリー機能、履歴表示などは実装していません。必要に応じて、さらなる機能を追加することができます。

そして、パフォーマンスです、大きな式や複雑な計算が行われる場合に、式の評価に時間がかかる可能性があります。より効率的なアルゴリズムやキャッシュの使用を検討するとよいでしょう。

これらの注意点を考慮して、アプリケーションを改善・拡張することで、より安全で機能が豊富な計算機アプリケーションを作成できるようになるはずです。

どうぞ、当記事のReactコードを参考にして、より良いコードの開発に向けて進んでいただければ幸いです。

本日は以上となります。

最後まで読んで頂きありがとうございます。

この記事が役に立ったら、ブックマークと共有をしていただけると嬉しいです。

プライバシーポリシー

© 2023 Icons8 LLC. All rights reserved.

© [deve.K], 2023. React logo is a trademark of Facebook, Inc. JavaScript is a trademark of Oracle Corporation and/or its affiliates. jQuery and the jQuery logo are trademarks of the JS Foundation. TypeScript and the TypeScript logo are trademarks of the Microsoft Corporation. Next.js and the Next.js logo are trademarks of Vercel, Inc. Firebase and the Firebase logo are trademarks of Google LLC. All logos edited by [deve.K].