📦 前端工具鏈與框架概覽
📌 什麼是前端工具鏈?
比喻:前端工具鏈就像工廠的生產線 🏭
想像你要大量生產家具(網頁應用程式),不可能全部手工打造。 你需要一條生產線:原料進貨系統(npm)、加工機台(Bundler)、 品質檢測站(Linter/TypeScript)、包裝出貨(Build)。 前端工具鏈就是這整條生產線,讓開發更有效率。
📌 npm / Node.js 基礎
# Node.js 是 JavaScript 的執行環境,npm 是套件管理工具
# 就像 C# 的 NuGet,npm 讓你下載和管理第三方套件
# 初始化專案(建立 package.json)
npm init -y # -y 表示全部使用預設值,快速建立
# 安裝套件
npm install axios # 安裝 axios 套件(HTTP 請求工具),加到 dependencies
npm install -D typescript # 安裝 TypeScript,加到 devDependencies(-D 是開發時依賴)
# package.json 的重要欄位
# {
# "name": "my-app", // 專案名稱
# "version": "1.0.0", // 版本號
# "scripts": { // 自訂指令(像是 Makefile)
# "dev": "vite", // npm run dev → 啟動開發伺服器
# "build": "vite build", // npm run build → 建置正式版
# "preview": "vite preview" // npm run preview → 預覽建置結果
# },
# "dependencies": { // 正式環境需要的套件
# "axios": "^1.6.0" // ^ 表示允許自動升級次要版本
# },
# "devDependencies": { // 只有開發時需要的套件
# "typescript": "^5.3.0" // TypeScript 編譯器
# }
# }
# 常用 npm 指令
npm install # 根據 package.json 安裝所有依賴(別人拉專案後第一件事)
npm update # 更新所有套件到最新允許的版本
npm run dev # 執行 scripts 裡定義的 dev 指令
npm list --depth=0 # 列出目前安裝的所有頂層套件
📌 Bundler 概念(Webpack vs Vite)
// Bundler 的作用:把多個 JavaScript 檔案打包成少數幾個檔案
// 就像把一堆零件組裝成成品,方便運送(部署)
// === Webpack(老牌,功能完整,設定複雜) ===
// webpack.config.js 範例
module.exports = { // 匯出 Webpack 設定物件
entry: "./src/index.js", // 進入點:從哪個檔案開始打包
output: { // 輸出設定
filename: "bundle.js", // 打包後的檔案名稱
path: __dirname + "/dist" // 輸出到 dist 資料夾
},
module: { // 模組處理規則
rules: [ // 定義不同檔案類型的處理方式
{
test: /\.css$/, // 正則表達式:匹配 .css 檔案
use: ["style-loader", "css-loader"] // 用這兩個 loader 處理 CSS
},
{
test: /\.js$/, // 匹配 .js 檔案
exclude: /node_modules/, // 排除 node_modules 資料夾
use: "babel-loader" // 用 Babel 轉譯新語法為舊瀏覽器能懂的
}
]
}
};
// === Vite(新一代,快速,設定簡單,推薦新專案使用) ===
// vite.config.js 範例
import { defineConfig } from "vite"; // 引入 Vite 的設定函式
export default defineConfig({ // 匯出 Vite 設定
server: { // 開發伺服器設定
port: 3000, // 開發伺服器使用 3000 埠
proxy: { // 代理設定(解決開發時的 CORS 問題)
"/api": { // 所有 /api 開頭的請求
target: "http://localhost:5000", // 轉發到後端伺服器
changeOrigin: true // 修改請求的 origin 標頭
}
}
},
build: { // 建置設定
outDir: "../wwwroot" // 輸出到 ASP.NET Core 的 wwwroot 資料夾
}
});
// Webpack vs Vite 比較:
// | 特性 | Webpack | Vite |
// |-------------|-------------------|-------------------|
// | 開發啟動速度 | 慢(全部打包) | 快(按需載入) |
// | 設定複雜度 | 高 | 低 |
// | 生態系 | 成熟豐富 | 快速成長 |
// | 推薦用於 | 大型遺留專案 | 新專案 |
📌 TypeScript 基礎(給 C# 開發者)
// TypeScript 是 JavaScript 的超集,加入了型別系統
// 對 C# 開發者來說,TypeScript 會感覺非常親切!
// === 基本型別(像 C# 的 int, string, bool) ===
let name: string = "小明"; // 字串型別(對應 C# 的 string)
let age: number = 25; // 數字型別(對應 C# 的 int/double)
let isActive: boolean = true; // 布林型別(對應 C# 的 bool)
let items: string[] = ["a", "b"]; // 字串陣列(對應 C# 的 string[])
let tuple: [string, number] = ["小明", 25]; // 元組(對應 C# 的 ValueTuple)
// === 介面(像 C# 的 interface) ===
interface User { // 定義 User 介面(像 C# 的 interface 或 class)
id: number; // 必要屬性:ID
name: string; // 必要屬性:名稱
email?: string; // 可選屬性:信箱(? 表示可省略,像 C# 的 string?)
readonly createdAt: Date; // 唯讀屬性(像 C# 的 { get; })
}
// 使用介面
const user: User = { // 建立符合 User 介面的物件
id: 1, // ID
name: "小明", // 名稱
createdAt: new Date() // 建立日期
}; // email 是可選的,所以可以省略
// === 泛型(像 C# 的 Generic) ===
interface ApiResponse<T> { // 定義泛型介面(像 C# 的 ApiResponse<T>)
data: T; // 泛型資料(對應 C# 的 T Data { get; set; })
success: boolean; // 是否成功
message: string; // 訊息
}
// 使用泛型
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> { // 泛型函式
const response = await fetch(url); // 送出請求
return await response.json() as ApiResponse<T>; // 斷言回傳型別
}
// 呼叫時指定型別
const result = await fetchApi<User[]>("/api/users"); // T 是 User[]
console.log(result.data); // data 的型別是 User[],IDE 會提供自動完成
// === Enum(像 C# 的 enum) ===
enum OrderStatus { // 定義訂單狀態列舉
Pending = "pending", // 待處理
Processing = "processing", // 處理中
Completed = "completed", // 已完成
Cancelled = "cancelled" // 已取消
}
// 使用 enum
const status: OrderStatus = OrderStatus.Pending; // 指定狀態為待處理
// === 型別別名和聯合型別 ===
type ID = string | number; // 聯合型別:ID 可以是字串或數字
type Theme = "light" | "dark" | "system"; // 字面量型別:只能是這三個值之一
function setTheme(theme: Theme): void { // 參數只接受指定的值
document.documentElement.dataset.theme = theme; // 設定 HTML 的 data-theme 屬性
}
setTheme("dark"); // 合法
// setTheme("blue"); // 編譯錯誤!"blue" 不在 Theme 型別中
📌 前端框架比較
// === React(Meta 開發,最大生態系) ===
// 特色:虛擬 DOM、JSX 語法、函式組件 + Hooks
// 適合:大型專案、豐富的第三方套件需求
// React 組件範例
function ProductCard({ product }) { // 函式組件,接收 props
const [count, setCount] = useState(0); // useState Hook 管理狀態
return ( // JSX 語法:在 JavaScript 中寫 HTML
<div className="card"> {/* className 取代 HTML 的 class */}
<h3>{product.name}</h3> {/* 用大括號插入 JavaScript 表達式 */}
<p>數量:{count}</p> {/* 顯示狀態值 */}
<button onClick={() => setCount(count + 1)}> {/* 事件處理 */}
增加
</button>
</div>
);
}
// === Vue(尤雨溪開發,漸進式框架) ===
// 特色:模板語法、響應式系統、Single File Component
// 適合:中小型專案、快速上手
// Vue 組件範例(Composition API)
// <template>
// <div class="card">
// <h3>{{ product.name }}</h3> <!-- 模板語法:用雙大括號插值 -->
// <p>數量:{{ count }}</p> <!-- 顯示響應式資料 -->
// <button @click="count++">增加</button> <!-- @click 是 v-on:click 的簡寫 -->
// </div>
// </template>
// <script setup>
// import { ref } from 'vue'; // 引入 ref 建立響應式資料
// const count = ref(0); // ref(0) 建立一個響應式的數字
// const product = defineProps(['product']); // 定義從父組件接收的 props
// </script>
// === Angular(Google 開發,企業級框架) ===
// 特色:完整框架(路由、表單、HTTP 全包)、TypeScript 優先、DI 系統
// 適合:大型企業專案、熟悉 C# 的開發者(概念相似)
// === Blazor(Microsoft 開發,用 C# 寫前端) ===
// 特色:使用 C# 和 Razor 語法、可選 WebAssembly 或 Server 模式
// 適合:純 .NET 團隊、不想學 JavaScript 的 C# 開發者
// Blazor 組件範例
// @page "/counter"
// <h3>計數器</h3>
// <p>目前數量:@count</p> <!-- Razor 語法:@ 開頭插入 C# -->
// <button @onclick="Increment">增加</button> <!-- @onclick 綁定 C# 方法 -->
// @code {
// private int count = 0; // C# 的私有欄位
// private void Increment() => count++; // C# 的方法
// }
// 框架選擇建議:
// | 情境 | 推薦框架 |
// |----------------------------|-------------|
// | 大型專案 + 大量第三方套件 | React |
// | 中小型專案 + 快速開發 | Vue |
// | 企業級 + 完整架構 | Angular |
// | 純 .NET 團隊 | Blazor |
📌 CSS 框架
<!-- === Bootstrap(最老牌,元件豐富) === -->
<!-- 用 class 就能快速建立響應式版面 -->
<div class="container"> <!-- container 自動置中和限制寬度 -->
<div class="row"> <!-- row 建立一列 -->
<div class="col-md-6"> <!-- col-md-6 在中等螢幕佔一半寬度 -->
<div class="card"> <!-- card 預設的卡片元件 -->
<div class="card-body"> <!-- 卡片內容區 -->
<h5 class="card-title">標題</h5> <!-- 卡片標題 -->
<p class="card-text">內容</p> <!-- 卡片內文 -->
<button class="btn btn-primary">按鈕</button> <!-- 主色按鈕 -->
</div>
</div>
</div>
</div>
</div>
<!-- === Tailwind CSS(Utility-First,近年最流行) === -->
<!-- 用小型工具 class 組合出任何設計 -->
<div class="max-w-sm mx-auto"> <!-- max-w-sm 最大寬度、mx-auto 水平置中 -->
<div class="bg-white rounded-lg shadow-md p-6"> <!-- 白背景、圓角、陰影、內距 -->
<h5 class="text-xl font-bold text-gray-800">標題</h5> <!-- 文字大小、粗體、深灰色 -->
<p class="text-gray-600 mt-2">內容</p> <!-- 較淺灰色、上方間距 -->
<button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> <!-- 藍色按鈕、hover 變深 -->
按鈕
</button>
</div>
</div>
/* Bootstrap vs Tailwind 比較 */
/* | 特性 | Bootstrap | Tailwind CSS | */
/* |-------------|--------------------|--------------------| */
/* | 學習曲線 | 低(預設元件多) | 中(需記 class 名) | */
/* | 客製化 | 有限(覆蓋樣式) | 極高(從零組合) | */
/* | 檔案大小 | 較大(整包引入) | 極小(只打包用到的) | */
/* | 設計一致性 | 高(統一風格) | 取決於開發者 | */
/* | 適合 | 快速原型、後台管理 | 客製化設計、現代專案 | */
📌 SPA vs MVC SSR:何時該用哪個?
// SPA(Single Page Application):整個應用是一個 HTML 頁面
// - 前後端完全分離
// - 使用 React/Vue/Angular 等框架
// - 頁面切換不重新載入(Client-Side Routing)
// - 適合:互動性強的應用(管理後台、社群平台、即時通訊)
// MVC SSR(Server-Side Rendering):伺服器產生完整 HTML
// - ASP.NET Core MVC + Razor Views
// - 每次換頁向伺服器請求新的 HTML
// - SEO 友好(搜尋引擎直接讀到完整內容)
// - 適合:內容網站、部落格、電商展示頁
// 混合方案:
// 1. ASP.NET Core MVC + 局部 JavaScript(最簡單)
// - 大部分用 Razor 渲染,需要互動的地方加 JS
// 2. ASP.NET Core + Blazor Server(純 C# 方案)
// - 用 SignalR 即時同步 UI,不需要寫 JavaScript
// 3. ASP.NET Core API + SPA 前端(最大彈性)
// - 後端只提供 API,前端用 React/Vue 開發
// - 適合前後端分開部署的場景
// 選擇建議:
// | 需求 | 推薦方案 |
// |------------------------|---------------------------|
// | SEO 重要 + 少互動 | MVC SSR(Razor Pages) |
// | 高互動 + 即時更新 | SPA(React/Vue) |
// | 純 .NET 團隊 | Blazor Server/WASM |
// | 兩者都要 | Next.js / Nuxt.js(SSR+SPA)|
🤔 我這樣寫為什麼會錯?
❌ 錯誤 1:npm install 後忘記把 node_modules 加入 .gitignore
# 錯誤:把 node_modules 推上 Git,倉庫暴增數百 MB
git add . # 這會把 node_modules 裡的數萬個檔案全部加入
git commit -m "加入專案" # 推上去後別人 clone 要等很久
# 正確:在專案根目錄建立 .gitignore
# .gitignore 內容:
# node_modules/ # 忽略所有下載的套件(別人用 npm install 重新安裝)
# dist/ # 忽略建置輸出(CI/CD 會重新建置)
# .env # 忽略環境變數檔案(包含密鑰等敏感資訊)
❌ 錯誤 2:TypeScript 的型別斷言濫用
// 錯誤:用 as any 跳過所有型別檢查,失去 TypeScript 的意義
const data = response.data as any; // 任何型別都通過,完全沒有型別保護
console.log(data.nonExistentProp.value); // 執行時才會爆炸
// 正確:定義正確的型別
interface Product { // 定義產品介面
id: number; // ID 是數字
name: string; // 名稱是字串
price: number; // 價格是數字
}
const data = response.data as Product; // 斷言為具體型別
console.log(data.name); // IDE 會提供自動完成,打錯字會有警告
// console.log(data.nonExistentProp); // 編譯錯誤!Property 不存在
❌ 錯誤 3:SPA 沒有處理 404 路由的後端設定
// 錯誤情境:SPA 部署到 IIS 或 Nginx 後,重新整理頁面得到 404
// 原因:SPA 的路由(例如 /products/123)是前端處理的
// 但重新整理時,瀏覽器會向伺服器請求 /products/123
// 伺服器找不到這個實際檔案,就回傳 404
// 解法:在 ASP.NET Core 中設定 Fallback
// Program.cs 中:
// app.UseStaticFiles(); // 先嘗試靜態檔案
// app.MapFallbackToFile("index.html"); // 找不到就回傳 index.html
// 或在 IIS 的 web.config 中:
// <rewrite>
// <rules>
// <rule name="SPA" stopProcessing="true">
// <match url=".*" />
// <conditions>
// <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
// </conditions>
// <action type="Rewrite" url="/" />
// </rule>
// </rules>
// </rewrite>