🧮
【React × TypeScript】医療系アプリで学ぶ基準値UI設計:③ 型安全に基準値を扱う!比較ロジックを安全設計する方法
📘 はじめに
Next.js + TypeScript + Tailwind CSSを学習しながら、
看護現場で役に立ちそうな「看護師向け計算ツールアプリ」の開発を進めております。
このアプリでは、計算結果を基準値(正常範囲)と照らし合わせて
「高い」「低い」を自動で判定します👇
🔺135より高ければ赤矢印
🔻135より低ければ青矢印
しかし初期実装では、こんな問題が発生しました。
- 数値が文字列のまま比較される
-
nullやundefinedでNaNが出る - 基準値の型が合わずにバグが出る
この記事では、これらをTypeScriptで型安全に扱う方法を解説します。
💡 比較の落とし穴
たとえば単純に以下のように書いていたとします。
if (value > range.max) return "高い";
ここで value が "140"(文字列)だった場合……
"9" > "100"; // true 😱(文字列比較)
JavaScriptでは暗黙の型変換で誤った比較が起きることがあります。
医療アプリのように「正確な数値」が前提の場面では致命的です。
⚙️ 型安全な比較ロジックを設計する
① Range型(基準値)
export type Range = {
min: number;
max: number;
};
② 比較関数
export const compareValueToRange = (value: unknown, range?: Range) => {
if (range === undefined) return "N/A";
// 1. 数値変換
const numericValue = parseFloat(String(value));
// 2. 数値でない場合は安全に終了
if (isNaN(numericValue)) return "invalid";
// 3. 比較処理
if (numericValue > range.max) return "high";
if (numericValue < range.min) return "low";
return "normal";
};
✅ ポイント
| 工夫 | 内容 |
|---|---|
| unknown | 外部入力値(文字列や空値)を安全に受け取れる |
| parseFloat(String(value)) | すべて文字列化して数値変換 |
| isNaN() チェック | 不正値を除外 |
| range? | オプショナル型で安全に比較 |
🧠 型ガードでさらに安全に
もし value が文字列・数値・nullなど混在するケースが多い場合、
型ガード関数を使うとさらに堅牢になります。
export const isNumeric = (value: unknown): value is number => {
return typeof value === "number" && !isNaN(value);
};
そして比較時に使う:
if (!isNumeric(value)) {
console.warn("数値ではありません:", value);
return "invalid";
}
📊 実用例:ResultBox で使う
import { compareValueToRange } from "@/lib/compareValueToRange";
import { getStatusIcon } from "@/lib/getStatusIcon";
import { normalRanges } from "@/config/normalRanges";
const range = normalRanges.potassium;
const resultType = compareValueToRange(value, range);
return (
<div className="flex items-center gap-2">
<span className="text-lg">{value}</span>
{getStatusIcon(value, range)}
{resultType === "invalid" && (
<span className="text-gray-400 text-sm">(値が不明です)</span>
)}
</div>
);
🧪 テスト例(Vitest / Jest)
test("compareValueToRange works correctly", () => {
const range = { min: 3.5, max: 5.0 };
expect(compareValueToRange(3.0, range)).toBe("low");
expect(compareValueToRange(4.0, range)).toBe("normal");
expect(compareValueToRange(6.0, range)).toBe("high");
expect(compareValueToRange("abc", range)).toBe("invalid");
});
テストを追加することで、バリデーションの信頼性が高まります。
🩺 医療UIの安全設計ポイント
| 観点 | 工夫 |
|---|---|
| 想定外入力 | 空欄・文字列・nullを安全に無視 |
| 型安全性 | unknown → number変換で堅牢化 |
| 戻り値 | `"high" |
| 再利用 | ResultBox/履歴判定など複数箇所で共通利用 |
🧠 まとめ
| 項目 | 内容 |
|---|---|
| ⚙️ 比較関数 | compareValueToRange() で安全に比較 |
| 🧱 型ガード | isNumeric() で数値検証 |
| 🩺 安全設計 | 不正値を安全にスルー |
| 🧩 ライブラリ判断 | 小規模なら型ガード、大規模入力ならZod系 |
| 🧪 信頼性 | テスト追加で安全性を担保 |
🔗 シリーズ案内
🩺 【React × TypeScript】医療系アプリで学ぶ基準値UI設計シリーズ
1.基準値より高い・低いを矢印で表示する方法
2.normalRanges.tsで基準値を一元管理する設計パターン
3.型安全に基準値を扱う!比較ロジックを安全設計する方法 ←(この記事)
4.見やすさ重視のUI設計!Tailwindで基準値判定をわかりやすく表示する
Discussion