ST_FUNC開発日記

建築構造設計Excelアドイン開発の記録

断面性能の座標軸の変更

最近のReactの話からちょっと変わるが、断面性能関数の設計方針

強軸、弱軸をX,Yとするか、Y,Zとするか。

前者は断面をXY平面と見たもの。後者は、線材をイメージして材軸方向をXとしたもの。

今回はStbAnaSectionに準じて、強軸をY、弱軸をZとすることにする。

という風に記載した。

しかし、次にロールHの断面性能を作成しようとして、鋼構造許容応力度設計基準の付録やその元ネタのJIS G 3192とかを見てみると、

基本的に強軸X、弱軸Yになっていた。

ロール材はデータベースを用意してそこから数値を参照することを考えていたが、こうなると、参照するテーブルと記号が変わるというややこしいことになってしまう。

ということで、JISに合わせて強軸X、弱軸Yとすることにした。

また、XYの表記も、JISに合わせて小文字にすることとした(Ix, Iyなど)。

ということで、関連するコードを頑張って書き直してみた。思ったより数が多くて大変だった・・・。

(直し忘れもありそう。見つけたら教えてください)

断面の部材座標軸の変更 · st-func/st-func-ts@6ba447d · GitHub

断面の部材座標軸の変更 · st-func/st_func_addin@b23967b · GitHub

断面の部材座標軸の変更 · st-func/st_func_docs@09bae48 · GitHub

Reactの中身の検証

前回作った記事では、ChatGPTの結果を何も考えずにそれっぽく修正しただけなので、実際に何をしているのか検索しながら見ている。

importの{}の有無

impoerするときに{}があったりなかったりするのは何だろう?

【JavaScript/TypeScript】import/export from使い方まとめ #JavaScript - Qiita

defaultというexportがあるらしい。特に気にする必要はなさそうかな

React.FCとは?

ここを見ると何となく書いてある。

React.FCってなんぞや? #JavaScript - Qiita

よくわからんが、何となく理解した。

そして上にあるよくわからんinterfaceはApp.tsxからcalcModeをCalculatorに伝えるためにあるようだ。

なんでcalcModeがApp.tsxのほうにあるんだろう?

コードを作成したChatGPTに追加で聞いてみた。

上記コードではcalcModeをなぜApp.tsxで指定しているのでしょうか?
Calculatorコンポーネント内では完結できないのでしょうか?

calcModeApp.tsxで管理しているのは、計算モードの切り替えがCalculatorコンポーネントだけでなく、他のコンポーネントからも操作できるようにするためです。例えば、将来的に他のページやコンポーネントからも計算モードを変更したい場合に、App.tsxcalcModeを管理しておくことで、状態の一貫性を保つことができます。

また、App.tsxcalcModeを管理することで、Calculatorコンポーネントが状態を完全に自己管理する必要がなくなります。これにより、コンポーネントの責務を分割し、コードの理解と保守性を向上させることができます。

もちろん、calcModeApp.tsxで管理せずにCalculatorコンポーネント内で完結させることも可能ですが、アプリケーション全体の状態管理を一元化するために、App.tsxで状態を管理するアプローチが一般的です。

なるほど。でも、この計算モードは別に断面性能機能だけしか使わないので、別にAppのところにある必要はないと思う。

このcalcModeはCalculatorコンポーネント内でしか使わないです。

了解しました。calcModeCalculatorコンポーネント内でしか使用されない場合は、App.tsxで管理する必要はありません。その場合は、Calculatorコンポーネント内で状態を完結させることができます。

以下は、Calculatorコンポーネント内でcalcModeを管理する例です。

import React, { useState } from 'react';

const Calculator: React.FC = () => {
  const [calcMode, setCalcMode] = useState<'sum' | 'product'>('sum');
  const [num1, setNum1] = useState('');
  const [num2, setNum2] = useState('');
  const [num3, setNum3] = useState('');
  const [result, setResult] = useState<number | undefined>(undefined);

  const handleCalculation = () => {
    if (calcMode === 'sum') {
      const sum = parseFloat(num1) + parseFloat(num2) + parseFloat(num3);
      setResult(sum);
    } else if (calcMode === 'product') {
      const product = parseFloat(num1) * parseFloat(num2);
      setResult(product);
    }
  };

  return (
    <div>
      <h2>Calculator</h2>
      <div>
        <label>Calculation Mode:</label>
        <select value={calcMode} onChange={(e) => setCalcMode(e.target.value as 'sum' | 'product')}>
          <option value="sum">Addition</option>
          <option value="product">Multiplication</option>
        </select>
      </div>
      <div>
        <label>Number 1:</label>
        <input type="number" value={num1} onChange={(e) => setNum1(e.target.value)} />
      </div>
      <div>
        <label>Number 2:</label>
        <input type="number" value={num2} onChange={(e) => setNum2(e.target.value)} />
      </div>
      {calcMode === 'sum' && (
        <div>
          <label>Number 3:</label>
          <input type="number" value={num3} onChange={(e) => setNum3(e.target.value)} />
        </div>
      )}
      <button onClick={handleCalculation}>Calculate</button>
      <div>
        Result: {result !== undefined ? result : 'Please calculate'}
      </div>
    </div>
  );
};

export default Calculator;

このコードでは、Calculatorコンポーネント内でcalcModeを管理し、計算モードの切り替えや計算処理を行っています。App.tsxからcalcModeを削除し、Calculatorコンポーネント内で完結させることができます。

やっぱりこっちの方がすっきりするよね。

ということでコードを修正。

calcModeをApp.tsxで設定せず、Calculator.tsx内で完結するように修正 · st-func/st-func-web@fa120f9 · GitHub

()内にタグがあるのって何?

今から始めるReact入門 〜 React の基本 #React - Qiita

こちらを参照。ほんとに今更だけど、ほかの部分も少し理解が深まった気がする。

「完全に理解した」すら全然到達できなそうだが。

setStateって何?

【React】useStateの基本と利用時の注意点まとめ #React - Qiita

よくわからんけど、入出力部分はこいつを使えばいいのかな?

{calcMode === "buildBox" && ( )って何?

条件付きレンダリングというやつらしい。

Reactの条件付きレンダリングを使いこなす(徹底解説)|Kinsta®

そんなところかな?

コードの概要はなんとなくわかったので、これを本格的な断面性能計算ツールにしていきたい。

WEBアプリに断面性能計算機能を追加

まずは、作ったst-func-tsをインストールする。

$ npm install ..\st-func-ts

st-func-tsをインストール · st-func/st-func-web@d71db70 · GitHub

さて、数字の和と積を求めるだけのCalculatorを断面積を求めるように書き換えてみる。

各コードの細かい意味はよく分からないので、そのままにしてそれっぽく書き換えていく。

計算部分はExcelアドインで使った手法の、SecSteelにインスタンスを代入して共通のメソッドで断面積を取得する。

inputのbuildBoxの部分で、そのままNumber4を足したらエラーになった。

エラーメッセージを見ると一番外側を1つのタグで囲わないといけないらしい。

しかたないので、とりあえずdivタグで囲ってみた。

上記修正をして、無事に断面積を計算できるようになった。

コードは以下のような感じ。

import React, { useState } from "react";
import { SecBuildBox, SecFlatBar, SecSteel } from "@st-func/st-func-ts";
import { Unit } from "@st-func/st-func-ts";

interface CalculatorProps {
  calcMode: "buildBox" | "flatBar";
  setCalcMode: React.Dispatch<React.SetStateAction<"buildBox" | "flatBar">>;
}

const Calculator: React.FC<CalculatorProps> = ({ calcMode, setCalcMode }) => {
  const [num1, setNum1] = useState("");
  const [num2, setNum2] = useState("");
  const [num3, setNum3] = useState("");
  const [num4, setNum4] = useState("");
  const [result, setResult] = useState<number | undefined>(undefined);

  const handleCalculation = () => {
    let secSteel: SecSteel;
    if (calcMode === "buildBox") {
      const secBuildBox: SecBuildBox = new SecBuildBox();
      secBuildBox.setDimensions(
        Unit.input(parseFloat(num1), "mm"),
        Unit.input(parseFloat(num2), "mm"),
        Unit.input(parseFloat(num3), "mm"),
        Unit.input(parseFloat(num4), "mm")
      );
      secSteel = secBuildBox;
    } else if (calcMode === "flatBar") {
      const secFlatBar: SecFlatBar = new SecFlatBar();
      secFlatBar.setDimensions(
        Unit.input(parseFloat(num1), "mm"),
        Unit.input(parseFloat(num2), "mm")
      );
      secSteel = secFlatBar;
    } else {
      secSteel = new SecSteel();
    }
    let result: number = Unit.output(secSteel.area(), "mm^2");
    setResult(result);
  };

  return (
    <div>
      <h2>Calculator</h2>
      <div>
        <label>Calculation Mode:</label>
        <select
          value={calcMode}
          onChange={(e) =>
            setCalcMode(e.target.value as "buildBox" | "flatBar")
          }
        >
          <option value="buildBox">BuildBox</option>
          <option value="flatBar">FlatBar</option>
        </select>
      </div>
      <div>
        <label>Number 1:</label>
        <input
          type="number"
          value={num1}
          onChange={(e) => setNum1(e.target.value)}
        />
      </div>
      <div>
        <label>Number 2:</label>
        <input
          type="number"
          value={num2}
          onChange={(e) => setNum2(e.target.value)}
        />
      </div>
      {calcMode === "buildBox" && (
        <div>
          <div>
            <label>Number 3:</label>
            <input
              type="number"
              value={num3}
              onChange={(e) => setNum3(e.target.value)}
            />
          </div>
          <div>
            <label>Number 4:</label>
            <input
              type="number"
              value={num4}
              onChange={(e) => setNum4(e.target.value)}
            />
          </div>
        </div>
      )}
      <button onClick={handleCalculation}>Calculate</button>
      <div>Result: {result !== undefined ? result : "Please calculate"}</div>
    </div>
  );
};

export default Calculator;

Calculatorを断面積計算に変更 · st-func/st-func-web@a98a38f · GitHub

Reactのテスト

サンプルプログラムをst-func用に書き換えていく。

まず、Home.tsxにタイトルと説明を記載。

import React from "react";

const Home: React.FC = () => {
  return (
    <div>
      <h1>st-func-web</h1>
      <p>建築構造設計に用いる簡単な計算をするアプリケーションです。</p>
    </div>
  );
};

export default Home;

App.test.tsxを見ながらHome.test.tsxを作成

import { render, screen } from "@testing-library/react";
import Home from "./Home";

test("renders home", () => {
  render(<Home />);
  const titleElement = screen.getByText(/st-func/i);
  expect(titleElement).toBeInTheDocument();
});
$ npm t

でテストは動いた。

なんか、今までのテストと少し動き方が違う気がするが、よしとする。

さて、次にApp.tsxのテストをしようとしているのだが全然うまくいかない。

test("has home page link", () => {
    render(<App />);
    const linkElement = screen.getByText(/home/i);

    expect(linkElement).toBeInTheDocument();
  });

というコードを書くと下記エラーが出る

TestingLibraryElementError: Unable to find an element with the text: /home/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

検索やchatGPTで調べてみると、Routerを使っているのが問題みたいなのだが、うまく解決しない。

test("has home page link", () => {
    render(<App />, { wrapper: BrowserRouter });
    const linkElement = screen.getByText(/home/i);
    expect(linkElement).toBeInTheDocument();
  });

というのも試してみたが、

You cannot render a <Router> inside another <Router>. You should never have more than one in your app.

というエラーになる。

いろいろ試してみたが、うまくいかないので、いったんあきらめることにする・・・。

Routerでbasenameを指定しているのも複雑化させている気がする。

まあ、App.tsxはページ切り替えしか実装しなそうなので、特にテストしなくてもいいかな・・・?

Home.tsxの修正 · st-func/st-func-web@891c98f · GitHub

ウェブアプリのデプロイ

さて、テストアプリケーションができたので、実際にサーバーに設置して動くかどうか試してみる。

$ npm start

で実行すると、ローカルのhttpサーバーが立ち上がり、http://localhost:3000/でアプリにアクセスされる。

これはテスト用のものなので、ビルドするには

$ npm run build

で、まとめられたファイルが作成されるはずである。

buildフォルダの中に、ファイルが作成された。

ここで、build/index.htmlをローカルで開いてみたが、ページが真っ白で何も表示されない。

また、ロリポップのサーバー上に置いてみてもやはり真っ白で何も表示されない。

やっぱりhttpサーバーだけじゃ動かない??

build/index.htmlのソースコードを見ると下記の様になっている。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="/logo192.png" />
    <link rel="manifest" href="/manifest.json" />
    <title>React App</title>
    <script defer="defer" src="/static/js/main.ec2d2994.js"></script>
    <link href="/static/css/main.e6c13ad2.css" rel="stylesheet" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

これを見るとjsとかpngのリンクが/始まりのリンクになっているので問題が生じているように見える。

ということは、jsがきちんと読み込まれれば、httpサーバーでも動く気がする。

試しにロリポップサーバーのトップに置いてみたら普通に動いた。

しかし、トップページは普通にページがあるので、ここで動作されると困る。そこで、フォルダを切ることができないか?

前回使ったchatGPTに追加の質問を実施することにした。

ビルドのときにルート位置を/ではなく/app/にすることはできますか?

Reactアプリケーションのビルド時に、ルート位置を/ではなく/app/に設定することは可能です。このように設定すると、ビルドされたアプリケーションは/app/以下に配置されます。

設定方法は、Reactアプリケーションのpackage.jsonファイル内のhomepageフィールドを編集することで行います。具体的には、package.jsonファイルに次のようにhomepageフィールドを追加します。

{
  "name": "your-app-name",
  "version": "1.0.0",
  "homepage": "/app/",
  ...
}

この設定を行った後に、通常通りnpm run buildコマンドを実行すると、ビルドされたアプリケーションは/app/以下に配置されます。その後、Webサーバーでこのディレクトリを公開すれば、アプリケーションを/app/以下のパスでアクセスできるようになります。

注意点として、homepageフィールドで設定したパスと実際の公開パスが一致している必要があります。つまり、Webサーバーでアプリケーションを公開する際にも/app/以下に配置する必要があります。

なるほど。そこに記載すればよいのか。 ということで、ここにアプリ名のフォルダ名(st-func-webとした)を記載して再ビルドを行う。

これで、st-func-webにアクセスしてみたら、無事に表示された!

しかし、ページの切り替えをすると、ページが表示されるものの、アドレスバーのアドレスは/がルートであるアドレスになってしまう。

ということでまた質問してみる。

下記コードで、rootのurlが/ではなく/app/のときはどう記載すればよいですか?

import React, { useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

// Components
import Home from './Home';
import Calculator from './Calculator';

const App: React.FC = () => {
  const [calcMode, setCalcMode] = useState<'sum' | 'product'>('sum');

  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/calc">Calculator</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/calc" element={<Calculator calcMode={calcMode} setCalcMode={setCalcMode} />} />
        </Routes>
      </div>
    </Router>
  );
};

export default App;

rootのURLを/app/に変更したい場合は、<Router>コンポーネントbasenameプロパティを追加して、/app/を指定します。以下は修正したコードです。

import React, { useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

// Components
import Home from './Home';
import Calculator from './Calculator';

const App: React.FC = () => {
  const [calcMode, setCalcMode] = useState<'sum' | 'product'>('sum');

  return (
    <Router basename="/app">
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/calc">Calculator</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/calc" element={<Calculator calcMode={calcMode} setCalcMode={setCalcMode} />} />
        </Routes>
      </div>
    </Router>
  );
};

export default App;

この修正により、<Router>/app/を基準としてルーティングを行います。例えば、<Link to="/">Home</Link>/app/にアクセスするためのリンクになります。

なるほど、ここにルートディレクトリを指定すればよいわけだ。

ということで、やってみたところ、イメージ通りの動作となった。

ということで、httpサーバーだけでwebアプリが動くようになった!

アプリのルートアドレスの指定 · st-func/st-func-web@6ee3f7a · GitHub

st-func.main.jp

Reactサンプルプログラムの作成

色々調べながら作ってもいいのだが、ChartGPTさんがいることだし、さくっとサンプルのアプリケーションを作ってもらおう。

断面性能を計算するアプリケーションをイメージして以下のようなアプリケーションを作ってもらう。

Reactを利用して、簡単な計算を実施するアプリを作成したいです。
indexページはタイトルとロゴを表示します。
ハンバーガーメニューでページを切り替えます。
calcページに移動すると以下の機能があります。
* 3つの数をinputフィールドに入力したらその和を表示します。
* 2つの数をinputフィールド入力したらその積を表示します。
* この2つの機能はメニューなどで切り替えることができます。
* 機能を切り替えると、inputのフィールドの数が適切に切り替わります。
以上のソースコードを提案してください。
ReactのプロジェクトはTypeScriptで作成しています。

以下回答

以下は、ReactとTypeScriptを使用して提案された機能を実装したソースコードの例です。

まず、プロジェクトのフォルダに移動して、必要なパッケージをインストールします。

cd your-project-folder
npm install react-router-dom

次に、以下のコードを含むApp.tsxファイルを作成します。

import React, { useState } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

// Components
import Home from './Home';
import Calculator from './Calculator';

const App: React.FC = () => {
  const [calcMode, setCalcMode] = useState<'sum' | 'product'>('sum');

  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/calc">Calculator</Link>
            </li>
          </ul>
        </nav>

        <Route path="/" exact component={Home} />
        <Route path="/calc" render={() => <Calculator calcMode={calcMode} setCalcMode={setCalcMode} />} />
      </div>
    </Router>
  );
};

export default App;

次に、Home.tsxファイルとCalculator.tsxファイルを作成します。

Home.tsx:

import React from 'react';

const Home: React.FC = () => {
  return (
    <div>
      <h1>Welcome to Calculator App</h1>
      <img src="logo.png" alt="Logo" />
    </div>
  );
};

export default Home;

Calculator.tsx:

import React, { useState } from 'react';

interface CalculatorProps {
  calcMode: 'sum' | 'product';
  setCalcMode: React.Dispatch<React.SetStateAction<'sum' | 'product'>>;
}

const Calculator: React.FC<CalculatorProps> = ({ calcMode, setCalcMode }) => {
  const [num1, setNum1] = useState('');
  const [num2, setNum2] = useState('');
  const [num3, setNum3] = useState('');
  const [result, setResult] = useState<number | undefined>(undefined);

  const handleCalculation = () => {
    if (calcMode === 'sum') {
      const sum = parseFloat(num1) + parseFloat(num2) + parseFloat(num3);
      setResult(sum);
    } else if (calcMode === 'product') {
      const product = parseFloat(num1) * parseFloat(num2);
      setResult(product);
    }
  };

  return (
    <div>
      <h2>Calculator</h2>
      <div>
        <label>Number 1:</label>
        <input type="number" value={num1} onChange={(e) => setNum1(e.target.value)} />
      </div>
      <div>
        <label>Number 2:</label>
        <input type="number" value={num2} onChange={(e) => setNum2(e.target.value)} />
      </div>
      {calcMode === 'sum' && (
        <div>
          <label>Number 3:</label>
          <input type="number" value={num3} onChange={(e) => setNum3(e.target.value)} />
        </div>
      )}
      <button onClick={handleCalculation}>Calculate</button>
      <div>
        Result: {result !== undefined ? result : 'Please calculate'}
      </div>
      <div>
        <button onClick={() => setCalcMode('sum')}>Calculate Sum</button>
        <button onClick={() => setCalcMode('product')}>Calculate Product</button>
      </div>
    </div>
  );
};

export default Calculator;

このコードでは、App.tsxでルーティングを設定し、ホームページと計算機能ページを切り替えることができます。Calculator.tsxでは、計算モードに応じて入力フィールドを制御し、和または積を計算する機能を実装しています。

また、calcModesetCalcModeをpropsとして受け取り、計算モードを切り替えるボタンを追加しています。

さすがである。

いわれたとおりに

$ npm install react-router-dom

とインストールして、コードをそのままコピペするとエラーが。

下記エラーが出ました。
ERROR in src/App.tsx:25:25
TS2322: Type '{ path: string; exact: true; component: FC<{}>; }' is not assignable to type 'IntrinsicAttributes & RouteProps'.
  Property 'exact' does not exist on type 'IntrinsicAttributes & RouteProps'.
    23 |         </nav>
    24 |
  > 25 |         <Route path="/" exact component={Home} />
       |                         ^^^^^
    26 |         <Route path="/calc" render={() => <Calculator calcMode={calcMode} setCalcMode={setCalcMode} />} />
    27 |       </div>
    28 |     </Router>

ERROR in src/App.tsx:26:29
TS2322: Type '{ path: string; render: () => Element; }' is not assignable to type 'IntrinsicAttributes & RouteProps'.
  Property 'render' does not exist on type 'IntrinsicAttributes & RouteProps'.
    24 |
    25 |         <Route path="/" exact component={Home} />
  > 26 |         <Route path="/calc" render={() => <Calculator calcMode={calcMode} setCalcMode={setCalcMode} />} />
       |                             ^^^^^^
    27 |       </div>
    28 |     </Router>
    29 |   );

以下回答

エラーメッセージによると、exactプロパティとrenderプロパティがRouteコンポーネントの型であるRoutePropsに存在しないためエラーが発生しています。これはReact Routerのバージョンによるものかもしれません。最新のReact Router v6ではexactプロパティは使用されず、Routeコンポーネントのpropsとして直接指定されるようになりました。

以下は、React Router v6に対応した修正したコードです。

import React, { useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';

// Components
import Home from './Home';
import Calculator from './Calculator';

const App: React.FC = () => {
  const [calcMode, setCalcMode] = useState<'sum' | 'product'>('sum');

  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/calc">Calculator</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/calc" element={<Calculator calcMode={calcMode} setCalcMode={setCalcMode} />} />
        </Routes>
      </div>
    </Router>
  );
};

export default App;

上記の修正により、Routeコンポーネントexactプロパティとrenderプロパティを使用せず、代わりにelementプロパティを使用するようになりました。これによりReact Router v6に対応したコードになります。

動いたぞ!本当にChatGPT半端ない・・・。

あとはイメージで

足し算と掛け算の切り替えをボタンではなく、コンボボックスで切り替えるようにしてください。

以下回答

コンボボックスを使用して足し算と掛け算の切り替えを実装するために、Calculator.tsxファイルの一部を変更します。以下が修正されたコードです。

import React, { useState } from 'react';

interface CalculatorProps {
  calcMode: 'sum' | 'product';
  setCalcMode: React.Dispatch<React.SetStateAction<'sum' | 'product'>>;
}

const Calculator: React.FC<CalculatorProps> = ({ calcMode, setCalcMode }) => {
  const [num1, setNum1] = useState('');
  const [num2, setNum2] = useState('');
  const [num3, setNum3] = useState('');
  const [result, setResult] = useState<number | undefined>(undefined);

  const handleCalculation = () => {
    if (calcMode === 'sum') {
      const sum = parseFloat(num1) + parseFloat(num2) + parseFloat(num3);
      setResult(sum);
    } else if (calcMode === 'product') {
      const product = parseFloat(num1) * parseFloat(num2);
      setResult(product);
    }
  };

  return (
    <div>
      <h2>Calculator</h2>
      <div>
        <label>Calculation Mode:</label>
        <select value={calcMode} onChange={(e) => setCalcMode(e.target.value as 'sum' | 'product')}>
          <option value="sum">Addition</option>
          <option value="product">Multiplication</option>
        </select>
      </div>
      <div>
        <label>Number 1:</label>
        <input type="number" value={num1} onChange={(e) => setNum1(e.target.value)} />
      </div>
      <div>
        <label>Number 2:</label>
        <input type="number" value={num2} onChange={(e) => setNum2(e.target.value)} />
      </div>
      {calcMode === 'sum' && (
        <div>
          <label>Number 3:</label>
          <input type="number" value={num3} onChange={(e) => setNum3(e.target.value)} />
        </div>
      )}
      <button onClick={handleCalculation}>Calculate</button>
      <div>
        Result: {result !== undefined ? result : 'Please calculate'}
      </div>
    </div>
  );
};

export default Calculator;

修正した部分では、labelselect要素を使用してコンボボックスを表示し、選択した計算モードをsetCalcMode関数を介して更新するようにしました。これにより、ボタンではなくコンボボックスで足し算と掛け算の切り替えが行えるようになります。

ばっちりだ。10分かからずにサンプルアプリが完成してしまった。 驚きを通り越して怖くなってくるレベル・・・。

さて、サンプルプログラムができたので、中身を紐解きながらReactの勉強をしていこう。

Sampleアプリケーションの作成 · st-func/st-func-web@1491d65 · GitHub

フロントエンドウェブアプリ

ウェブアプリを開発してみたい

st-func-tsを別のライブラリにしているが、現状特に分けている意味があまりない。

また、Excelへのアドインインストールがサイドロードしかできない。

なんとなくもったいないので、せっかく作ったライブラリを活用できるようにウェブアプリを作ってみようと思う。

そういえば、昔構造計算用のAndroidアプリを作ろうと試みたことがあったが、ウェブアプリならどの端末でも動くからそっちの方が使いやすいのかな?

現在使っているロリポップはただのウェブサーバーでバックエンドは動かないので、フロントエンドだけで動くプログラムにしてみたい。

フロントエンドだけで完結させれば、ページをダウンロードすればオフラインでも動くのかしら?とりあえず試してみよう。

何のフレームワークを使うか

ウェブアプリはdjangoとflaskは触ったことがあるが、javascriptとフロントエンドの開発は初めてなので、まず調べるところから。

qiita.com

なんとなくこの辺のサイトが分かりやすい?

ReactとVue.jsというのがあるようだ。

上記サイトのおすすめなのと、シェアが大きいということでReactでやってみることにする。

まあ、そんなに複雑なものを作るつもりはないし、簡単なら何でもよい。

プロジェクトの作成

qiita.com

この辺を眺めながらやってみることとする。

Node.jsは既にインストール済なので、いきなりプロジェクトを作成する。

$ npx create-react-app st-func-web --template typescript
Need to install the following packages:
create-react-app@5.0.1
Ok to proceed? (y) 

インストールするか聞かれるので、yで進むとアプリの作成が始まった。

インストールが完了したが、

You had a `README.md` file, we renamed it to `README.old.md`

と表示された。あら、親切ね。

いろいろとファイルができた。

ここで

$ npm start

とすると、Reactのアイコンがぐるぐるするページが表示された。アプリの作成はうまくいったようだ。

GitHub - st-func/st-func-web at eada04a74e977f07a8ec257623f3f51fd0b064f2

結局サーバーが必要?

動いたものの、普通にhtmlを開くだけだとアプリが起動しない。これは結局バックエンドで何か動いていないと動かないのかしら?