Skip to main content

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 + 二次封装
工具 hooksahooks
样式less + css module(更推荐:Tailwind / UnoCSS)
代码规范ESLint + Prettier + Stylelint
提交规范Husky + lint-staged + Commitlint
测试Vitest + React Testing Library
CI/CDGitHub 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"
}
}

十二、避坑指南

  1. 不要用 process.env:Vite 用 import.meta.env
  2. 动态 import 一定要 lazyconst Page = lazy(() => import(...)),否则首屏加载所有页面
  3. 大文件放进 public:public 下的文件不参与 hash,构建后保持原文件名
  4. 环境变量必须有 VITE_ 前缀:否则客户端读不到
  5. CSS Module 文件名 xxx.module.less:否则不生效
  6. 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 推送模块更新,浏览器只替换变化的模块,不刷新页面

十四、相关文档