---
title: React 前端環境相關
tags: [環境相關]
---
# React 前端環境相關
> 📅 **最後更新:2025-12-23**
>
> **2025 年重要更新:**
>
> - ✅ **TailwindCSS v4**(2025 年 1 月發布)- 安裝流程大幅簡化,使用 `@tailwindcss/vite` 插件
> - ✅ **React Router v7** - 新版路由系統,改用 `@react-router/dev`
> - ✅ **ESLint Flat Config** - 現為標準配置方式
> - ✅ 所有套件版本已驗證為最新
# 🧰 專案初始化與安裝流程
## 1️⃣ 建立環境(系統框架與開發基礎)
📦 定義:為了讓整個專案能「順利執行、順利開發」的底層工具
### 🪜 建立 React + Vite 專案
```bash
npm create vite@latest
```
接下來依照指示輸入:
✔ Project name: » vite-project(← 你想要的專案名稱)
✔ Select a framework: » React
✔ Select a variant: » JavaScript
### 💿 接著進入資料夾 vite-project (← 你想要的專案名稱)並安裝
```bash
cd vite-project
npm install
```
### 📍 React 工具函式庫 與 Vite 插件行為檢查工具
```bash
npm i ahooks
npm i -D vite-plugin-inspect
```
📌 用途說明
- `ahooks`:給 React 組件內用的 Hooks 工具函式庫
- 常見像 useRequest、useLocalStorageState、useDebounce 等實用 hooks,可以幫你快速處理非同步請求、本地儲存、防抖等功能。
- `vite-plugin-inspect`:前端開發用的 Vite 插件
### 📍 初始化 Node 專案(如果需要)
```bash
npm init --yes
npm i -D nodemon
```
### 📍 安裝路由
> 無換頁就不需要
```bash
npm i react-router
npm i -D @react-router/dev
```
**特點:**
- 統一的套件名稱 `react-router`(取代 react-router-dom)
- 使用 `@react-router/dev` 作為開發工具
- 支援 Vite 整合(透過 `@react-router/dev/vite`)
- 新的路由配置方式(`app/routes.ts`)
### 📌 如果沒有的話需手動建立的檔案(.gitignore)
#### 📝 建立檔案.gitignore
> 內容為:要設定讓 git 忽略的檔案
檔名
```
.gitignore
```
內容(最少)
```
.env
node_modules
dist
dump/*
!dump/.gitkeep
```
### 📍 安裝開發用工具(語法檢查、自動重啟)
> 檢查語法是否符合規則(eslint)
> 檔案變動時自動重啟伺服器(開發用)
```bash
npm init @eslint/config@latest
```
```bash
npm i -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks
```
#### 📝 eslint.config.js 內容
##### js 版
檔名
```
eslint.config.js
```
檔案內容(JS 版)
```js
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
export default [
{ ignores: ["dist"] },
{
files: ["**/*.{js,jsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: "module",
},
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"prettier/prettier": [
"warn",
{ printWidth: 100, semi: false, singleQuote: true },
],
},
},
js.configs.recommended,
eslintPluginPrettierRecommended,
];
```
##### ts 版
檔名
```
eslint.config.ts
```
檔案內容(TS 版)
```js
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import { defineConfig } from "eslint/config";
export default defineConfig(
js.configs.recommended,
tseslint.configs.recommended,
{
files: ["**/*.{js,mjs,cjs,ts}"],
languageOptions: {
globals: globals.node,
},
},
{
files: ["**/*.ts"],
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_" },
],
},
},
eslintPluginPrettierRecommended,
);
```
#### 📝 .prettierrc.json 內容
檔名
```
.prettierrc.json
```
檔案內容
```json
{
"$schema": "https://json.schemastores.org/prettierrc",
"printWidth": 100,
"semi": false,
"singleQuote": true
}
```
---
> 選擇想要的 UI 框架,ex: [Material-UI](https://mui.com/material-ui/getting-started/installation/) 套件、[Ant Design](https://ant.design/docs/react/introduce) 套件、[Chakra UI](https://chakra-ui.com/getting-started) 套件
---
### 🪜 安裝 UI 框架
#### Material-UI (MUI)
```bash
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/icons-material
```
##### 設定 Material-UI
參考官方文件:https://mui.com/material-ui/getting-started/
#### Ant Design
```bash
npm install antd
```
##### 設定 Ant Design
參考官方文件:https://ant.design/docs/react/introduce-cn
#### Chakra UI
```bash
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
```
##### 設定 Chakra UI
參考官方文件:https://chakra-ui.com/
#### shadcn/ui
```bash
npx shadcn@latest init
```
##### 設定 shadcn/ui
參考官方文件:https://ui.shadcn.com/docs/installation/vite
**注意事項:**
- 需要 TypeScript 專案(shadcn/ui 主要支援 TypeScript)
- 會自動設定 `tsconfig.json` 和 `tsconfig.app.json` 的 path aliases
- 建議先安裝 TailwindCSS v4,再初始化 shadcn/ui
---
### 🕶️ TailwindCSS 相關
#### 只想安裝 TailwindCSS ,不安裝其他 UI 框架
```bash
npm install -D tailwindcss @tailwindcss/vite
```
#### Tailwind 的設定
{%preview https://hackmd.io/@63Ywhax5TLKtGhFSWnacvQ/SJMNh1Oqxe %}
{%preview https://tailwindcss.com/docs/guides/vite %}
##### 在 src/index.css 匯入 Tailwind
```css
@import "tailwindcss";
```
##### 檢查 main.jsx 中是否有匯入這個 css 檔案
```jsx
import "./index.css";
```
---
## 2️⃣ 常駐工具型功能(必裝輔助開發用,不是主流程但超常出現)
📦 定義:專案未必一開始就用到,但絕大多數專案後期一定會補,甚至常常預設就先裝。
### 狀態管理
> 二選一即可
使用 Zustand:
> 輕量級狀態管理,新手或中小型專案,建議使用
```bash
npm i zustand
```
或使用 Redux Toolkit:
> Redux 官方推薦的狀態管理
```bash
npm i @reduxjs/toolkit react-redux
```
### 狀態儲存、 HTTP 請求、表單驗證
> 無論選擇哪種狀態管理工具,以下套件都建議安裝
```bash
npm i axios react-hook-form yup @hookform/resolvers
```
### 開發用套件
自動引入 import 的工具、路徑別名
```bash
npm i -D @vitejs/plugin-react vite-tsconfig-paths
```
自動化路由
```bash
npm i @generouted/react-router react-router-dom
```
##### 若為 TS 專案
建議在 `package.json` 的 `script` 區塊新增:
```
"type-check": "tsc --noEmit"
```
```
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview",
"type-check": "tsc --noEmit"
}
```
### React 專案推薦資料夾結構(Generouted 自動路由版)
> **💡 使用 Generouted 檔案系統路由**,檔案結構即路由配置
```
src/
├── assets/ # 靜態資源(圖片、字體、icons)
│ ├── images/
│ ├── fonts/
│ └── icons/
│
├── components/ # 可重用的 UI 元件
│ ├── common/ # 通用元件(Button、Input、Modal)
│ ├── layout/ # 版型相關(Header、Footer、Sidebar)
│ └── features/ # 功能專屬元件
│
├── pages/ # ⭐ 檔案系統路由(自動生成路由)
│ ├── _app.jsx # 全站布局(包裹所有頁面)
│ ├── _404.jsx # 404 頁面
│ ├── index.jsx # 首頁 → /
│ │
│ ├── about.jsx # 關於頁面 → /about
│ │
│ ├── products/
│ │ ├── index.jsx → /products
│ │ ├── [id].jsx → /products/:id
│ │ └── components/ # 頁面專屬元件
│ │ ├── ProductFilter.jsx
│ │ └── ProductGrid.jsx
│ │
│ ├── (auth)/ # 認證相關群組(不影響 URL)
│ │ ├── _layout.jsx # 認證布局
│ │ ├── +login.jsx → /login (Modal)
│ │ └── +register.jsx → /register (Modal)
│ │
│ └── dashboard/ # 儀表板
│ ├── _layout.jsx # 儀表板專屬布局
│ ├── index.jsx → /dashboard
│ ├── profile.jsx → /dashboard/profile
│ └── settings.jsx → /dashboard/settings
│
├── hooks/ # 自定義 React Hooks
│ ├── useAuth.js
│ ├── useLocalStorage.js
│ └── useFetch.js
│
├── stores/ # 狀態管理(Zustand / Redux)
│ ├── slices/ # Redux Toolkit slices(如使用 Redux)
│ └── useUserStore.js # Zustand stores
│
├── services/ # API 呼叫相關
│ ├── api.js # Axios 實例設定
│ ├── authService.js # 認證相關 API
│ └── userService.js # 使用者相關 API
│
├── utils/ # 工具函式(純 JS,無 React 依賴)
│ ├── formatters.js # 格式化函式
│ ├── validators.js # 驗證函式
│ └── helpers.js # 其他輔助函式
│
├── constants/ # 常數定義
│ ├── apiEndpoints.js # API 端點
│ └── config.js # 設定檔
│
├── types/ # TypeScript 型別定義(如使用 TS)
│ ├── user.ts
│ └── product.ts
│
├── styles/ # 全域樣式
│ ├── globals.css # 全域 CSS
│ ├── variables.css # CSS 變數
│ └── mixins.scss # SCSS mixins(如使用 SCSS)
│
├── router.ts # ✨ Generouted 自動生成(勿手動編輯)
├── main.jsx # 應用程式入口
└── index.css # 主樣式檔
```
> **💡 提示**:`router.ts` 即使在純 JavaScript 專案中也會是 `.ts` 檔案,這是 Generouted 的設計,無需改為 `.js`
**🔄 與傳統路由的差異:**
| 項目 | 傳統手動路由 | Generouted 自動路由 |
| ------------------ | --------------------- | ------------------------ |
| `router/` 資料夾 | ✅ 需要手動建立 | ❌ 不需要 |
| `layouts/` 資料夾 | ✅ 存放布局元件 | ❌ 改用 `pages/_app.jsx` |
| `constants/routes` | ✅ 手動定義路由常數 | ❌ 自動生成型別安全路由 |
| `App.jsx` | ✅ 根元件 | ❌ 改用 `pages/_app.jsx` |
| `main.jsx` | 使用 RouterProvider | 使用 Generouted Routes |
| 路由配置 | 手動在 `router/` 定義 | 檔案結構即路由 |
| 布局元件 | 在 `layouts/` 資料夾 | 在 `pages/_app.jsx` 等 |
---
#### 在 /src 需手動建立的檔案建議(結構)
##### 新增 components 資料夾
**用途**:可重用元件(推薦按功能分類)
```
components
```
**範例**:
```
components/
├── common/ # 通用 UI 元件
│ ├── Button/
│ │ ├── Button.jsx
│ │ ├── Button.module.css
│ │ └── index.js
│ ├── Input/
│ ├── Modal/
│ └── Card/
│
├── layout/ # 版型相關元件
│ ├── Header.jsx
│ ├── Footer.jsx
│ ├── Sidebar.jsx
│ └── Navbar.jsx
│
└── features/ # 功能專屬元件
├── ProductCard/ # 商品卡片
├── UserProfile/ # 使用者資料
└── CartItem/ # 購物車項目
```
---
##### 新增 pages 資料夾
**用途**:頁面元件(Generouted 檔案系統路由)
```
pages
```
###### 規則說明:Generouted 檔案命名
| 檔案名稱 | 路由 | 說明 |
| -------------- | -------- | ---------------------- |
| `index.jsx` | `/` | 該層級的預設頁面 |
| `about.jsx` | `/about` | 固定路由頁面 |
| `[id].jsx` | `/:id` | 動態路由參數 |
| `[...all].jsx` | `/*` | 通配符路由 |
| `_app.jsx` | - | 全站布局 |
| `_layout.jsx` | - | 區域布局 |
| `(group)/` | - | 路徑群組(不影響 URL) |
| `+modal.jsx` | `/modal` | Modal 路由 |
| `_ignored.jsx` | - | 忽略的檔案 |
詳細的檔案命名規則說明請參考
{%preview https://hackmd.io/@63Ywhax5TLKtGhFSWnacvQ/rk309Luxbx %}
---
##### 新增 `pages/_app.jsx` 檔案,用於全站布局
**用途**:全站布局元件 檔案(使用 Generouted)
**`pages/_app.jsx` - 全站布局:**
同 Vue `layouts/default.vue`
> **🔔 重要提醒**:使用 Generouted 時,所有路由相關的功能都應從 `@/router` 導入(而非 `react-router-dom`),以獲得完整的型別安全檢查。
```
pages/_app.jsx
```
```jsx
// pages/_app.jsx
import { Outlet, Link } from "@/router";
export default function App() {
return (
<div className="min-h-screen flex flex-col">
{/* Header */}
<header className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<Link to="/" className="text-xl font-bold text-gray-900">
MyApp
</Link>
{/* Navigation */}
<nav className="flex gap-6">
<Link to="/" className="text-gray-700 hover:text-gray-900">
首頁
</Link>
<Link to="/about" className="text-gray-700 hover:text-gray-900">
關於
</Link>
<Link
to="/products"
className="text-gray-700 hover:text-gray-900"
>
商品
</Link>
</nav>
</div>
</div>
</header>
{/* Main Content */}
<main className="flex-1">
<Outlet /> {/* 各頁面內容渲染位置 */}
</main>
{/* Footer */}
<footer className="bg-gray-50 border-t">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<p className="text-center text-gray-600 text-sm">
© {new Date().getFullYear()} MyApp. All rights reserved.
</p>
</div>
</footer>
</div>
);
}
```
---
##### 新增 hooks 資料夾
**用途**:自定義 React Hooks,封裝常用邏輯
```
hooks
```
**Zustand 版本:**
```js
// hooks/useAuth.js
import { useState, useEffect } from "react";
import useUserStore from "@/stores/useUserStore";
export function useAuth() {
const { user, isLoggedIn, login, logout } = useUserStore();
const [loading, setLoading] = useState(true);
useEffect(() => {
// 檢查登入狀態
setLoading(false);
}, []);
return { user, isLoggedIn, login, logout, loading };
}
```
**Redux Toolkit 版本:**
```js
// hooks/useAuth.js
import { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {
login as loginAction,
logout as logoutAction,
} from "@/stores/slices/userSlice";
export function useAuth() {
const dispatch = useDispatch();
const { user, isLoggedIn } = useSelector((state) => state.user);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 檢查登入狀態
setLoading(false);
}, []);
const login = (userData, token) => {
dispatch(loginAction({ user: userData, token }));
};
const logout = () => {
dispatch(logoutAction());
};
return { user, isLoggedIn, login, logout, loading };
}
```
```js
// hooks/useLocalStorage.js
import { useState, useEffect } from "react";
export function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
```
---
##### 新增 stores 資料夾
**用途**:狀態管理(相對於 Vue 的 Pinia)
```
stores
```
###### Zustand 版本
適合:新手或中小型專案,輕量級狀態管理
```js
// stores/useUserStore.js
import { create } from "zustand";
import { persist } from "zustand/middleware";
const useUserStore = create(
persist(
(set) => ({
user: null,
token: "",
isLoggedIn: false,
login: (userData, token) => {
set({ user: userData, token, isLoggedIn: true });
},
logout: () => {
set({ user: null, token: "", isLoggedIn: false });
localStorage.removeItem("token");
},
updateUser: (userData) => {
set({ user: userData });
},
updateToken: (newToken) => {
set({ token: newToken });
},
}),
{
name: "user-storage",
partialize: (state) => ({ token: state.token, user: state.user }),
}
)
);
export default useUserStore;
```
###### Redux Toolkit 版本
適合:大型專案或需要複雜狀態管理
```
stores/
├── index.js # Store 設定
└── slices/
├── userSlice.js
└── cartSlice.js
```
```js
// stores/slices/userSlice.js
import { createSlice } from "@reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: {
user: null,
token: "",
isLoggedIn: false,
},
reducers: {
login: (state, action) => {
state.user = action.payload.user;
state.token = action.payload.token;
state.isLoggedIn = true;
},
logout: (state) => {
state.user = null;
state.token = "";
state.isLoggedIn = false;
localStorage.removeItem("token");
},
updateToken: (state, action) => {
state.token = action.payload;
},
updateUser: (state, action) => {
state.user = action.payload;
},
},
});
export const { login, logout, updateToken, updateUser } = userSlice.actions;
export default userSlice.reducer;
```
**基礎版本(無持久化):**
```js
// stores/index.js
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./slices/userSlice";
const store = configureStore({
reducer: {
user: userReducer,
// 其他 reducers 可以加在這裡
// cart: cartReducer,
},
});
export default store;
```
**進階版本(使用 redux-persist 持久化):**
> **💡 提示**:需要先安裝 `npm install redux-persist`,才能使用持久化功能
```js
// stores/index.js
import { configureStore } from "@reduxjs/toolkit";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; // 使用 localStorage
import userReducer from "./slices/userSlice";
// 持久化配置
const persistConfig = {
key: "root",
storage,
whitelist: ["user"], // 只持久化 user,其他 state 不持久化
// blacklist: ['temporaryData'], // 或用黑名單排除不需要持久化的
};
// 包裹 reducer
const persistedReducer = persistReducer(persistConfig, userReducer);
const store = configureStore({
reducer: {
user: persistedReducer,
// 其他 reducers 可以加在這裡
// cart: cartReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
// 忽略 redux-persist 的 action
ignoredActions: ["persist/PERSIST", "persist/REHYDRATE"],
},
}),
});
export const persistor = persistStore(store);
export default store;
```
**使用持久化時的 main.jsx:**
```jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import store, { persistor } from "./stores";
import "./index.css";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(
<StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
</StrictMode>
);
```
---
##### 新增 services 資料夾
**用途**:API 呼叫
```
services
```
```
services/
└── api.js # Axios 實例設定 + 串後端基礎建設
```
**Zustand 版本:**
```js
// src/services/api.js
import axios from "axios";
import useUserStore from "@/stores/useUserStore";
// 無須登入即可使用(接後端)
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:4000",
});
// 須登入才能使用(接後端)
const apiAuth = axios.create({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:4000",
});
// 每次發送請求前自動加上 token
apiAuth.interceptors.request.use((config) => {
const { token } = useUserStore.getState();
config.headers.Authorization = `Bearer ${token}`;
return config;
});
// token 相關 成功處理與失敗處理
apiAuth.interceptors.response.use(
(res) => res,
async (error) => {
if (
error.response &&
error.response.status === 400 &&
error.response.data.message === "token 已過期" &&
error.config.url !== "/user/refresh"
) {
try {
// 直接使用 apiAuth instance 發送請求,避免循環相依
const { data } = await apiAuth.patch("/user/refresh");
// 使用 store 的 updateToken action 更新 token
useUserStore.getState().updateToken(data.token);
error.config.headers.Authorization = `Bearer ${data.token}`;
return axios(error.config);
} catch {
// 刷新失敗則登出
useUserStore.getState().logout();
}
}
throw error;
}
);
export default { api, apiAuth };
```
**Redux Toolkit 版本:**
```js
// src/services/api.js
import axios from "axios";
import store from "@/stores"; // 需要導入 store
import { updateToken, logout } from "@/stores/slices/userSlice";
// 無須登入即可使用(接後端)
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:4000",
});
// 須登入才能使用(接後端)
const apiAuth = axios.create({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:4000",
});
// 每次發送請求前自動加上 token
apiAuth.interceptors.request.use((config) => {
const { token } = store.getState().user;
config.headers.Authorization = `Bearer ${token}`;
return config;
});
// token 相關 成功處理與失敗處理
apiAuth.interceptors.response.use(
(res) => res,
async (error) => {
if (
error.response &&
error.response.status === 400 &&
error.response.data.message === "token 已過期" &&
error.config.url !== "/user/refresh"
) {
try {
// 直接使用 apiAuth instance 發送請求,避免循環相依
const { data } = await apiAuth.patch("/user/refresh");
// 使用 store 的 dispatch 更新 token
store.dispatch(updateToken(data.token));
error.config.headers.Authorization = `Bearer ${data.token}`;
return axios(error.config);
} catch {
// 刷新失敗則登出
store.dispatch(logout());
}
}
throw error;
}
);
export default { api, apiAuth };
```
---
##### 新增 utils 資料夾
**用途**:工具函式
```
utils
```
```jsx
// utils/validators.js
export const isEmail = (email) => {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
};
export const isPhone = (phone) => {
const re = /^09\d{8}$/;
return re.test(phone);
};
```
---
##### 新增 constants 資料夾
**用途**:常數定義
```
constants
```
> **⚠️ 注意**:使用 Generouted 後不需要 `constants/routes.js`,改用型別安全的路由
---
##### 路由導航(使用 Generouted)
**使用型別安全的路由導航:**
> 要在匯入什麼檔案匯入?
> 1. 先確定你已經安裝並設定好 Generouted。
> 2. 確保你的專案結構符合 Generouted 的要求,並且已經生成了 router.ts 檔案。
> 3. 在你的元件中,從生成的 router.ts 檔案中匯入 Link 和 useNavigate。
```jsx
// 從 Generouted 自動生成的 router.ts 匯入
import { Link, useNavigate } from "@/router";
```
例如:
```jsx
// 從 Generouted 自動生成的 router.ts 匯入
import { Link, useNavigate } from "@/router";
export default function Navigation() {
const navigate = useNavigate();
return (
<nav>
{/* ✅ 型別安全,TypeScript 會檢查路由是否存在 */}
<Link to="/">首頁</Link>
<Link to="/about">關於</Link>
<Link to="/products">商品列表</Link>
<Link to="/products/:id" params={{ id: "123" }}>
商品詳情
</Link>
{/* 程式化導航 */}
<button onClick={() => navigate("/dashboard")}>前往儀表板</button>
{/* ❌ 如果路由不存在,TypeScript 會報錯 */}
{/* <Link to="/not-exist">不存在</Link> */}
</nav>
);
}
```
**動態路由參數:**
```jsx
import { useParams } from "@/router";
export default function ProductDetail() {
const { id } = useParams();
return <h1>商品 ID: {id}</h1>;
}
```
---
### 日期處理
```bash
npm i dayjs
```
或
```bash
npm i date-fns
```
### Icon 額外擴充
{%preview https://hackmd.io/@63Ywhax5TLKtGhFSWnacvQ/SkLmkfOlWe %}
---
## 3️⃣ 明確功能型功能(安裝你需要用的功能套件,依需求增減)
📦 定義:為了「特定功能」才會加的套件,專案不同會差很多。
作業要用到 gsap、swiper、animejs
```bash
npm install gsap swiper animejs
```
| 套件名 | 分類 | 用途 | 特點 | 適合情境 |
| ------------ | ---------- | -------------------- | ----------------------------- | -------------------------------- |
| **GSAP** | 明確功能型 | 強大動畫函式庫 | 功能完整、控制精細、效能佳 | 頁面轉場、滾動動畫、複雜動畫流程 |
| **Swiper** | 明確功能型 | 幻燈片輪播工具 | 手機觸控友善、支援 React 元件 | Banner、圖片輪播、步驟流程切換 |
| **Anime.js** | 明確功能型 | 動畫函式庫(較輕巧) | 語法簡潔、適合小動畫 | 元素浮動、icon 動畫、數字增減 |
> **Swiper** 有 React 版,所以:
> 📌 使用 Swiper 時需使用 `swiper/react` 元件,並引入樣式 `swiper/css`
```jsx
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
```
---
## 需增加的檔案內容
安裝完之後要思考:
1. vite.config 是否需掛載
2. main.jsx(或 .tsx)是否需要匯入
### vite-project/vite.config.js
```js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import generouted from "@generouted/react-router/plugin";
import { fileURLToPath, URL } from "node:url";
export default defineConfig({
plugins: [
react({
// React 19 的 Fast Refresh 自動啟用
// 只在需要自定義 Babel 時配置
}),
generouted(),
tailwindcss(),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
"@components": fileURLToPath(
new URL("./src/components", import.meta.url)
),
"@pages": fileURLToPath(new URL("./src/pages", import.meta.url)),
"@hooks": fileURLToPath(new URL("./src/hooks", import.meta.url)),
"@services": fileURLToPath(new URL("./src/services", import.meta.url)),
"@stores": fileURLToPath(new URL("./src/stores", import.meta.url)),
"@assets": fileURLToPath(new URL("./src/assets", import.meta.url)),
},
},
server: {
port: 3000,
host: true,
open: true,
cors: true,
// API 代理配置 (根據後端調整)
proxy: {
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
build: {
chunkSizeWarningLimit: 1000,
reportCompressedSize: false,
rollupOptions: {
output: {
manualChunks(id) {
// 更智能的代碼分割策略
if (id.includes("node_modules")) {
// React 核心
if (
id.includes("react") ||
id.includes("react-dom") ||
id.includes("react-router")
) {
return "react-vendor";
}
// Redux 狀態管理
if (id.includes("redux") || id.includes("@reduxjs")) {
return "redux-vendor";
}
// 表單處理
if (
id.includes("react-hook-form") ||
id.includes("yup") ||
id.includes("@hookform")
) {
return "form-vendor";
}
// 動畫庫 (只在實際使用時才分割)
if (
id.includes("gsap") ||
id.includes("animejs") ||
id.includes("swiper")
) {
return "animation-vendor";
}
// 工具庫
if (
id.includes("axios") ||
id.includes("dayjs") ||
id.includes("ahooks")
) {
return "utils-vendor";
}
// 其他 node_modules
return "vendor";
}
},
chunkFileNames: "js/[name]-[hash].js",
entryFileNames: "js/[name]-[hash].js",
assetFileNames: (assetInfo) => {
// 更細緻的資源文件分類
const fileName = assetInfo.names?.[0] || "asset";
const ext = fileName.split(".").pop();
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(ext)) {
return `images/[name]-[hash].[ext]`;
} else if (/woff2?|eot|ttf|otf/i.test(ext)) {
return `fonts/[name]-[hash].[ext]`;
} else if (ext === "css") {
return `css/[name]-[hash].[ext]`;
}
return `assets/[name]-[hash].[ext]`;
},
},
},
},
optimizeDeps: {
include: [
"react",
"react-dom",
"react-router-dom",
"@generouted/react-router",
"@reduxjs/toolkit",
"react-redux",
"axios",
"dayjs",
"ahooks",
],
exclude: ["@react-router/dev"],
},
css: {
devSourcemap: true,
},
preview: {
host: true,
open: true,
},
});
```
> 💡 如果使用 TailwindCSS,請加入 `tailwindcss` 插件:
>
> ```js
> import tailwindcss from "@tailwindcss/vite";
>
> export default defineConfig({
> plugins: [react(), tailwindcss()],
> // ... 其他設定
> });
> ```
### vite-project/src/App.jsx
```jsx
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { routes } from "@generouted/react-router";
import "./App.css";
const router = createBrowserRouter(routes);
function App() {
return <RouterProvider router={router} />;
}
export default App;
```
### vite-project/src/main.jsx
**基礎版本(無狀態管理):**
```jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);
```
**Redux Toolkit 版本(需配置 Provider):**
> **⚠️ 重要**:使用 Redux Toolkit 時,**必須**用 `<Provider>` 包裹應用,否則 `useSelector` 和 `useDispatch` 會報錯!
```jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import store from "./stores";
import "./index.css";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);
```
### vite-project/.browserslistrc
定義要支援的瀏覽器版本(例如 last 2 versions, > 1%)。
Vite / Babel / PostCSS / Autoprefixer 會依這裡決定要轉譯的語法和加的 CSS 前綴。
```
.browserslistrc
```
```
> 1%
last 2 versions
not dead
not ie 11
```
### vite-project/.editorconfig
統一編輯器的基本格式設定(縮排、換行符號、字元編碼等),確保團隊開發風格一致。
```
.editorconfig
```
```
[*.{js,jsx,ts,jsx}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
```
### vite-project/.env
環境變數設定檔,用於存放 API URL、金鑰等敏感資訊。
> **⚠️ 重要**:
>
> - Vite 專案中,只有以 `VITE_` 開頭的變數才能在前端存取
> - `.env` 檔案應加入 `.gitignore`,避免上傳敏感資訊
> - 使用 `import.meta.env.VITE_變數名稱` 來存取
```
.env
```
**開發環境範例:**
```bash
# API 設定
VITE_API_URL=http://localhost:4000
VITE_API_TIMEOUT=10000
# 功能開關
VITE_ENABLE_MOCK=true
VITE_ENABLE_DEBUG=true
# 第三方服務(範例,實際使用時請替換成真實的)
VITE_GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
VITE_FIREBASE_API_KEY=your_firebase_api_key_here
VITE_STRIPE_PUBLIC_KEY=your_stripe_public_key_here
# 應用程式設定
VITE_APP_NAME=My React App
VITE_APP_VERSION=1.0.0
```
**生產環境範例(.env.production):**
```bash
# API 設定
VITE_API_URL=https://api.your-domain.com
VITE_API_TIMEOUT=30000
# 功能開關
VITE_ENABLE_MOCK=false
VITE_ENABLE_DEBUG=false
# 第三方服務
VITE_GOOGLE_MAPS_API_KEY=your_production_google_maps_api_key
VITE_FIREBASE_API_KEY=your_production_firebase_api_key
VITE_STRIPE_PUBLIC_KEY=your_production_stripe_public_key
# 應用程式設定
VITE_APP_NAME=My React App
VITE_APP_VERSION=1.0.0
```
**在程式中使用:**
```js
// services/api.js
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:4000",
timeout: import.meta.env.VITE_API_TIMEOUT || 10000,
});
// 檢查是否為開發模式
if (import.meta.env.DEV) {
console.log("開發模式");
}
// 檢查是否為生產模式
if (import.meta.env.PROD) {
console.log("生產模式");
}
```
### vite-project/index.html
Vite 的專案入口 HTML,會載入 JS 入口檔(如 main.jsx 或 main.tsx)
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vite-project</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
```
# 各資料夾「建立時機」與啟用條件
| 資料夾 / 功能區 | 建立時機 or 什麼情況需要? | 備註 |
| --------------- | -------------------------------------------------- | ------------------------------------------------ |
| `assets/` | 專案一開始就建立,存放靜態資源 | 圖片、字體、icons、SVG 等 |
| `components/` | 專案一開始就建立,存放可重用元件 | 建議分 `common/`、`layout/`、`features/` |
| `pages/` | 專案一開始就建立,對應每個路由頁面 | 採用 Feature-based 結構,每個頁面一個資料夾 |
| `layouts/` | 有共用版型需求時(如前台、後台不同框架) | 配合 `<Outlet />` 使用,至少有 MainLayout |
| `hooks/` | 出現重複邏輯時(如 useAuth、useLocalStorage) | 自定義 React Hooks,保持元件簡潔 |
| `stores/` | 有跨元件全域狀態需求時(登入、購物車、主題等) | Zustand 或 Redux Toolkit,建議從 Zustand 開始 |
| `services/` | 開始串接 API 時就應建立 | 每個功能模組一個檔案(authService、userService) |
| `utils/` | 有通用工具函式需求時(格式化、驗證、計算等) | 純 JS 函式,無 React 依賴 |
| `constants/` | 有固定資料、設定值時(路由路徑、API 端點、選項等) | 方便維護與修改 |
| `types/` | 使用 TypeScript 時 | 定義型別、介面 |
| `router/` | 安裝 `react-router-dom` 後 | 集中管理路由設定與守衛 |
| `styles/` | 有全域樣式、CSS 變數需求時 | 存放 globals.css、variables.css 等 |
| `.env` | 有環境變數需求時(API URL、金鑰等) | `VITE_` 開頭的變數可在前端使用 |
| `.gitignore` | 專案第一天就該建立 | 避免上傳 node_modules、.env、dist 等 |
---
## 📊 React 專案資料夾優先順序
### 🔴 必須建立(專案一開始)
1. `components/` - 元件庫
2. `pages/` - 頁面
3. `assets/` - 靜態資源
4. `.gitignore` - Git 設定
5. `.env` - 環境變數
### 🟡 很快會需要(前期開發)
6. `layouts/` - 版型(有多頁面時)
7. `router/` - 路由設定(使用 react-router-dom)
8. `services/` - API 呼叫(開始串接後端)
9. `hooks/` - 自定義 Hooks(出現重複邏輯)
10. `stores/` - 狀態管理(有全域狀態需求)
### 🟢 依需求建立(中後期)
11. `utils/` - 工具函式(出現通用邏輯)
12. `constants/` - 常數定義(有固定資料)
13. `styles/` - 全域樣式(需統一管理樣式)
14. `types/` - 型別定義(使用 TypeScript)
---
## 💡 React vs Vue 資料夾結構差異
| 項目 | Vue 3 習慣 | React 習慣 | 說明 |
| ------------ | ----------------------- | ------------------------------------------ | -------------------------------- |
| 元件資料夾 | `components/` | `components/`(分類更細) | React 更強調按功能分類 |
| 頁面資料夾 | `views/` 或 `pages/` | `pages/`(Feature-based) | React 常用 Feature-based 結構 |
| Hooks 資料夾 | `composables/` | `hooks/` | 命名不同,概念相同 |
| 狀態管理 | `stores/` (Pinia) | `stores/` (Zustand/Redux) | React 更多選擇 |
| API 呼叫 | `services/` 或 `api/` | `services/` 或 `api/` | 概念相同 |
| 路由設定 | `router/index.js` | `router/index.jsx` | 概念相同,React 用 JSX |
| 靜態資源 | `assets/` | `assets/` 或 `public/` | React 有 public 資料夾可直接訪問 |
| 版型 | `layouts/` | `layouts/` | 概念相同,React 用 `<Outlet />` |
| 元件組織方式 | 較扁平 | 更傾向 Feature-based(功能導向) | React 常用資料夾包元件的方式 |
| CSS 處理 | `.vue` 檔案內 `<style>` | CSS Modules / Styled-components / Tailwind | React 較多樣化 |
| 自動引入 | unplugin 系列 | 較少使用,傾向明確 import | React 更強調明確性 |
---
## 🎯 React 開發建議
### 1️⃣ 元件組織建議
```
✅ 推薦:Feature-based(功能導向)
components/
├── common/ # 通用元件
├── layout/ # 版型元件
└── features/ # 功能專屬元件
pages/
├── Home/
│ ├── index.jsx
│ └── components/ # 頁面專屬元件
└── Products/
├── index.jsx
├── ProductDetail.jsx
└── components/
❌ 避免:所有元件全部扁平放在 components/
```
### 2️⃣ 命名規範
- 元件檔案:**PascalCase**(`Button.jsx`、`UserProfile.jsx`)
- Hooks 檔案:**camelCase** 以 `use` 開頭(`useAuth.js`、`useLocalStorage.js`)
- 工具函式:**camelCase**(`formatDate.js`、`validators.js`)
- 常數檔案:**camelCase** 或 **UPPER_CASE**(`routes.js`、`API_ENDPOINTS.js`)
### 3️⃣ 資料夾 index 模式
```jsx
// ✅ 推薦:使用 index.js 統一匯出
components/Button/
├── Button.jsx
├── Button.module.css
└── index.js // export { default } from './Button'
// 使用時
import Button from '@/components/Button' // 簡潔!
// ❌ 避免:沒有 index.js
import Button from '@/components/Button/Button' // 重複!
```
### 4️⃣ 狀態管理選擇
- **小型專案**:React Context API + hooks
- **中型專案**:Zustand(推薦新手)
- **大型專案 / 複雜狀態**:Redux Toolkit
- **伺服器狀態**:React Query / SWR
### 5️⃣ 樣式處理建議
- **Tailwind CSS**:最流行,開發快速
- **CSS Modules**:作用域隔離,適合元件化
- **Styled-components**:CSS-in-JS,動態樣式方便
- **Sass/SCSS**:傳統但強大,適合大型專案
# 🧩 前端套件分類表
| 套件名稱 | 安裝位置 | 類型 | 用途說明 | 代表常見用途 |
| ---------------------- | -------- | ---------- | ------------------------- | ---------------- |
| `react` | ✅ 前端 | 主框架 | 建立 React App | 必裝主角 |
| `react-dom` | ✅ 前端 | 主框架 | React DOM 渲染 | 必裝主角 |
| `vite` | ✅ 前端 | 開發工具 | 開發伺服器、熱更新 | 必裝主角 |
| `@vitejs/plugin-react` | ✅ 前端 | 插件 | 支援 `.jsx/.jsx` 組件語法 | Vite 必備 |
| `react-router-dom` | ✅ 前端 | 路由工具 | 頁面切換、SPA | 路由系統 |
| `zustand` | ✅ 前端 | 狀態管理 | 輕量級 state 管理工具 | 全域資料 |
| `@reduxjs/toolkit` | ✅ 前端 | 狀態管理 | Redux 官方推薦工具 | 全域資料 |
| `axios` | ✅ 前端 | HTTP 工具 | 前端請求 API | 抓資料 |
| `react-hook-form` | ✅ 前端 | 表單驗證 | 表單輸入處理與驗證 | 登入、註冊 |
| `yup` / `zod` | ✅ 前端 | 表單驗證 | Schema 驗證 | 表單驗證 |
| `@iconify/react` | ✅ 前端 | Icon | 通用 icon 套件 | 自定義圖示 |
| `react-icons` | ✅ 前端 | Icon | 整合多種 icon 庫 | 自定義圖示 |
| `dayjs` / `date-fns` | ✅ 前端 | 日期處理 | 處理時間與格式 | 預約、顯示日期 |
| `gsap` / `animejs` | ✅ 前端 | 動畫 | 控制動畫流程 | 頁面轉場 |
| `swiper` | ✅ 前端 | 輪播工具 | 輪播圖、滑動效果 | banner、步驟切換 |
| `ahooks` | ✅ 前端 | 工具函式庫 | 一堆超實用的 React Hooks | 類似小助手 |
| `vite-plugin-inspect` | ✅ 前端 | Vite 插件 | 檢查套件掛載情況 | debug 開發流程 |
| `@mui/material` | ✅ 前端 | UI 框架 | Material Design UI 元件庫 | UI 設計 |
| `antd` | ✅ 前端 | UI 框架 | Ant Design UI 元件庫 | UI 設計 |
| `@chakra-ui/react` | ✅ 前端 | UI 框架 | Chakra UI 元件庫 | UI 設計 |
| `framer-motion` | ✅ 前端 | 動畫 | React 動畫函式庫 | 頁面動畫 |
---
## React vs Vue 主要差異對照表
| 項目 | Vue 3 | React |
| ------------ | --------------------------- | ------------------------------ |
| 主要檔案 | `App.vue` | `App.jsx` / `App.jsx` |
| 入口檔案 | `main.js` | `main.jsx` / `main.jsx` |
| 路由套件 | `vue-router` | `react-router-dom` |
| 狀態管理 | `pinia` | `zustand` / `@reduxjs/toolkit` |
| 表單驗證 | `vee-validate` + `yup` | `react-hook-form` + `yup` |
| Hooks 工具庫 | `@vueuse/core` | `ahooks` |
| 樣板語法 | `<template>` | JSX |
| 響應式資料 | `ref()` / `reactive()` | `useState()` / `useReducer()` |
| 副作用處理 | `watch()` / `watchEffect()` | `useEffect()` |
| 組件定義 | `<script setup>` | `function Component()` |
| 路由插槽 | `<router-view />` | `<Outlet />` |
---
# 總結
這份文件涵蓋了 React + Vite 專案的完整初始化流程,包括:
1. ✅ **基礎環境建立**(React + Vite + ESLint + Prettier)
2. ✅ **UI 框架選擇**(MUI / Ant Design / Chakra UI / shadcn/ui)
3. ✅ **必備工具安裝**(路由、狀態管理、HTTP 請求、表單驗證)
4. ✅ **專案結構建議**(pages / components / hooks / services / stores)
5. ✅ **設定檔範例**(vite.config.js / eslint.config.js / jsconfig.json)
根據你的專案需求,選擇性安裝對應的套件,建立完善的 React 開發環境!🚀
---
---
## 📚 官方文件連結
- [Vite 官方文件](https://vite.dev/)
- [React 官方文件](https://react.dev/)
- [TailwindCSS v4 文件](https://tailwindcss.com/)
- [React Router v7 文件](https://reactrouter.com/)
- [Zustand 文件](https://zustand.docs.pmnd.rs/)
- [shadcn/ui 文件](https://ui.shadcn.com/)
---
**文件維護說明:**
本文件會定期更新以確保資訊的正確性和時效性。最後檢查日期:2025-12-23