做前端这些年,我一直坚持一个理念:一套靠谱的企业级项目模板,比十篇零散的配置教程都管用。尤其是当你从零接手一个新项目,或者团队里来了新同学,一套结构清晰、规范统一、开箱即用的 React + Vite 模板,能省掉大量重复造轮子和互相磨合的成本。今天这篇就完整梳理一下我搭建这套模板的整个过程,包括技术选型、目录设计、工程化封装、构建优化,以及中间踩过的那些坑。
这套模板面向的不是"能跑就行"的 Demo,而是真实业务环境下的长期维护。所以它需要满足几个硬指标:新成员加入后能快速上手、代码风格统一可 review、环境切换零成本、构建产物可观测可优化。适合正准备搭新项目的团队,也适合想系统了解企业级前端工程化套路的同学参考。下面我按当时的搭建顺序来聊。
1. 为什么值得花时间做一套企业级模板
很多人第一反应是"用脚手架不就行了,create vite 一把梭"。确实,Vite 官方脚手架可以快速生成一个基础项目,但距离"企业级"还差得很远。我见过太多项目是从官方模板起步,半年后变成了一个谁都不敢动的大杂烩。
1.1 没有模板时,团队每天都在做重复劳动
先说说我在某公司经历过的真实场景。当时团队同时维护三个中后台项目,每个项目都是不同同学各自搭的:一个用 Webpack,一个用 Vite,还有一个是 CRA 改的。这带来什么问题?
最直观的就是维护成本翻倍。三个项目的 ESLint 规则不一致,导致 code review 的时候一半时间在争论"到底该用单引号还是双引号"、"分号要不要"这种完全没有意义的问题。第二个问题是公共能力没法沉淀。请求层封装每个项目各写一套,A 项目的登录失效处理逻辑写在了拦截器里,B 项目写在了路由守卫里,完全没法互相借鉴。第三个问题是新人上手慢。新同学入职,光是搞清楚每个项目的目录结构和启动方式就要花两三天,更别说理解各自不同的构建配置了。
这时候才意识到,模板不是写给机器看的,是写给团队看的。它的本质是把团队对工程化的共识、对代码风格的约束、对公共能力的沉淀,固化成一个所有人都能直接使用的起点。
1.2 企业级模板的核心评判标准
那什么才算"企业级模板"?我自己的评判标准有四条,缺一不可:
- 规范统一:代码风格、Git 提交规范、目录结构有明确约定,并且通过工具强制落地,而不是靠自觉。这一条解决的是"团队协作的摩擦力"。
- 开箱即用:请求层、路由、状态管理、环境变量这些业务开发天天要用的能力,模板里已经封装好,业务同学只写业务代码。这一条解决的是"重复造轮子"。
- 可扩展:新增一个页面、新增一个微应用、接入一套新的权限体系,模板能撑得住,不需要伤筋动骨。这一条解决的是"项目长大后的失控风险"。
- 可观测:构建产物有多大、哪个依赖占了最多体积、打包耗时多少,都是可以量化的,出了问题能快速定位。这一条解决的是"性能问题无从下手"。
我在搭建之前先把这四条写下来,后面所有决策都拿它们来卡一遍。比如选 TypeScript 还是 JavaScript,TS 在这四条标准下几乎是必选——它在"规范统一"和"可扩展"上的收益是实打实的,后面我会详细说。
2. 技术选型:Vite + React 的前前后后
技术选型这一步,最容易犯的错误是"追新"。某个工具社区热度高,就无脑引入;某个配置别人都说好,就直接照抄。我的原则是:每一项选型都对应一个明确的痛点,不解决问题的技术再新也不碰。
2.1 为什么最终选了 Vite
之前的团队项目用 Webpack,构建速度在项目变大之后确实让人着急。开发环境下改一行代码,热更新等三五秒是常事;如果是改了公共组件,那等待时间更酸爽。后来在一个新项目上试了 Vite,开发启动速度基本是秒级,冷启动甚至不需要完整打包整个项目。
Vite 的原理是用浏览器原生的 ES Module,开发环境直接按需加载模块,省掉了 Webpack 那种"先把所有模块打包再启动 Dev Server"的步骤。生产构建则用 Rollup,打包能力和质量也经过了大量项目验证。对日常开发体验来说,这个优势是决定性的。
有人担心 Vite 生态不够成熟,生产环境出问题。我的实际体验是,随着 Vite 迭代到 4.x、5.x 版本,周边生态已经非常完善。常用的 React 插件、代码分割、静态资源处理、多页面构建这些能力都有成熟方案。在我维护的模板里,生产构建一直很稳定,没有遇到过因为 Vite 本身导致的线上问题。
提示:Vite 版本升级很快,模板里要锁定主版本号,并且把升级测试纳入流程。我见过因为从 4.x 升到 5.x 导致插件兼容问题的情况,后面"常见问题"部分会具体说。
2.2 React 生态里的配套选择
React 选 18.x 版本,没有悬念,函数组件 + Hook 已经是主流模式,类组件时代的一些配套库也逐步淡出。我直接把模板里所有组件都按函数组件 + TypeScript 的方式写,这样类型推导最自然,IDE 提示也最友好。
配套选型上,几个关键决策如下:
- 路由:React Router 6,这是事实标准。6.x 的 API 比 5.x 清爽很多,路由配置改成对象数组,配合懒加载和嵌套路由,写权限控制也方便。
- 状态管理:Zustand。相比 Redux Toolkit,Zustand 的样板代码少得多,不需要 Provider 包裹组件树,直接在组件里调用 Hook 就能拿状态和修改方法。对于中后台项目,它的数据流足够清晰,团队上手成本也低。
- 请求层:axios 二次封装。拦截器统一处理 token 注入、错误提示、登录失效跳转,业务代码只关心数据和错误类型。
- CSS 方案:CSS Modules + 全局主题变量。CSS Modules 避免类名冲突,主题变量用于统一维护颜色、间距、字体等设计规范。
- 代码规范:ESLint 9 的 flat config + Prettier + Husky + lint-staged。这个组合现在是主流,配置起来比旧的 eslintrc 略繁琐,但可维护性更好。
- TypeScript:必选,严格模式。模板里设置了
strict: true,这也是很多团队不敢开严格模式的地方,但我建议从一开始就开,否则项目写到中后期再想开,改动量大到几乎不可能完成。
2.3 目录结构:分层清晰是模板的地基
目录结构是模板最容易忽视却最影响长期维护的部分。我见过太多项目,页面组件、业务组件、公共组件、工具函数全部堆在 src 下的几个固定目录里,时间一长,谁都不敢删文件,因为不知道哪里还在引用。
我使用的目录结构如下:
src/ ├── api/ # 接口定义,按业务域划分 ├── assets/ # 静态资源 ├── components/ # 通用基础组件 ├── hooks/ # 通用自定义 Hooks ├── layouts/ # 布局组件 ├── pages/ # 页面级组件 ├── router/ # 路由配置 ├── store/ # 全局状态 ├── styles/ # 全局样式与主题变量 ├── types/ # 全局类型定义 ├── utils/ # 工具函数 ├── App.tsx # 应用根组件 └── main.tsx # 入口文件分层的核心逻辑是按职责拆分,而不是按类型堆放。比如 api 目录按业务域再分子目录——user、order、goods 各自一个文件,接口变更时能快速定位。pages 目录和路由一一对应,组件目录只放跨页面复用的通用组件,页面内部的局部组件就写在页面目录下的 components 子目录里。这个约定非常重要,否则复用和归属问题又会回到"哪里都用、哪里都乱"的老路。
3. 工程化配置的完整落地
选型定下来,接下来就是把这些技术真正配置进模板。这部分最容易碰到的坑是:网上教程版本不一,照着配置跑不起来。所以我直接把最终能跑通的配置完整贴出来,并解释每个关键配置的作用。
3.1 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') } }, server: { host: true, port: 3000, open: false, proxy: { '/api': { target: env.VITE_API_TARGET || 'http://localhost:8080', changeOrigin: true } } }, build: { target: 'es2018', sourcemap: false, chunkSizeWarningLimit: 1500 } } })几个关键点的说明:
loadEnv配合函数式 defineConfig,可以把.env里的变量读取到配置里。比如代理目标地址、CDN 前缀这些,不需要写死,换环境时只改.env文件即可。resolve.alias配@指向src,这是全项目的路径基础。如果没有这个别名,代码里全是../../../,维护起来极其痛苦。server.proxy把/api开头的请求代理到后端,解决开发环境的跨域问题。注意changeOrigin: true必须开,否则后端拿到请求的 Host 还是前端地址,部分校验严格的接口会报错。build.target设为es2018,兼顾了现代浏览器和构建产物的语法兼容性。不需要追求 esnext,因为最终产物还要考虑用户实际使用的浏览器。
3.2 TypeScript 的严格模式
模板的tsconfig.json里,strict: true是硬性要求。严格模式最直接的影响是:类型错误会在编码阶段就被发现,而不是等到运行时才炸。
我举个真实例子。之前某个项目里,有个函数接收一个可能是undefined的参数,非严格模式下编译没问题,但运行时偶尔报错,排查了很久才发现是某个返回值可能为空。开启严格模式后,TypeScript 会直接提示"对象可能为 undefined",逼着你在写代码的时候就处理边界情况。
配置上还有两个小点容易被忽略:
{ "compilerOptions": { "strict": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] }, "types": ["vite/client"] } }paths和 Vite 的alias要同步配置,这样 IDE 里点击导入路径可以跳转到真实文件。types: ["vite/client"]则让 TypeScript 认识import.meta.env这类 Vite 特有的类型,避免到处报红。
3.3 代码规范:ESLint + Prettier + Husky
代码规范不是靠嘴说,而是靠工具强制。模板里整合了完整的代码检查链条。
先看 ESLint 的 flat config(ESLint 9 及以上版本使用 eslint.config.js):
import js from '@eslint/js' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' export default tseslint.config({ extends: [js.configs.recommended, tseslint.configs.recommended], files: ['**/*.{ts,tsx}'], plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh }, rules: { ...reactHooks.configs.recommended.rules, 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true } ] } })这里最关键的是react-hooks插件,它能在开发阶段就识别出 Hooks 调用顺序错误、依赖数组遗漏之类的隐患。这不是风格问题,是运行时 bug 的预防。
Prettier 负责格式化,统一代码风格。配合.prettierrc配置:
{ "semi": false, "singleQuote": true, "printWidth": 100, "trailingComma": "none", "endOfLine": "lf" }注意:我特意选了"无分号 + 单引号",这纯属团队约定,不是对错问题。关键是全项目统一,并且把冲突规则在 ESLint 里关掉(比如
@stylistic/semi),否则 ESLint 和 Prettier 会打架,格式化一遍,ESLint 报一遍错,体验极差。
Husky 在package.json里配置 Git hooks。我用了三种 hook:
{ "husky": { "hooks": { "pre-commit": "lint-staged", "commit-msg": "node scripts/check-commit-message.mjs" } } }lint-staged只对暂存区的文件做检查和格式化,这样即使老代码不规范,也不会阻塞你改一个文件的简单提交。commit-msg则校验提交信息格式,模板要求feat(scope): description这样的 Conventional Commits 格式,方便之后自动生成 changelog。
3.4 多环境变量方案
中后台项目几乎都有多套环境:本地开发、测试环境、预发环境、生产环境。Vite 天然支持按模式加载不同环境文件,模板里直接复用了这个机制。
环境文件按命名约定组织:
.env:所有环境共享的基础变量.env.development:本地开发环境.env.production:生产构建.env.staging:预发环境
内容示例:
# .env.development VITE_API_BASE_URL=/api VITE_LOG_LEVEL=debug# .env.production VITE_API_BASE_URL=https://api.example.com VITE_LOG_LEVEL=error变量以VITE_开头,Vite 才会在打包时把它注入到import.meta.env里。非VITE_开头的变量不会被暴露到客户端代码中,这是一个安全边界。比如后端地址直接在.env里写完整域名也建议用VITE_前缀,因为没有前缀的变量就算你引用了,构建后也拿不到。
模板里还封装了一个获取环境变量的工具函数,业务代码统一通过它读变量,避免到处直接写import.meta.env:
// src/utils/env.ts export function getEnv(name: string, fallback = ''): string { const value = import.meta.env[name] return value !== undefined ? value : fallback }4. 业务能力的骨架封装
模板的工程化配置只是地基,真正让开发效率提升的是业务骨架的封装。这一层决定了业务同学每天写的代码量,也决定了后续扩展的顺畅程度。
4.1 请求层的统一封装
axios 封装几乎是每个项目必做的第一件事,但封装的细节差别很大。我总结了一套比较稳的方案。
核心思路是:在拦截器里解决跨切面的问题,业务代码只负责传参和拿数据。
// src/utils/request.ts import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 30000 }) request.interceptors.request.use((config) => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( (response) => { const res = response.data if (res.code && res.code !== 0) { // 业务错误统一处理 return Promise.reject(new Error(res.message || '请求失败')) } return res }, (error) => { if (error.response?.status === 401) { // 登录失效:清除本地登录态,跳转登录页 localStorage.removeItem('token') window.location.href = '/login' return Promise.reject(new Error('登录状态已失效')) } return Promise.reject(error) } ) export function get<T>(url: string, params?: Record<string, unknown>) { return request.get<T, T>(url, { params }) } export function post<T>(url: string, data?: Record<string, unknown>) { return request.post<T, T>(url, data) }几个细节值得展开:
第一,统一响应包装。国内企业级项目的接口几乎都有统一的{ code, data, message }包装结构。拦截器里直接拆掉这层包装,业务代码拿到的就是纯数据,不需要每个页面都写一遍res.data.data。
第二,网络错误和业务错误分流。业务错误(code 不为 0)在响应拦截器里统一 reject;网络错误(超时、断网、5xx)走 axios 的 error 分支。两层分开处理,不会互相混淆。
第三,类型透出。get<T>和post<T>通过类型参数把返回值类型透出,业务侧写接口时能获得完整的类型提示。模板里的 api 目录就是基于这个封装的:
// src/api/user.ts import { get, post } from '@/utils/request' export interface UserInfo { id: number name: string avatar: string } export function fetchUserInfo() { return get<UserInfo>('/user/info') } export function updateUserProfile(data: Partial<UserInfo>) { return post<UserInfo>('/user/profile', data) }4.2 路由与权限控制
中后台项目的路由几乎都离不开权限。模板里采用的方式是:静态路由 + 动态路由结合。登录页、404 页等公开页面用静态路由,业务页面由后端返回的权限列表动态生成。
路由配置是 React Router 6 的对象式写法:
// src/router/index.tsx import { createBrowserRouter, Navigate } from 'react-router-dom' import AuthGuard from '@/components/AuthGuard' import Login from '@/pages/Login' import Dashboard from '@/pages/Dashboard' const routes = [ { path: '/login', element: <Login /> }, { path: '/', element: <AuthGuard />, children: [ { index: true, element: <Navigate to="/dashboard" replace /> }, { path: 'dashboard', element: <Dashboard /> } ] }, { path: '*', element: <Navigate to="/404" replace /> } ] export const router = createBrowserRouter(routes)权限控制的核心是AuthGuard组件。它充当路由守卫:没有登录态就重定向到登录页,登录态失效就统一处理。这块看起来简单,但在实际项目中有一个容易被忽略的点——动态路由的权限过滤。
假设后端返回当前用户的菜单权限列表,模板里在拿到权限后只注册有权限的路由:
function filterRoutesByPermission(allRoutes, permissionCodes) { return allRoutes .filter((route) => { return !route.meta?.permission || permissionCodes.includes(route.meta.permission) }) .map((route) => { if (route.children) { return { ...route, children: filterRoutesByPermission(route.children, permissionCodes) } } return route }) }这个方案避免了"前端隐藏按钮但路由仍可直达"的安全漏洞——没有权限的页面在路由层就不可达,而不仅仅是菜单里不显示。
4.3 状态管理选型与封装
模板选 Zustand,理由是它对 TypeScript 的支持非常自然,心智负担小。看一个实际封装的 store 示例:
// src/store/useUserStore.ts import { create } from 'zustand' import { fetchUserInfo, type UserInfo } from '@/api/user' interface UserState { userInfo: UserInfo | null loading: boolean fetchUser: () => Promise<void> clearUser: () => void } export const useUserStore = create<UserState>((set) => ({ userInfo: null, loading: false, fetchUser: async () => { set({ loading: true }) try { const userInfo = await fetchUserInfo() set({ userInfo }) } finally { set({ loading: false }) } }, clearUser: () => set({ userInfo: null }) }))组件里使用起来很直接:
const userInfo = useUserStore((state) => state.userInfo) const fetchUser = useUserStore((state) => state.fetchUser)对比 Redux Toolkit,Zustand 不需要配置 Provider、不需要写 action creator,状态定义在一个文件里就能看全。模板里的 Zustand store 都放在src/store目录,按业务域拆分——用户信息一个文件、全局偏好设置一个文件,互不干扰。
有一个小技巧:如果只是跨组件传少量数据,优先考虑提升 state 到共同父组件,而不是直接开全局 store。全局状态越多,组件复用性越差。模板里在 README 中明确写了这个约定,避免团队过度使用。
4.4 全局样式与主题体系
样式这块,中后台项目最大的痛点是颜色、间距、字体这些设计值不统一。模板里直接用 CSS 变量做了一套主题体系:
:root { --color-primary: #1677ff; --color-success: #52c41a; --color-warning: #faad14; --color-error: #ff4d4f; --color-text: rgba(0, 0, 0, 0.88); --color-text-secondary: rgba(0, 0, 0, 0.65); --color-bg: #f5f5f5; --border-radius: 6px; --spacing-unit: 8px; --font-size-base: 14px; }业务组件里用var(--color-primary)取颜色,换肤时只需要覆盖这些变量。这套方案的收益是:设计规范真正落地到了代码层面,而不是停留在设计稿里。还支持做暗黑模式,只要在某个容器上覆盖变量集合即可。
CSS Modules 的使用约定是:全局样式(重置样式、主题变量)放 styles 目录,组件样式用 CSS Modules。避免把全局样式和组件样式混在一个文件里,这是很多项目混乱的根源。
5. 构建优化与日常开发体验
模板不只是开发环境跑得快,生产构建的产物体积和加载性能同样重要。这一节说说我做了哪些构建优化,以及怎么量化这些优化的效果。
5.1 拆包与按需加载
React 常用库打包出来的基础体积不小。如果所有代码打进同一个 chunk,首屏加载会被迫下载包含所有依赖的 JS 文件。模板里用manualChunks拆分依赖:
build: { rollupOptions: { output: { manualChunks: { react: ['react', 'react-dom', 'react-router-dom'], axios: ['axios'], zustand: ['zustand'] } } } }这样浏览器可以单独缓存 react 相关的 chunk,业务代码更新时,react 的 chunk 不变,缓存依然有效。实际项目中我把这个策略配合路由懒加载一起用:
const Dashboard = lazy(() => import('@/pages/Dashboard'))每个页面独立成 chunk,首屏只加载当前页面需要的代码。这套组合下来,中后台项目首屏 JS 体积普遍能控制在 300KB 以内(gzip 后),这个体量在正常网络环境下基本秒开。
5.2 产物体积的可视化分析
光凭感觉优化体积是不行的。模板里集成了rollup-plugin-visualizer,在构建时生成依赖体积分析报告:
import { visualizer } from 'rollup-plugin-visualizer' plugins: [ react(), visualizer({ open: true, filename: 'dist/report.html' }) ]跑一次npm run build,浏览器会打开一个可视化的体积分布图,每个依赖、每个页面 chunk 占多少体积一目了然。我在实际使用中靠这个工具发现过几次意外情况:某个页面不小心 import 了整个图表库、某个老依赖带入了很大的子依赖,都是通过体积报告定位并解决的。
5.3 开发环境的细节调优
除了构建优化,开发体验也有几个值得调的点。
第一,开启依赖预构建的监控。Vite 默认会把 node_modules 里的依赖预构建成缓存的 esbuild 产物。但你改了vite.config.ts或者新增了依赖之后,偶尔会遇到"缓存没刷新"的问题,表现为页面报奇怪的模块错乱。模板里在package.json的启动脚本中加了--force参数:
{ "scripts": { "dev": "vite --force", "build": "tsc -b && vite build", "preview": "vite preview" } }--force会强制重新预构建依赖,代价是启动慢个几秒,但换来的是稳定性,很值。
第二,构建前先跑类型检查。构建脚本里的tsc -b很关键。Vite 的 esbuild 做转译时不做类型检查,如果构建脚本里不加这步,类型错误会悄悄溜到代码里,直到运行时才暴露。加上tsc -b后,类型错误会导致构建失败,强制大家修复。这个习惯坚持下来,项目的类型健康度会高很多。
6. 高频问题与排查记录
整理模板的过程中,我积累了不少问题的排查经验。这一节挑几个高频的写出来,方便直接对照排查。
6.1 Vite 版本和插件兼容问题
Vite 从 4.x 升到 5.x 时,@vitejs/plugin-react也跟着发版。如果你的 vite 和 react 插件版本不匹配,最常见的现象是启动后页面白屏、控制台报@vitejs/plugin-react can't detect preamble之类错误,或者热更新失效。
排查思路很直接:
npm ls vite @vitejs/plugin-react看版本是否匹配。模板里在package.json中锁定了主版本,并用~或^合理控制次版本更新范围。我的建议是:Vite 这类核心工具锁定主版本,升级时单独排期并跑一次完整的构建和冒烟测试。
6.2 依赖预构建的"幽灵问题"
Vite 的预构建缓存偶尔会带来比较隐蔽的 bug。我遇到过的一个典型案例:新增了一套 Monorepo 里的本地包依赖后,页面访问某个组件时总报"模块无法解析",但实际路径完全正确。清掉node_modules/.vite缓存后问题消失。
所以排查这类问题,第一个动作就是:
rm -rf node_modules/.vite如果还不行,再删node_modules重装。这个顺序能解决 90% 的模块解析诡异问题。
6.3 部署环境的路径问题
Vite 构建产物默认引用根路径/assets/xxx.js。如果部署在子目录下,比如https://example.com/console/,资源全部 404。解决办法是在.env.production里设置 base:
# .env.production VITE_BASE_URL=/console/然后在vite.config.ts里读取:
base: env.VITE_BASE_URL || '/'这个配置做得还算顺手,但容易漏。我见过不止一个项目上线后发现样式和脚本全挂,排查到最后就是base没配。
注意:如果你用了
createBrowserRouter,路由的basename也要和base保持一致。不然刷新子页面时,服务器路由和前端路由对不上,会出现 404。部署到 Nginx 时还需要配置 try_files 回退到 index.html,这个属于运维协作的一部分,模板 README 里单独写了一段说明。
6.4 缓存导致的"新版本不生效"
生产环境里,用户打开页面发现还是旧版本,这是中后台项目非常常见的反馈。根因是浏览器对 JS/CSS 资源的缓存策略,以及通过 Nginx 下发时带上了强缓存头。
模板里的做法是:保证 Vite 构建产物的文件名带 hash(默认就是带 hash 的),同时要求部署时给index.html设置no-cache。hash 变化后,新 index.html 引用的就是新文件名,和旧资源自然隔离,也给浏览器识别新版本留出了余地。
最后再说两句
框架和工具迭代得很快,但工程化的核心问题始终是那几个:团队协作的效率、代码质量的底线、项目长期演进的能力。模板只是把这些共识固化下来,真正用好它的关键是持续维护——每一次踩坑、每一个新人提出的疑问,都应该回流到模板里,而不是让它在某个版本上变成一个"文物"。
我个人的体会是,搭模板最有价值的部分不是最终产出的这套配置,而是过程中逼自己去想清楚"为什么这么选"的那些瞬间。等到你再遇到一个"能不能引入某个新工具"的提议时,能第一时间判断它在这个模板的体系里是加分还是添乱,这比任何单一技术都要值钱。希望这份梳理能给你一些参考,哪怕只是其中一节让你少踩一个坑,也算没白写。