Vite + React + TS
2025+ 推荐方案:Vite 启动快、配置简单、HMR 体验好,新项目首选。CRA / react-app-rewired 已停维护。
旧版 CRA 实战见 react 单页面项目(CRA 实战),仅作历史参考。
一、技术栈选型
| 维度 | 选型 |
|---|---|
| 构建工具 | Vite 5+ |
| 框架 | React 18 + Hooks |
| 语言 | TypeScript 5+ |
| 路由 | react-router v6 |
| 状态管理 | Zustand(轻量) / Redux Toolkit(复杂业务) |
| UI 库 | Ant Design 5+ |
| 请求 | axios + 二次封装 |
| 工具 hooks | ahooks |
| 样式 | less + css module(更推荐:Tailwind / UnoCSS) |
| 代码规范 | ESLint + Prettier + Stylelint |
| 提交规范 | Husky + lint-staged + Commitlint |
| 测试 | Vitest + React Testing Library |
| CI/CD | GitHub Actions |
二、一键创建
# Vite 官方模板
pnpm create vite my-app --template react-ts
cd my-app && pnpm install
可选模板:react-ts / react-swc-ts(用 SWC 编译,更快)
三、目录结构
my-app
├─ public/ # 静态资源(不会被 hash)
│ └─ favicon.ico
├─ src
│ ├─ assets/ # 静态资源(图片、字体)
│ ├─ components/ # 公共组件
│ │ └─ Button/
│ │ ├─ index.tsx
│ │ └─ Button.module.less
│ ├─ pages/ # 页面级组件
│ │ └─ Home/
│ │ └─ index.tsx
│ ├─ hooks/ # 自定义 hooks
│ ├─ utils/ # 工具函数
│ ├─ api/ # 接口请求
│ ├─ store/ # 状态管理
│ ├─ router/ # 路由配置
│ ├─ types/ # 全局类型
│ ├─ styles/ # 全局样式、变量
│ │ ├─ global.less
│ │ └─ variables.less
│ ├─ config/ # 环境配置
│ ├─ App.tsx
│ └─ main.tsx
├─ .editorconfig
├─ .eslintrc.cjs
├─ .prettierrc.json
├─ .stylelintrc.cjs
├─ .env.development
├─ .env.production
├─ tsconfig.json
├─ vite.config.ts
├─ package.json
└─ README.md
四、Vite 核心配置
vite.config.ts:
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import path from "node:path";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
return {
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
css: {
preprocessorOptions: {
less: {
additionalData: `@import "@/styles/variables.less";`,
},
},
modules: {
localsConvention: "camelCase",
},
},
server: {
port: 3000,
open: true,
proxy: {
"/api": {
target: env.VITE_API_BASE_URL,
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api/, "/api"),
},
},
},
build: {
target: "es2015",
outDir: "dist",
sourcemap: mode !== "production",
rollupOptions: {
output: {
chunkFileNames: "static/js/[name]-[hash].js",
entryFileNames: "static/js/[name]-[hash].js",
assetFileNames: "static/[ext]/[name]-[hash].[ext]",
manualChunks: {
react: ["react", "react-dom", "react-router-dom"],
antd: ["antd"],
},
},
},
},
};
});
五、TypeScript 配置
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
tsconfig.node.json单独给 vite.config 等 node 端代码用。
六、ESLint + Prettier
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh prettier eslint-config-prettier
.eslintrc.cjs:
module.exports = {
root: true,
env: { browser: true, es2021: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier",
],
parser: "@typescript-eslint/parser",
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
settings: { react: { version: "detect" } },
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": "warn",
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
},
};
.prettierrc.json:
{
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
"arrowParens": "always",
"endOfLine": "lf"
}
七、环境变量
.env.development:
VITE_API_BASE_URL=https://dev-api.example.com
VITE_APP_TITLE=MyApp (Dev)
.env.production:
VITE_API_BASE_URL=https://api.example.com
VITE_APP_TITLE=MyApp
使用:
console.log(import.meta.env.VITE_API_BASE_URL);
Vite 的环境变量必须以
VITE_开头才能在客户端访问。
八、路由(懒加载)
// src/router/index.tsx
import { lazy, Suspense } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const Home = lazy(() => import("@/pages/Home"));
const User = lazy(() => import("@/pages/User"));
const router = createBrowserRouter([
{ path: "/", element: <Home /> },
{ path: "/user", element: <User /> },
]);
export const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<RouterProvider router={router} />
</Suspense>
);
九、请求函数封装
见 请求函数封装。
十、提交规范(Husky + Commitlint)
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional
pnpm husky init
详细配置见 统一编码规范 - Husky + lint-staged。
十一、package.json 脚本
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint . --ext .ts,.tsx --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,less,md,json}\"",
"typecheck": "tsc --noEmit",
"test": "vitest"
}
}
十二、避坑指南
- 不要用
process.env:Vite 用import.meta.env - 动态 import 一定要 lazy:
const Page = lazy(() => import(...)),否则首屏加载所有页面 - 大文件放进 public:public 下的文件不参与 hash,构建后保持原文件名
- 环境变量必须有
VITE_前缀:否则客户端读不到 - CSS Module 文件名
xxx.module.less:否则不生效 - TypeScript
noEmit: true:Vite 自己处理编译,tsc 只做类型检查
十三、面试高频
- Q:Vite 为什么比 webpack 快? A:开发用 esbuild + 原生 ESM,按需编译;生产用 Rollup,产物质量有保障。webpack 启动时把所有模块打包,Vite 启动时只启动 dev server,请求哪个模块再编译。
- Q:Vite 的 esbuild 在生产环境也用吗? A:不用。生产环境是 Rollup 打包,因为 Rollup 的 tree-shaking 和代码分割更精细,产物更小。
- Q:HMR 原理? A:WebSocket 推送模块更新,浏览器只替换变化的模块,不刷新页面。
十四、相关文档
- 新建项目结构
- webpack5 从 0 搭建 SPA(学习用,2026 优先选 Vite)
- 统一编码规范
- 技术选型