今天的進度是完成一個「打字訓練工具」,這次我想做一個不依賴外部 API、能夠直接在瀏覽器裡互動的小功能。
//TypingTrainer.jsx
import { useState, useEffect } from "react"
export default function TypingTrainer() {
const sentences = [
"The quick brown fox jumps over the lazy dog.",
"Typing fast takes practice and patience.",
"Code is like humor. When you have to explain it, it’s bad.",
"Practice makes perfect.",
"Never stop learning new things.",
]
const [currentSentence, setCurrentSentence] = useState("")
const [userInput, setUserInput] = useState("")
const [startTime, setStartTime] = useState(null)
const [accuracy, setAccuracy] = useState(0)
const [wpm, setWpm] = useState(0)
const [finished, setFinished] = useState(false)
useEffect(() => {
newSentence()
}, [])
const newSentence = () => {
const random = sentences[Math.floor(Math.random() * sentences.length)]
setCurrentSentence(random)
setUserInput("")
setAccuracy(0)
setWpm(0)
setFinished(false)
setStartTime(null)
}
const handleChange = (e) => {
const value = e.target.value
setUserInput(value)
if (!startTime) setStartTime(Date.now())
const correctChars = currentSentence
.split("")
.filter((ch, i) => ch === value[i]).length
const acc = Math.round((correctChars / currentSentence.length) * 100)
setAccuracy(acc)
if (value === currentSentence) {
const elapsed = (Date.now() - startTime) / 1000 / 60
const words = currentSentence.split(" ").length
setWpm(Math.round(words / elapsed))
setFinished(true)
}
}
return (
<div className="space-y-4">
<div className="bg-white shadow p-6 rounded">
<p className="text-gray-700 text-lg mb-3">請輸入以下句子:</p>
<p className="font-mono bg-gray-100 p-3 rounded">{currentSentence}</p>
<textarea
value={userInput}
onChange={handleChange}
disabled={finished}
className="w-full border p-3 mt-3 rounded focus:ring focus:ring-blue-300"
rows="3"
placeholder="開始打字..."
/>
<div className="mt-4 flex items-center justify-between text-gray-700">
<p>🎯 正確率:{accuracy}%</p>
<p>⚡ 速度:{wpm} WPM</p>
</div>
{finished && (
<div className="mt-4 text-center">
<p className="text-green-600 font-semibold">✅ 完成!</p>
<button
onClick={newSentence}
className="mt-2 bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
再來一句
</button>
</div>
)}
</div>
</div>
)
}
再來更改我們原本的layout讓它可以顯示出來
//layout.jsx
import { useState } from "react"
import TodoApp from "./TodoApp" // Day11 做好的待辦清單
import ChatBox from "./ChatBox" // Chat 工具
import TranslationTool from "./TranslationTool" // 翻譯工具
import TypingTrainer from "./TypingTrainer" // 👈 新增打字訓練工具
export default function Layout() {
const [active, setActive] = useState("todo")
return (
<div className="min-h-screen flex">
{/* 側邊選單 */}
<aside className="w-64 bg-gray-800 text-white p-6 space-y-4">
<h2 className="text-xl font-bold mb-6">AI工具箱</h2>
<nav className="space-y-2">
<button
className={`block w-full text-left px-3 py-2 rounded ${
active === "todo" ? "bg-blue-600" : "hover:bg-gray-700"
}`}
onClick={() => setActive("todo")}
>
待辦清單
</button>
<button
className={`block w-full text-left px-3 py-2 rounded ${
active === "chat" ? "bg-blue-600" : "hover:bg-gray-700"
}`}
onClick={() => setActive("chat")}
>
Chat工具
</button>
<button
className={`block w-full text-left px-3 py-2 rounded ${
active === "translate" ? "bg-blue-600" : "hover:bg-gray-700"
}`}
onClick={() => setActive("translate")}
>
翻譯工具
</button>
<button
className={`block w-full text-left px-3 py-2 rounded ${
active === "typing" ? "bg-blue-600" : "hover:bg-gray-700"
}`}
onClick={() => setActive("typing")}
>
打字訓練
</button>
</nav>
</aside>
{/* 主內容區 */}
<main className="flex-1 bg-gray-50 p-6">
{active === "todo" && (
<>
<h1 className="text-2xl font-bold mb-4">待辦清單</h1>
<TodoApp />
</>
)}
{active === "chat" && (
<>
<h1 className="text-2xl font-bold mb-4">Chat 工具</h1>
<ChatBox />
</>
)}
{active === "translate" && (
<>
<h1 className="text-2xl font-bold mb-4">翻譯工具</h1>
<TranslationTool />
</>
)}
{active === "typing" && (
<>
<h1 className="text-2xl font-bold mb-4">打字訓練</h1>
<TypingTrainer />
</>
)}
</main>
</div>
)
}
這次功能的主要目的是讓我練習前端事件控制(onChange、useEffect、狀態更新)、即時運算與 UI 反饋,讓下次可以來嘗試讓句子可以「中英文切換」或「由使用者自訂內容」。