--- 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