ST_FUNC開発日記

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

WEBアプリで他の断面性能も計算する仕組みを作る

ファイル名の修正他

とりあえずCalculatorのままだといまいちなので、ファイル名やコンポーネント名などを修正

断面性能計算のクラス名を修正 · st-func/st-func-web@d164fcd · GitHub

ほかの断面性能

現在断面積の結果しか表示されないが、ほかの断面性能も計算していきたい。

最初に考えたのは

const [area, setArea] = useState<number | undefined>(undefined);
const [massPerMetre, setMassPerMetre] = useState<number | undefined>(undefined);

というように大量に変数を作って処理してみようと思ったのだが、ちょっと書くのがめんどそうである。

そこで、

const [result, setResult] = useState<string | undefined>(undefined);

というのを作って、そこに結果のhtmlを書き込んでしまえばいいのではないかということにした。

結果部分はひたすらテキストで書いていくとちょっと見にくそうなので、表にしてしまいたい。

ということは、コンポーネントを使えばいいに違いない。

ということでChatGPTさんに相談

reactでfor分でtableのtrを作っていくにはどう書けばよいですか?

分は文のたいぽ。

javascriptのコードになってしまったので

言語はtypescriptです

を追記。

TypeScriptを使用して同じ動作を実現する場合は、コンポーネントのpropsやstateにデータを保持し、それを利用して動的にtableを生成することができます。以下はその例です。

import React from 'react';

interface User {
  id: number;
  name: string;
  age: number;
}

interface Props {
  data: User[];
}

const UserTable: React.FC<Props> = ({ data }) => (
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Age</th>
      </tr>
    </thead>
    <tbody>
      {data.map((user) => (
        <tr key={user.id}>
          <td>{user.id}</td>
          <td>{user.name}</td>
          <td>{user.age}</td>
        </tr>
      ))}
    </tbody>
  </table>
);

export default UserTable;

このコンポーネントはUser型の配列を受け取り、そのデータを元に動的にtableのtr要素を生成します。map関数を使ってデータ配列を走査し、各要素に対してtr要素を生成しています。また、キー(key)属性を設定してReactに一意性を伝えています。これを利用する側では、適切なデータをpropsとして渡すことでこのコンポーネントを利用できます。

ということで、上記を参考に断面性能計算結果の表を作る。

interface CalcData {
  secPropertyType: SecPropertyType;
  symbol: string;
  description: string;
  unit: string;
  result: number | undefined;
}

interface CalcDataProps {
  calcDatas: CalcData[];
}
const ResultTable: React.FC<CalcDataProps> = ({ calcDatas }) => (
  <table>
    <tbody>
      {calcDatas.map((calcData) => (
        <tr>
          <td>{calcData.description}</td>
          <td>{calcData.symbol}</td>
          <td>=</td>
          <td>
            {calcData.result}
            {calcData.unit}
          </td>
        </tr>
      ))}
    </tbody>
  </table>
);

このコンポーネントを使ってresultにhtmlをセットしようと思ったのだけど、なかなかうまくいかなかった。

ということで、コンポーネントではなく、CalcDataの配列のみを渡すことにした。

なんやかやで、こんな感じに。

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

interface CalcData {
  secPropertyType: SecPropertyType;
  symbol: string;
  description: string;
  unit: string;
  result: number | undefined;
}

interface CalcDataProps {
  calcDatas: CalcData[];
}
const ResultTable: React.FC<CalcDataProps> = ({ calcDatas }) => (
  <table>
    <tbody>
      {calcDatas.map((calcData) => (
        <tr>
          <td>{calcData.description}</td>
          <td>{calcData.symbol}</td>
          <td>=</td>
          <td>
            {calcData.result}
            {calcData.unit}
          </td>
        </tr>
      ))}
    </tbody>
  </table>
);

const SecProperty: React.FC = () => {
  const [calcMode, setCalcMode] = useState<"buildBox" | "flatBar">("flatBar");
  const [num1, setNum1] = useState("");
  const [num2, setNum2] = useState("");
  const [num3, setNum3] = useState("");
  const [num4, setNum4] = useState("");
  const [result, setResult] = useState<CalcData[] | 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 calcDatas: CalcData[] = [
      {
        secPropertyType: SecPropertyType.Area,
        symbol: "A",
        description: "断面積",
        unit: "mm^2",
        result: undefined,
      },
      {
        secPropertyType: SecPropertyType.MassPerMetre,
        symbol: "m",
        description: "単位質量",
        unit: "kg/m",
        result: undefined,
      },
    ];
    for (let calcData of calcDatas) {
      calcData.result = Unit.output(
        secSteel.property(calcData.secPropertyType),
        calcData.unit
      );
    }
    setResult(calcDatas);
  };

  return (
    <div>
      <h2>断面性能計算</h2>
      <h3>断面寸法</h3>
      <div>
        <label>断面タイプ:</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}>計算</button>
      <div>
        <h3>計算結果</h3>
        {result !== undefined ? (
          <ResultTable calcDatas={result} />
        ) : (
          "寸法を入力してください"
        )}
      </div>
    </div>
  );
};

export default SecProperty;

ここで、コードはあっていそうだけど下記のようなエラーが出ている。

Cannot access ambient const enums when 'isolatedModules' is enabled.

インポートはできているのに何でエラーになるのかな? ということでChatGPTに相談。

/**
 * 断面性能のタイプ
 */
export const enum SecPropertyType {
  Area = "断面積",
  ElasticModulusX = "断面係数X",
  ElasticModulusY = "断面係数Y",
  MassPerMetre = "単位質量",
  RadiusOfGyrationX = "断面二次半径X",
  RadiusOfGyrationY = "断面二次半径Y",
  SecondMomentOfAreaX = "断面二次モーメントX",
  SecondMomentOfAreaY = "断面二次モーメントY",
}

これをnpmでインストールしてほかのパッケージで呼び出したところ
Cannot access ambient const enums when 'isolatedModules' is enabled.
というエラーが出ました。なぜでしょうか。

このエラーは、TypeScriptでisolatedModulesオプションを有効にしている場合に、環境でambient const enums を利用できないことが原因です。isolatedModulesオプションは、各ファイルを個別にコンパイルするためのものであり、ambient const enums は型情報のみを生成するため、コードが生成されません。そのため、isolatedModulesオプションとambient const enumsを同時に使うことはできません。

このエラーを解決するためには、次のいずれかの方法を選択することができます。

  1. isolatedModulesオプションを無効にする。
  2. enumをconst enumではなく通常のenumに変更する。
  3. enumを外部モジュールとして定義して、それをインポートする。

具体的な対応方法を教えてもらえると、どの方法が適切か判断できますか?

2はどうすればよいのですか?

enumを通常のenumに変更するためには、constキーワードを取り除くだけです。つまり、以下のようにenumを修正します。

/**
 * 断面性能のタイプ
 */
export enum SecPropertyType {
  Area = "断面積",
  ElasticModulusX = "断面係数X",
  ElasticModulusY = "断面係数Y",
  MassPerMetre = "単位質量",
  RadiusOfGyrationX = "断面二次半径X",
  RadiusOfGyrationY = "断面二次半径Y",
  SecondMomentOfAreaX = "断面二次モーメントX",
  SecondMomentOfAreaY = "断面二次モーメントY",
}

これにより、ambient const enums を使用しなくなり、isolatedModulesオプションが有効な状態でもエラーが発生しなくなります。

なるほど。constを付けたせいでエラーになっていたのかな? でも、なんかenumjavascriptの互換性からconstつけた方がいいとどっかの記事で見たような・・・。といってもtypescriptでやっている以上使い勝手が悪いので、constは消すことにした。

SecPropertyTypeをconst enumから通常のenumへ修正 · st-func/st-func-ts@1f2fb24 · GitHub

これでうまく動いた!

断面性能計算に単位質量を追加 · st-func/st-func-web@8fb1837 · GitHub