第三章:義大利麵條程式碼大掃除:讓 AI 幫你重構與拆分 React 元件
在 Vibe Coding 的過程中,我們常常為了求快,不斷請 AI 把新功能塞進同一個檔案裡。漸漸地,你的 page.tsx 可能會膨脹到 800 行甚至 1500 行以上。這時候,AI 開始變得越來越笨,甚至改了 A 壞了 B。
這種亂七八糟、互相牽連的程式碼,我們稱為「義大利麵條程式碼 (Spaghetti Code)」。本章將教你如何指揮 AI,安全地幫你進行「程式碼重構 (Refactoring)」與「元件拆分」。
為什麼檔案太大會讓 AI 變笨?
- Context Window 浪費:每次對話,AI 都需要閱讀整個檔案。1500 行的檔案會消耗大量 Token,導致 AI「忘記」你最初的需求。
- 修改風險極高:一個檔案裡塞滿了 Header, Sidebar, Main Content, Footer。請 AI 改 Footer 的時候,它可能不小心動到了 Header 的狀態 (State)。
- 人類看不懂:Vibe Coding 不代表人類不用看 Code。當檔案大到你無法掌握時,你就失去了對專案的控制權。
重構的黃金法則:單一職責原則 (SRP)
在請 AI 拆分元件前,你必須先在腦海中建立「單一職責」的概念。 一個 React 元件應該只負責「一件事情」。
例如,一個 PricingPage 不應該同時負責:
- 取得資料庫的價格方案
- 渲染方案卡片的 UI
- 處理結帳按鈕的 Stripe API 邏輯
- 渲染常見問題 (FAQ) 手風琴
它應該被拆分成:
PricingPage(只負責組合)PricingCard(只負責渲染卡片 UI)CheckoutButton(只負責金流邏輯)FaqSection(只負責 FAQ 邏輯)
實戰:如何詠唱「拆分元件」的 Prompt
請不要直接對 AI 說:「幫我把這個檔案拆小一點」。這太模糊了,AI 可能會拆出讓你匪夷所思的結構。
請使用以下這個重構專用 Prompt 模板:
[任務目標] 目前的
src/app/pricing/page.tsx檔案太長了,我希望進行重構與元件拆分,以提升可維護性。[拆分規劃] 請幫我將程式碼拆分成以下三個檔案:
src/components/pricing/PricingCard.tsx:負責渲染單一方案卡片的 UI,需要接收tier(方案資料) 作為 Props。src/components/pricing/FaqSection.tsx:負責渲染底部的常見問題,並包含手風琴的開關狀態 (useState)。src/app/pricing/page.tsx:保持為主頁面,負責引入上述兩個元件,並保留資料夾取的邏輯。[執行要求]
- 請一步一步來,先提供
PricingCard.tsx的完整程式碼。- 確保 TypeScript 的 Interface / Type 定義正確匯出並共用。
- 確保原有的 Tailwind 樣式與動畫完全不變。
拆分過程中的地雷防範
當 AI 開始給你拆分後的程式碼時,請注意以下幾點:
1. Props 的傳遞 (Prop Drilling)
當你把元件拆出去後,原本在主頁面裡的變數(例如 const [isLoading, setIsLoading] = useState(false)),子元件會拿不到。AI 應該要透過 Props 把這些變數傳進去。如果 AI 漏掉了,請提醒它:「記得把 isLoading 作為 prop 傳給 PricingCard」。
2. 絕對路徑 vs 相對路徑
拆分到 src/components/ 後,原本檔案裡的 import 路徑可能會錯。
檢查 AI 寫的 import:
✅ import { AlertCircle } from "lucide-react";
✅ import { cn } from "@/lib/utils";
❌ import { cn } from "../../lib/utils"; (在 Next.js 中,盡量使用 @/ 絕對路徑)
3. 一次只做一件事
絕大規則:千萬不要在「重構」的同時「新增功能」! 如果你想把按鈕拆出去,順便加上 Loading 旋轉動畫,請分兩步做。 第一步:拆分。測試會不會動。 第二步:加動畫。 這能確保你隨時可以 Git Commit 退回安全點。
定期大掃除的好處
只要你養成習慣,當一個檔案超過 300 行時,就請 AI 幫你做一次「元件拆分」。你會發現:
- AI 寫程式的速度變快了(因為 Token 變少了)。
- Bug 變少了(因為狀態被隔離在各自的元件中)。
- 你自己看 Code 的心情變好了。
這就是 Vibe Coding 能夠走得長遠、維護大型商業專案的終極秘訣!
🧹 程式碼重構的實際案例
案例:將長函數拆分為小函數
重構前(一個函數做太多事):
// ❌ 一個函數 80 行,同時處理 API 請求、資料轉換、UI 渲染
function ProductPage() {
const [products, setProducts] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch("/api/products")
.then(r => r.json())
.then(data => {
const filtered = data.filter(p => p.active)
const sorted = filtered.sort((a, b) => b.price - a.price)
setProducts(sorted)
setLoading(false)
})
.catch(err => { setError(err.message); setLoading(false) })
}, [])
if (loading) return <Spinner />
if (error) return <Error msg={error} />
return <ProductGrid items={products} />
}
重構後(每個函數只做一件事):
// ✅ 職責分離:自訂 Hook 處理資料
function useProducts() {
const { data, loading, error } = useSWR("/api/products", fetcher)
const activeProducts = useMemo(
() => data?.filter(p => p.active).sort((a, b) => b.price - a.price) ?? [],
[data]
)
return { products: activeProducts, loading, error }
}
// ✅ UI 元件只負責渲染
function ProductPage() {
const { products, loading, error } = useProducts()
if (loading) return <Skeleton />
if (error) return <Alert type="error" message={error} />
return <ProductGrid items={products} />
}
重構原則
| 原則 | 說明 | 如何請 AI 協助 | |------|------|---------------| | 單一職責 | 一個函數/元件只做一件事 | 「幫我將這個 useEffect 的邏輯獨立成自訂 Hook」 | | DRY | 不要重複自己 | 「這段程式碼在三個檔案中重複了,幫我提取成共用函數」 | | 早回傳 | 避免巢狀 if-else | 「幫我將巢狀條件改為 Guard Clause 風格」 | | 命名清晰 | 變數/函數名稱說明意圖 | 「幫我檢查並改善這些變數名稱的可讀性」 |
關鍵要點
- ✅ Vibe Coding = 用自然語言描述需求,AI 生成程式碼
- ✅ 精準的 Prompt = 角色 + 目標 + 格式 + 約束
- ✅ AI 鬼打牆時:打斷重來 > 繼續糾纏
- ✅ .cursorrules / CLAUDE.md 是規範 AI 行為的關鍵
- ✅ Terminal 報錯訊息是 AI 除錯最重要的線索
Prompt 框架
角色:你是一個 [資深 Python 工程師]
目標:[幫我建立一個 FastAPI 使用者 CRUD API]
格式:[回傳完整的 Python 程式碼]
約束:[使用 Pydantic v2, SQLAlchemy async, PostgreSQL]
📊 程式碼品質指標
重構不只是「把程式碼變短」,而是讓程式碼更容易理解、維護和測試。以下是幾個客觀指標:
| 指標 | 壞程式碼 | 好程式碼 | |------|---------|---------| | 函數行數 | > 50 行 | < 20 行 | | 巢狀深度 | > 3 層 if/for | < 2 層 | | 參數數量 | > 4 個 | < 3 個 | | 全域狀態 | 直接修改 | 透過 Context/Store | | 錯誤處理 | 吞錯誤 (空的 catch) | 明確的錯誤訊息 | | 命名 | a, b, c, data, temp | 有意義的名稱 |
如何用 AI 做 Code Review
「請幫我 review 以下這段程式碼,針對以下幾點給建議:
1. 是否有可以提取成自訂 Hook 的邏輯?
2. 是否有可以簡化的條件判斷?
3. 是否有潛在的效能問題(不必要的 re-render)?
4. TypeScript 型別是否正確?」
瀏覽器 DevTools:前端除錯的核心武器
Elements 面板
Elements 面板顯示網頁的 DOM 結構和 CSS 樣式。你可以直接在面板中修改 HTML 和 CSS——即時看到效果,不用重新整理頁面。
Console 面板
Console 是 JavaScript 的互動式執行環境:
// 輸出除錯資訊
console.log('變數值:', variable);
console.table(array); // 用表格顯示陣列
console.time('label'); // 計時開始
// ... 要測量的程式碼
console.timeEnd('label'); // 計時結束,顯示耗時
// 中斷點效果
console.trace('誰呼叫了這裡?');
Network 面板
Network 面板記錄所有網路請求——API 呼叫、圖片載入、CSS/JS 檔案。你可以看到:請求 URL、HTTP 方法、狀態碼、回應時間、請求/回應內容。
Sources 面板
Sources 是 JavaScript 除錯的主戰場——設定中斷點、逐步執行程式碼、觀察變數變化。
下一章預告:API 除錯
DevTools 解決前端問題。下一章的 API 除錯解決前後端溝通的問題。