第121课:工程化实践——React 项目初始化与目录结构 在前端框架的学习中,我们掌握了 React、Vue、Angular 各自的组件化、状态管理、路由、响应式原理等核心知识。然而,将这些知识落地到一个可维护、可扩展、可团队协作 的真实项目中,还需要工程化的项目架构作为骨架。本节课作为第十部分“工程化实践”的开篇,将以 React + TypeScript + Vite 为技术栈,从零搭建一个企业级单页应用(SPA)的项目骨架。我们将详细讲解项目初始化、TypeScript 严格配置、ESLint + Prettier 规范配置、Husky + lint-staged 提交拦截、目录结构分层设计原则、以及环境变量与 API 模块的封装。目标不是“能跑就行”,而是构建一个即开即用、结构清晰、约束到位 的工程模板,让团队成员能专注于业务开发而非配置调试。
1. 项目初始化:Vite + React + TypeScript 模板 1.1 使用 Vite 创建项目 1 2 3 4 5 6 7 8 pnpm create vite my-react-app --template react-ts cd my-react-apppnpm install
生成的项目结构包含默认的 src/App.tsx、vite.config.ts、tsconfig.json 等。但默认配置不足以支撑中大型项目,需要进行深度定制。
1.2 初始清理 删除 Vite 模板中的演示代码,为实际项目骨架做准备:
1 2 3 rm src/App.css src/index.css src/assets/react.svg
将 src/App.tsx 精简为:
1 2 3 4 5 function App ( ) { return <div > My App</div > ; } export default App ;
将 src/main.tsx 调整为:
1 2 3 4 5 6 7 8 9 import React from 'react' ;import ReactDOM from 'react-dom/client' ;import App from './App' ;ReactDOM .createRoot (document .getElementById ('root' )!).render ( <React.StrictMode > <App /> </React.StrictMode > ,);
保留 public/vite.svg 作为占位图标,后续可替换。
2. TypeScript 严格配置 默认 tsconfig.json 的 strict: true 已经开启了许多严格检查,但大型项目还需要更细粒度的编译选项和路径别名。
2.1 更新 tsconfig.json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 { "compilerOptions" : { "target" : "ES2022" , "lib" : [ "ES2022" , "DOM" , "DOM.Iterable" ] , "module" : "ESNext" , "moduleResolution" : "bundler" , "jsx" : "react-jsx" , "strict" : true , "noUnusedLocals" : true , "noUnusedParameters" : true , "noFallthroughCasesInSwitch" : true , "forceConsistentCasingInFileNames" : true , "skipLibCheck" : true , "esModuleInterop" : true , "allowSyntheticDefaultImports" : true , "resolveJsonModule" : true , "isolatedModules" : true , "noEmit" : true , "baseUrl" : "." , "paths" : { "@/*" : [ "src/*" ] } } , "include" : [ "src" ] , "references" : [ { "path" : "./tsconfig.node.json" } ] }
关键选项说明 :
noUnusedLocals / noUnusedParameters:强制移除未使用的变量和参数,保持代码干净。
paths:定义 @/* 路径别名,映射到 src/ 目录,配合 Vite 的 resolve.alias 使用。
isolatedModules: true:确保每个文件都是独立模块,兼容 Vite 的 esbuild 转译。
noEmit: true:仅做类型检查,不生成编译产物(由 Vite 负责构建)。
2.2 配置 Vite 路径别名 在 vite.config.ts 中同步路径别名,确保 Vite 能正确解析 @/ 导入:
1 2 3 4 5 6 7 8 9 10 11 12 import { defineConfig } from 'vite' ;import react from '@vitejs/plugin-react' ;import { resolve } from 'path' ;export default defineConfig ({ plugins : [react ()], resolve : { alias : { '@' : resolve (__dirname, 'src' ), }, }, });
注意 :__dirname 在 ESM 模块中不可用,需要在 tsconfig.node.json 中设置 "module": "ESNext" 和 "moduleResolution": "bundler",或者在 vite.config.ts 顶部使用 import.meta.url 配合 fileURLToPath 来获取目录路径。为简化,可以使用 resolve('./src') 相对路径,但推荐使用 import.meta 方式。在 Vite 项目中,vite.config.ts 会被 Vite 以 Node 环境编译,__dirname 实际上是可用的,但为保险起见,可以使用 new URL('./src', import.meta.url).pathname 方式。
更健壮的写法:
1 2 3 4 5 6 7 8 9 10 11 12 import { defineConfig } from 'vite' ;import react from '@vitejs/plugin-react' ;import { fileURLToPath, URL } from 'url' ;export default defineConfig ({ plugins : [react ()], resolve : { alias : { '@' : fileURLToPath (new URL ('./src' , import .meta .url )), }, }, });
3. 代码规范:ESLint + Prettier + Husky + lint-staged 3.1 ESLint 配置 Vite 的 react-ts 模板已经包含了 ESLint,但需要根据企业标准进行调整。在项目根目录创建 .eslintrc.cjs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 module .exports = { root : true , env : { browser : true , es2022 : true }, extends : [ 'eslint:recommended' , 'plugin:@typescript-eslint/recommended' , 'plugin:react-hooks/recommended' , 'plugin:react/recommended' , 'plugin:react/jsx-runtime' , 'prettier' , ], parser : '@typescript-eslint/parser' , parserOptions : { ecmaVersion : 'latest' , sourceType : 'module' , ecmaFeatures : { jsx : true }, }, plugins : ['react-refresh' ], rules : { 'react-refresh/only-export-components' : ['warn' , { allowConstantExport : true }], '@typescript-eslint/no-unused-vars' : ['error' , { argsIgnorePattern : '^_' }], 'no-console' : ['warn' , { allow : ['warn' , 'error' ] }], 'prefer-const' : 'error' , 'eqeqeq' : ['error' , 'always' ], }, settings : { react : { version : 'detect' }, }, };
关键规则 :
eslint:recommended + @typescript-eslint/recommended 覆盖基本类型和逻辑错误。
plugin:react-hooks/recommended 检查 Hooks 规则(依赖数组完整性、不在条件中调用 Hooks)。
prettier 放在 extends 最后,关闭所有与 Prettier 冲突的格式化规则。
no-console 设为警告,仅允许 console.warn 和 console.error,禁止 console.log 提交到代码库。
3.2 Prettier 配置 创建 .prettierrc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "printWidth" : 100 , "tabWidth" : 2 , "useTabs" : false , "semi" : true , "singleQuote" : true , "quoteProps" : "as-needed" , "jsxSingleQuote" : false , "trailingComma" : "all" , "bracketSpacing" : true , "bracketSameLine" : false , "arrowParens" : "always" , "endOfLine" : "lf" }
创建 .prettierignore:
1 2 3 4 dist/ node_modules/ coverage/ pnpm-lock.yaml
3.3 Husky + lint-staged 提交拦截 1 2 3 4 5 pnpm add -D husky lint-staged npx husky init
编辑 .husky/pre-commit:
在 package.json 中添加 lint-staged 配置:
1 2 3 4 5 6 { "lint-staged" : { "*.{ts,tsx,js,jsx}" : [ "eslint --fix" , "prettier --write" ] , "*.{css,scss,md,json}" : [ "prettier --write" ] } }
在 package.json 中添加 scripts:
1 2 3 4 5 6 7 8 9 10 11 12 { "scripts" : { "dev" : "vite" , "build" : "tsc && vite build" , "preview" : "vite preview" , "lint" : "eslint src --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0" , "lint:fix" : "eslint src --ext .ts,.tsx --fix" , "format" : "prettier --write \"src/**/*.{ts,tsx,css,scss,md,json}\"" , "typecheck" : "tsc --noEmit" , "prepare" : "husky" } }
**tsc && vite build**:在构建前先执行 TypeScript 类型检查,类型不通过则构建失败。这是防止类型错误进入生产环境的最后一道防线。
4. 环境变量与 API 模块封装 4.1 环境变量文件 Vite 使用 .env 文件管理环境变量。创建以下文件:
1 2 3 4 5 6 7 8 VITE_APP_TITLE=My React App VITE_API_BASE_URL=http://localhost:3000/api VITE_API_BASE_URL=https://api.example.com
在 src/vite-env.d.ts 中扩展类型(如果已存在,追加):
1 2 3 4 5 6 7 8 9 10 interface ImportMetaEnv { readonly VITE_APP_TITLE : string ; readonly VITE_API_BASE_URL : string ; } interface ImportMeta { readonly env : ImportMetaEnv ; }
4.2 API 客户端封装 在 src/api/client.ts 中创建一个基于 fetch 的通用 HTTP 客户端,封装请求拦截、错误处理、Token 注入等功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 const API_BASE_URL = import .meta .env .VITE_API_BASE_URL ;interface RequestConfig extends Omit <RequestInit , 'body'> { body?: unknown ; params?: Record <string , string | number | undefined >; } class HttpClient { private async request<T>(endpoint : string , config : RequestConfig = {}): Promise <T> { const { body, params, ...init } = config; let url = `${API_BASE_URL} ${endpoint} ` ; if (params) { const searchParams = new URLSearchParams (); Object .entries (params).forEach (([key, value] ) => { if (value !== undefined ) searchParams.set (key, String (value)); }); const qs = searchParams.toString (); if (qs) url += `?${qs} ` ; } const headers : Record <string , string > = { 'Content-Type' : 'application/json' , ...init.headers as Record <string , string >, }; const token = localStorage .getItem ('token' ); if (token) { headers['Authorization' ] = `Bearer ${token} ` ; } const response = await fetch (url, { ...init, headers, body : body ? JSON .stringify (body) : undefined , }); if (!response.ok ) { const error = await response.json ().catch (() => ({ message : response.statusText })); throw new Error (error.message || `HTTP Error ${response.status} ` ); } if (response.status === 204 ) return undefined as T; return response.json (); } get<T>(endpoint : string , config?: RequestConfig ) { return this .request <T>(endpoint, { ...config, method : 'GET' }); } post<T>(endpoint : string , body?: unknown , config?: RequestConfig ) { return this .request <T>(endpoint, { ...config, method : 'POST' , body }); } put<T>(endpoint : string , body?: unknown , config?: RequestConfig ) { return this .request <T>(endpoint, { ...config, method : 'PUT' , body }); } patch<T>(endpoint : string , body?: unknown , config?: RequestConfig ) { return this .request <T>(endpoint, { ...config, method : 'PATCH' , body }); } delete <T>(endpoint : string , config?: RequestConfig ) { return this .request <T>(endpoint, { ...config, method : 'DELETE' }); } } export const http = new HttpClient ();
4.3 API 模块示例 创建 src/api/user.api.ts,按业务领域组织 API 调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import { http } from './client' ;export interface User { id : number ; name : string ; email : string ; role : string ; } export const userApi = { getUsers : (params?: { page?: number ; size?: number } ) => http.get <User []>('/users' , { params }), getUserById : (id: number ) => http.get <User >(`/users/${id} ` ), createUser : (data: Omit<User, 'id' > ) => http.post <User >('/users' , data), updateUser : (id: number , data: Partial<User> ) => http.patch <User >(`/users/${id} ` , data), deleteUser : (id: number ) => http.delete <void >(`/users/${id} ` ), };
5. 目录结构分层设计 5.1 推荐目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 my-react-app/ ├── public/ │ └── favicon.svg ├── src/ │ ├── api/ # API 请求模块 │ │ ├── client.ts # 通用 HTTP 客户端 │ │ └── user.api.ts # 用户相关 API │ ├── assets/ # 静态资源(图片、字体、图标) │ ├── components/ # 通用 UI 组件 │ │ ├── ui/ # 基础 UI 组件(Button、Input、Modal) │ │ └── layout/ # 布局组件(Header、Sidebar、Footer) │ ├── hooks/ # 自定义 Hooks │ │ ├── useAuth.ts │ │ └── useDebounce.ts │ ├── pages/ # 页面组件(按路由组织) │ │ ├── Home/ │ │ │ ├── index.tsx │ │ │ └── Home.module.css │ │ ├── Users/ │ │ └── NotFound/ │ ├── router/ # 路由配置 │ │ └── index.tsx │ ├── stores/ # 状态管理(Zustand 或 Redux) │ │ ├── useAuthStore.ts │ │ └── useAppStore.ts │ ├── styles/ # 全局样式与 CSS 变量 │ │ ├── globals.css │ │ └── variables.css │ ├── types/ # 共享 TypeScript 类型定义 │ │ └── common.ts │ ├── utils/ # 工具函数 │ │ ├── format.ts │ │ └── storage.ts │ ├── App.tsx │ ├── main.tsx │ └── vite-env.d.ts ├── .env.development ├── .env.production ├── .eslintrc.cjs ├── .prettierrc ├── .prettierignore ├── .gitignore ├── index.html ├── package.json ├── pnpm-lock.yaml ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts
5.2 分层原则
**api/**:与后端交互的逻辑集中在 HTTP 客户端和按业务领域拆分的 API 模块中。组件不应直接调用 fetch,必须通过 API 层。
components/:存放 可复用 的 UI 组件。ui/ 下是完全无业务逻辑的基础组件(按钮、输入框、弹窗),layout/ 下是页面布局组件。它们不应直接访问路由或全局状态(通过 Props 接收数据)。
**pages/**:对应路由的页面入口。它们组合 components/ 和 hooks/,负责加载数据、处理交互,并调用 API。
**hooks/**:封装可复用的逻辑(如 useAuth、useFetch),遵循 React Hooks 命名规则。
**stores/**:全局状态管理。每个 Store 负责一个业务领域,通过自定义 Hook 暴露接口。
**utils/**:纯函数工具集(格式化日期、数字、本地存储操作等),不包含 React 依赖,可被任何模块导入。
**types/**:跨模块共享的 TypeScript 类型定义。避免在多个文件中重复定义相同的接口。
5.3 命名约定
类型
规则
示例
组件文件与目录
PascalCase(组件目录优先使用目录名 + index.tsx)
UserCard/ 或 UserCard.tsx
Hook 文件
camelCase,以 use 开头
useAuth.ts
工具函数文件
camelCase
format.ts
类型定义文件
camelCase
common.ts
CSS Module 文件
与组件同名 + .module.css
Home.module.css
页面目录
PascalCase
Home/
API 模块文件
与资源名对应,resource.api.ts
user.api.ts
6. CSS 方案:CSS Modules + 全局变量 6.1 CSS Modules 配置 Vite 原生支持 CSS Modules。任何以 .module.css(或 .module.scss)结尾的文件都会自动启用模块化。在 TypeScript 中获得类型安全,需要添加类型声明文件。创建 src/types/css-modules.d.ts:
1 2 3 4 5 6 7 8 9 declare module '*.module.css' { const classes : { readonly [key : string ]: string }; export default classes; } declare module '*.module.scss' { const classes : { readonly [key : string ]: string }; export default classes; }
6.2 全局 CSS 变量 在 src/styles/variables.css 中定义设计令牌(Design Tokens):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 :root { --color-primary : #4f46e5 ; --color-primary-hover : #4338ca ; --color-success : #10b981 ; --color-warning : #f59e0b ; --color-error : #ef4444 ; --color-bg : #f8fafc ; --color-text : #1e293b ; --color-text-secondary : #64748b ; --color-border : #e2e8f0 ; --space-1 : 4px ; --space-2 : 8px ; --space-3 : 12px ; --space-4 : 16px ; --space-6 : 24px ; --space-8 : 32px ; --radius-sm : 4px ; --radius-md : 8px ; --radius-lg : 12px ; --font-family : 'Inter' , system-ui, -apple-system, sans-serif; --font-size-sm : 0.875rem ; --font-size-base : 1rem ; --font-size-lg : 1.125rem ; --font-size-xl : 1.25rem ; --font-size-2xl : 1.5rem ; }
在 src/styles/globals.css 中引入变量并设置全局基础样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @import './variables.css' ;*, *::before , *::after { box-sizing : border-box; margin : 0 ; padding : 0 ; } body { font-family : var (--font-family); font-size : var (--font-size-base); color : var (--color-text); background-color : var (--color-bg); -webkit-font-smoothing : antialiased; -moz-osx-font-smoothing : grayscale; } a { color : var (--color-primary); text-decoration : none; } a :hover { color : var (--color-primary-hover); }
在 src/main.tsx 中引入全局样式:
1 import './styles/globals.css' ;
7. 路由配置:React Router v6 集成 7.1 安装 React Router 1 pnpm add react-router-dom
7.2 创建路由配置 在 src/router/index.tsx 中定义路由表,使用懒加载 拆分页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import { lazy, Suspense } from 'react' ;import { createBrowserRouter, RouterProvider } from 'react-router-dom' ;const HomePage = lazy (() => import ('@/pages/Home' ));const UsersPage = lazy (() => import ('@/pages/Users' ));const NotFoundPage = lazy (() => import ('@/pages/NotFound' ));function PageLoader ( ) { return <div className ="page-loader" > 加载中...</div > ; } const router = createBrowserRouter ([ { path : '/' , element : <HomePage /> , }, { path : '/users' , element : <UsersPage /> , }, { path : '*' , element : <NotFoundPage /> , }, ]); export function AppRouter ( ) { return ( <Suspense fallback ={ <PageLoader /> }> <RouterProvider router ={router} /> </Suspense > ); }
在 src/App.tsx 中使用路由:
1 2 3 4 5 6 7 import { AppRouter } from '@/router' ;function App ( ) { return <AppRouter /> ; } export default App ;
7.3 受保护路由与布局路由(可选扩展) 对于需要登录认证的页面,可以封装 ProtectedRoute 组件,在路由配置中使用 element 包裹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { lazy } from 'react' ;import { createBrowserRouter } from 'react-router-dom' ;const DashboardLayout = lazy (() => import ('@/components/layout/DashboardLayout' ));const DashboardPage = lazy (() => import ('@/pages/Dashboard' ));const LoginPage = lazy (() => import ('@/pages/Login' ));const router = createBrowserRouter ([ { path : '/login' , element : <LoginPage /> }, { path : '/' , element : <DashboardLayout /> , children : [ { index : true , element : <DashboardPage /> }, ], }, ]);
这里仅给出骨架,实际认证逻辑可通过 ProtectedRoute 组件(已在第 98 课详述)与 Zustand 或 Context 配合实现。
8. 开发工作流与常用命令 在 package.json 中整理最终脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 { "scripts" : { "dev" : "vite" , "build" : "tsc && vite build" , "preview" : "vite preview" , "lint" : "eslint src --ext .ts,.tsx --max-warnings 0" , "lint:fix" : "eslint src --ext .ts,.tsx --fix" , "format" : "prettier --write \"src/**/*.{ts,tsx,css,scss,md,json}\"" , "typecheck" : "tsc --noEmit" , "test" : "vitest" , "prepare" : "husky" } }
常用命令清单 :
命令
用途
pnpm dev
启动开发服务器(HMR)。
pnpm build
类型检查 + 生产构建。
pnpm preview
预览生产构建产物。
pnpm lint
检查代码规范(ESLint),警告也算失败。
pnpm lint:fix
自动修复可修复的 ESLint 错误。
pnpm format
使用 Prettier 格式化代码。
pnpm typecheck
仅运行 TypeScript 类型检查,不构建。
pnpm test
运行 Vitest 单元测试(需安装 vitest,可后续扩展)。
课后练习 一、概念自测(选择题 / 填空题)
(单选) 在 Vite + React + TypeScript 项目中,tsconfig.json 的 paths 配置需要与哪个文件中的 resolve.alias 保持一致? A. package.json B. vite.config.ts C. index.html D. .eslintrc.cjs
(单选) 在 lint-staged 配置中,以下哪项是典型的目标? A. 在 CI 服务器上运行整个项目的 lint。 B. 仅在 Git 暂存区的文件上运行 lint 和格式化。 C. 替代 ESLint 和 Prettier。 D. 生成代码覆盖率报告。
(填空) 为了防止包含类型错误的代码被构建到生产环境,build 脚本应在 vite build 之前执行 ______ 命令进行类型检查。
(多选) 以下哪些文件应通过 .gitignore 忽略(不提交到仓库)? A. node_modules/ B. dist/ C. .env.development D. pnpm-lock.yaml
二、AI 编程任务:编写面向 AI 的提示词 场景 :你需要从零创建一个 React + TypeScript + Vite 的企业级项目骨架。要求如下:
使用 pnpm create vite 初始化项目,模板为 react-ts。
配置 TypeScript 严格模式,@/* 路径别名指向 src/。
配置 ESLint(eslint:recommended + @typescript-eslint/recommended + react-hooks/recommended + prettier)。
配置 Prettier(singleQuote: true, semi: true, trailingComma: 'all', printWidth: 100)。
配置 Husky + lint-staged,在提交前自动执行 ESLint 和 Prettier。
配置环境变量 VITE_API_BASE_URL(开发环境与生产环境不同),并封装 HttpClient 类(GET/POST/PUT/DELETE),自动携带 Authorization token。
创建完整的目录结构(api/、components/、hooks/、pages/、router/、stores/、styles/、types/、utils/)。
使用 React Router v6 配置路由(首页 /、用户页 /users、404 页面),所有页面组件懒加载。
创建全局 CSS 变量(颜色、间距、圆角、字体)和基础样式重置。
任务要求 :请写出一段完整的中文提示词 ,发送给 AI,使其生成符合上述要求的完整项目初始化的所有配置文件和核心代码文件。提示词中需明确指定每个文件的内容和职责。
三、Agent 模式下的提示词示例
你是一个资深前端开发 Agent。请创建一个 React + TypeScript + Vite 企业级项目骨架。需要生成/修改以下文件:
使用 pnpm create vite 初始化项目(模板 react-ts),安装额外依赖:react-router-dom、zustand、husky、lint-staged、eslint-plugin-react-hooks、@typescript-eslint/eslint-plugin、@typescript-eslint/parser、eslint-config-prettier。
tsconfig.json:严格模式,paths: { "@/*": ["src/*"] },noUnusedLocals: true,noUnusedParameters: true。
vite.config.ts:引入 react 插件,resolve.alias 配置 @ 指向 src。
.eslintrc.cjs:extends 包含 eslint:recommended、plugin:@typescript-eslint/recommended、plugin:react-hooks/recommended、prettier。rules 禁止 console.log,允许 warn/error。
.prettierrc:singleQuote: true,semi: true,trailingComma: 'all',printWidth: 100。
.husky/pre-commit:npx lint-staged。
package.json:lint-staged 配置(*.{ts,tsx} 执行 eslint --fix + prettier --write,*.{css,md,json} 执行 prettier --write)。scripts 包含 dev、build(tsc && vite build)、lint、lint:fix、format、typecheck、prepare。
.env.development 和 .env.production:分别设置 VITE_API_BASE_URL。
src/vite-env.d.ts:扩展 ImportMetaEnv 类型。
src/api/client.ts:HttpClient 类(GET/POST/PUT/DELETE),自动拼接 API_BASE_URL,处理查询参数,自动注入 token,错误处理。
src/api/user.api.ts:使用 HttpClient 封装用户 API。
src/styles/variables.css 和 src/styles/globals.css:全局变量和基础样式。
src/router/index.tsx:React Router v6,createBrowserRouter,懒加载 Home、Users、NotFound 页面,Suspense 包裹。
src/pages/Home/index.tsx、src/pages/Users/index.tsx、src/pages/NotFound/index.tsx:简单的占位页面组件。
src/App.tsx 和 src/main.tsx:引入路由和全局样式。
创建完整的目录结构(空文件夹:components/ui/、components/layout/、hooks/、stores/、utils/、types/)。在每个目录下放置 .gitkeep 文件或 index.ts 占位,或仅说明目录结构。 所有文件添加必要的 JSDoc 注释,确保可直接运行。完成后列出所有文件内容。
课后练习答案 一、概念自测答案
B
解析:tsconfig.json 的 paths 仅用于 TypeScript 类型检查,实际模块解析由打包工具处理。Vite 需要在 vite.config.ts 中配置 resolve.alias 才能正确解析 @/ 路径。两者必须同步。
B
解析:lint-staged 仅对 git add 暂存的文件运行检查,速度快,适合 pre-commit 钩子。A 描述的是 npm run lint,C 和 D 无关。
**tsc**(或 tsc --noEmit、vue-tsc --noEmit)
解析:在构建前执行 tsc(配合 --noEmit 仅检查不输出文件)确保所有类型通过,否则构建失败。
A、B
解析:node_modules/ 和 dist/ 是生成目录,不应提交。.env.development 通常提交(作为团队共享的开发环境配置模板),pnpm-lock.yaml 是锁文件,必须提交以保持一致依赖。
二、AI 编程任务参考答案(提示词示例)
示例提示词 : “请从零创建一个 React + TypeScript + Vite 企业级项目骨架。要求:
使用 pnpm create vite 初始化 react-ts 模板。
TypeScript 严格模式,@/ 别名。
ESLint(推荐配置 + prettier)+ Prettier(单引号/分号/尾逗号/100宽)+ Husky + lint-staged 拦截。
.env 管理 VITE_API_BASE_URL,封装 HttpClient 并导出 user API。
目录结构:api/components/hooks/pages/router/stores/styles/types/utils。
React Router v6 懒加载路由。
全局 CSS 变量和重置样式。
输出所有配置文件、API 模块、路由、页面占位组件和样式文件的完整内容。”