經過思考後,我決定配合專題的技術使用 react
,所以打算把正個網頁都改成使用react
語言去撰寫。邊做邊學。
npm create vite@latest my-app
cd my-app
npm install
npm run dev
(相片)
在資料夾找到 my-app / src / App.jsx,打開後從裡面修改後儲存點擊:http://localhost:5173/,就可以即時看到頁面上的變化。再來要開始搬移頁面
這個頁面原本的功能有:
localStorage
讀取 records
先安裝會用到的套件
npm i chart.js
將路徑變成:src / pages / Report.jsx ( pages 是資料夾,Report.jsx 為檔案 )
將 report.html 轉換成 Report.jsx,因為這是第一個轉換的頁面,所以會把程式碼貼上來,邊解析邊學習,之後轉換的頁面會直接放上成果。
引入 React Hooks & Chart.js
import { useEffect, useMemo, useRef, useState } from "react";
useState
→ 我想要「記住一個值,改了會刷新畫面」。useEffect
→ 我想要「畫面出現或值改了,就順便做某些事」。useRef
→ 我想要「存東西在抽屜裡,不會因刷新丟失」。useMemo
→ 我想要「計算過的結果,不要白白重算」。註冊 Chart.js 元件
import {
Chart,
LineController,
LineElement,
PointElement,
LinearScale,
CategoryScale,
Title,
Tooltip,
Legend,
} from "chart.js";
讓 Chart.js 知道「要用折線圖」並啟用需要的元件。
小工具函式:血糖狀態判斷
function getGlucoseStatus(v) {
const val = parseInt(v, 10);
if (isNaN(val)) return "未知";
if (val < 70) return "過低";
if (val <= 140) return "正常";
if (val <= 200) return "偏高";
return "過高";
}
輸入一個血糖數字 → 回傳「過低、正常、偏高、過高」,這邊寫法雖然跟 HTML 一樣,但:
tbody.appendChild(row)
{getGlucoseStatus(...)}
,React 來更新畫面utils.js
,任何元件 import 就能用,不會互相干擾。紀錄狀態管理
const [records, setRecords] = useState([]);
useEffect(() => {
const raw = localStorage.getItem("records");
try {
setRecords(raw ? JSON.parse(raw) : []);
} catch {
setRecords([]);
}
}, []);
const [records, setRecords] = useState([]);
records
。[]
(因為還沒讀資料)。setRecords
就是「拿筆把白板改掉」的工具。setRecords(新資料)
,React 會自動重畫畫面,表格就會出現資料。useEffect(() => { ... }, []);
[]
代表:只做一次(元件第一次出現在畫面後)。const raw = localStorage.getItem("records");
getItem("records")
會拿到你以前 setItem("records", "....")
存的那串字。null
。setRecords(raw ? JSON.parse(raw) : []);
raw
有值,就用 JSON.parse(raw)
把字串變回陣列/物件。raw
沒值(是 null
),那就用 []
(表示沒有任何紀錄)。setRecords(...)
一做,畫面會重渲染,你的表格會從「空」變成「有資料」。catch { setRecords([]); }
如果有一天 localStorage
裡被塞進去的不是合法 JSON(例如被手動改壞了),
JSON.parse
會報錯。
我們用 try/catch
把它擋住,保底就直接用 []
,避免整個頁面掛掉。
}, []);
(依賴陣列是空的)
records
先是 []
→ 畫面可能顯示「目前沒有紀錄」setRecords(真的資料)
紀錄轉成圖表資料
const { labels, bfData, afData } = useMemo(() => {
const labels = records.map((r) => r.date);
const bfData = records.map((r) => Number(r.bf_glucose) || null);
const afData = records.map((r) => Number(r.af_glucose) || null);
return { labels, bfData, afData };
}, [records]);
labels
:橫軸的日期bfData
:餐前血糖陣列afData
:餐後血糖陣列useMemo
,只有當 records
改變時才重新計算,提升效能。畫 Chart.js 圖表
const canvasRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
if (!canvasRef.current) return;
// 如果之前有圖 → 銷毀,避免重疊
if (chartRef.current) {
chartRef.current.destroy();
chartRef.current = null;
}
if (labels.length === 0) return;
const ctx = canvasRef.current.getContext("2d");
chartRef.current = new Chart(ctx, {
type: "line",
data: {
labels,
datasets: [
{ label: "餐前血糖", data: bfData, borderColor: "blue", ... },
{ label: "餐後血糖", data: afData, borderColor: "red", ... },
],
},
options: { ...圖表設定... },
});
return () => {
if (chartRef.current) {
chartRef.current.destroy();
chartRef.current = null;
}
};
}, [labels, bfData, afData]);
canvasRef
:對應 <canvas>
元素,拿到 2D 繪圖 contextchartRef
:存 Chart 實例,方便在 re-render 或卸載時銷毀labels
、bfData
、afData
有變 → 重新畫圖畫面結構
<nav>...</nav>
<h1>糖尿病病患紀錄表</h1>
<table>... 表格顯示每筆紀錄 ...</table>
<div>
<canvas ref={canvasRef} />
</div>
Link
)records.map
把每一筆紀錄畫成 <tr>
把路由接上
App.jsx
使用 react-router-dom
,把 /report
指到這個元件:
( 剛剛寫的 Report.jsx 就是元件 )
// src/App.jsx
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Report from "./pages/Report";
export default function App() {
return (
<>
{/* 可以放自己的 NavBar 元件 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/report" element={<Report />} />
</Routes>
</>
);
}
不過在這之前還有其他事要做。
npm install react-router-dom
Home.jsx
export default function Home() {
return (
<div style={{ padding: "16px" }}>
<h1>糖尿病管理系統</h1>
<p>這是我的第一個 React 頁面 🚀</p>
<a href="/report">查看紀錄</a>
</div>
)
}
App.jsx
改成 → 導入 Routes
import { Routes, Route, Link } from "react-router-dom"
import Home from "./pages/Home"
import Report from "./pages/Report"
function App() {
return (
<>
{/* 導覽列 */}
<nav style={{ display: "flex", gap: "12px", padding: "12px" }}>
<Link to="/">首頁</Link>
<Link to="/report">紀錄表</Link>
</nav>
{/* 路由規則 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/report" element={<Report />} />
</Routes>
</>
)
}
export default App
main.jsx
→ 包 BrowserRouterimport React from "react"
import ReactDOM from "react-dom/client"
import { BrowserRouter } from "react-router-dom"
import App from "./App"
import "./index.css"
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)