前端框架vue3,vite 开发前端项目实践步骤指南

作者:慧一居士日期:2026/8/1

Vue 3 + Vite 前端项目实践指南

适用版本(截至 2026 年 7 月):Vue 3.5+ | Vite 6/7 | TypeScript 5.6+ | Node.js ^20.19.0 || >=22.12.0
目标:从零搭建一个可投入生产的工程化项目,覆盖「初始化 → 架构 → 规范 → 联调 → 构建部署」全流程。


全景路线图

1环境准备 ──▶ 脚手架创建 ──▶ 目录规划 ──▶ Vite 配置 ──▶ 路由/状态/请求层
2                                                          
3部署上线 ◀── 构建优化 ◀── 代码规范 ◀── 组件开发实践 ◀──────┘
4

Step 01 · 环境准备

① 安装 Node.js(版本必须满足 ^20.19.0 || >=22.12.0,这是 Vue 官方文档的硬性要求)

推荐使用 nvm(Windows 用 nvm-windows)管理多版本:

1nvm install 22
2nvm use 22
3node -v   # 验证
4npm -v
5

② 安装 pnpm(2026 年社区主流选择:磁盘占用小、安装快、幽灵依赖管控严格)

1npm install -g pnpm
2pnpm config set registry https://registry.npmmirror.com   # 国内镜像加速
3

③ 编辑器:VS Code + 插件 Vue - Official(原 Volar,务必禁用旧的 Vetur)。


Step 02 · 创建项目

两条路线,按需选择:

方式命令特点
create-vue(官方推荐)pnpm create vue@latest交互式勾选 TS / Router / Pinia / ESLint / Prettier / Vitest,开箱即用
create-vite(极简模板)pnpm create vite my-app --template vue-ts只给最干净的骨架,一切自己装,适合想完全掌控配置的人

以官方脚手架为例:

1pnpm create vue@latest
2
3# 交互式选项(建议新手全部按下面选):
4#  Project name:  my-vue-app
5#  Add TypeScript?  Yes
6#  Add JSX Support?  No(需要时再开)
7#  Add Vue Router?  Yes
8#  Add Pinia?  Yes
9#  Add Vitest for Unit Testing?  Yes
10#  Add ESLint?  Yes(生成 ESLint 9 扁平配置 eslint.config.js)
11#  Add Prettier?  Yes
12
13cd my-vue-app
14pnpm install
15pnpm dev        # 浏览器打开 http://localhost:5173
16

💡 实践建议:团队项目优先用 create-vue——它生成的 ESLint 9 flat config、TS 配置、env.d.ts 都是官方校准过的,能避开大量新手坑。


Step 03 · 规划目录结构

脚手架的默认结构只够写 Demo,正式项目建议重构为「按职责分层」:

1src/
2├── api/              # 接口请求模块(按业务域拆分:user.ts、order.ts)
3├── assets/           # 静态资源(图片、字体、全局样式)
4   └── styles/
5       ├── variables.scss   # 设计变量
6       └── reset.scss       # 样式重置
7├── components/       # 全局通用组件(BaseButton、AppHeader…)
8├── composables/      # 组合式函数(useXxx,逻辑复用核心)
9├── layouts/          # 布局组件(DefaultLayout、BlankLayout)
10├── router/           # 路由配置与守卫
11   ├── index.ts
12   └── routes.ts
13├── stores/           # Pinia 状态模块
14   └── modules/
15├── types/            # 全局 TS 类型声明
16├── utils/            # 工具函数(request.ts、format.ts、storage.ts)
17├── views/            # 页面级组件(按路由组织)
18   ├── home/
19   └── user/
20├── App.vue
21└── main.ts
22

原则一句话:**views**** 只放页面,可复用的进 components,可复用的逻辑进 ****composables**


Step 04 · 核心配置 vite.config.ts

1import { fileURLToPath, URL } from 'node:url'
2import { defineConfig, loadEnv } from 'vite'
3import vue from '@vitejs/plugin-vue'
4import AutoImport from 'unplugin-auto-import/vite'
5import Components from 'unplugin-vue-components/vite'
6
7export default defineConfig(({ mode }) => {
8  const env = loadEnv(mode, process.cwd())
9
10  return {
11    plugins: [
12      vue(),
13      // 自动导入 ref/computed/watch 等,免去满屏 import
14      AutoImport({ imports: ['vue', 'vue-router', 'pinia'] }),
15      // 组件按需自动注册,无需手动 import + components 声明
16      Components({ dirs: ['src/components'] }),
17    ],
18    resolve: {
19      alias: {
20        '@': fileURLToPath(new URL('./src', import.meta.url)),
21      },
22    },
23    css: {
24      preprocessorOptions: {
25        scss: {
26          // 全局注入设计变量,组件内无需重复 @use
27          additionalData: `@use "@/assets/styles/variables.scss" as *;`,
28        },
29      },
30    },
31    server: {
32      port: 5173,
33      open: true,
34      // 开发代理:解决跨域,指向真实后端
35      proxy: {
36        '/api': {
37          target: env.VITE_API_BASE_URL,
38          changeOrigin: true,
39          rewrite: (path) => path.replace(/^\/api/, ''),
40        },
41      },
42    },
43    build: {
44      target: 'es2020',
45      sourcemap: false,
46      chunkSizeWarningLimit: 1000,
47      rollupOptions: {
48        output: {
49          // 手动分包:第三方库独立 chunk,利用浏览器缓存
50          manualChunks: {
51            vue: ['vue', 'vue-router', 'pinia'],
52          },
53        },
54      },
55    },
56  }
57})
58

同步配置 TS 路径别名(tsconfig.app.json):

1{
2  "compilerOptions": {
3    "baseUrl": ".",
4    "paths": { "@/*": ["src/*"] }
5  }
6}
7

Step 05 · 多环境变量

根目录创建三个文件(注意:只有 VITE_ 前缀的变量才会暴露给客户端代码):

1# .env.development
2VITE_API_BASE_URL=http://localhost:8080
3VITE_APP_TITLE=我的应用(开发)
4
5# .env.production
6VITE_API_BASE_URL=https://api.example.com
7VITE_APP_TITLE=我的应用
8

补充类型提示(src/types/env.d.ts),让 import.meta.env 有智能补全:

1/// <reference types="vite/client" />
2interface ImportMetaEnv {
3  readonly VITE_API_BASE_URL: string
4  readonly VITE_APP_TITLE: string
5}
6

Step 06 · 路由:Vue Router

src/router/routes.ts —— 全部使用路由懒加载,首屏只加载当前页面:

1import type { RouteRecordRaw } from 'vue-router'
2
3export const routes: RouteRecordRaw[] = [
4  {
5    path: '/',
6    component: () => import('@/layouts/DefaultLayout.vue'),
7    children: [
8      { path: '', name: 'Home', component: () => import('@/views/home/index.vue') },
9      {
10        path: 'user/:id',
11        name: 'UserDetail',
12        component: () => import('@/views/user/detail.vue'),
13        meta: { title: '用户详情', requiresAuth: true },
14      },
15    ],
16  },
17  { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/404.vue') },
18]
19

src/router/index.ts —— 全局守卫统一处理标题与鉴权:

1import { createRouter, createWebHistory } from 'vue-router'
2import { routes } from './routes'
3import { useUserStore } from '@/stores/modules/user'
4
5const router = createRouter({
6  history: createWebHistory(import.meta.env.BASE_URL),
7  routes,
8  scrollBehavior: () => ({ top: 0 }),
9})
10
11router.beforeEach((to) => {
12  document.title = (to.meta.title as string) ?? import.meta.env.VITE_APP_TITLE
13  const userStore = useUserStore()
14  if (to.meta.requiresAuth && !userStore.isLoggedIn) {
15    return { name: 'Login', query: { redirect: to.fullPath } }
16  }
17})
18
19export default router
20

Step 07 · 状态管理:Pinia

推荐 Setup Store 写法(与 <script setup> 心智一致,TS 推导更顺):

src/stores/modules/user.ts

1import { ref, computed } from 'vue'
2import { defineStore } from 'pinia'
3import { fetchUserInfo, login as loginApi } from '@/api/user'
4import type { UserInfo, LoginParams } from '@/types/user'
5
6export const useUserStore = defineStore('user', () => {
7  // state
8  const token = ref(localStorage.getItem('token') ?? '')
9  const profile = ref<UserInfo | null>(null)
10
11  // getters
12  const isLoggedIn = computed(() => !!token.value)
13
14  // actions(支持 async)
15  async function login(params: LoginParams) {
16    const { data } = await loginApi(params)
17    token.value = data.token
18    localStorage.setItem('token', data.token)
19  }
20
21  async function loadProfile() {
22    const { data } = await fetchUserInfo()
23    profile.value = data
24  }
25
26  function logout() {
27    token.value = ''
28    profile.value = null
29    localStorage.removeItem('token')
30  }
31
32  return { token, profile, isLoggedIn, login, loadProfile, logout }
33})
34

main.ts 挂载:

1import { createApp } from 'vue'
2import { createPinia } from 'pinia'
3import App from './App.vue'
4import router from './router'
5
6createApp(App).use(createPinia()).use(router).mount('#app')
7

💡 经验法则:只有「跨页面共享、需要持久化、服务端缓存」的数据才进 Pinia;组件内部状态留在 ref 里即可,不要滥用全局状态。


Step 08 · 请求层:Axios 二次封装

src/utils/request.ts —— 拦截器统一处理 Token、错误、Loading:

1import axios, { AxiosError } from 'axios'
2import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
3import { useUserStore } from '@/stores/modules/user'
4import { ElMessage } from 'element-plus'
5
6const service: AxiosInstance = axios.create({
7  baseURL: '/api',          // 开发走 proxy,生产由部署层转发
8  timeout: 15_000,
9})
10
11// 请求拦截:注入 Token
12service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
13  const { token } = useUserStore()
14  if (token) config.headers.Authorization = `Bearer ${token}`
15  return config
16})
17
18// 响应拦截:剥离 data  + 统一错误出口
19service.interceptors.response.use(
20  (res) => {
21    const { code, data, message } = res.data
22    if (code !== 0) {
23      ElMessage.error(message ?? '请求失败')
24      return Promise.reject(new Error(message))
25    }
26    return data
27  },
28  (error: AxiosError) => {
29    if (error.response?.status === 401) {
30      useUserStore().logout()
31      location.href = '/login'
32    } else {
33      ElMessage.error(error.message || '网络异常,请稍后重试')
34    }
35    return Promise.reject(error)
36  },
37)
38
39export default service
40

src/api/user.ts —— 接口按业务域聚合,返回值必须标注类型

1import request from '@/utils/request'
2import type { UserInfo, LoginParams, LoginResult } from '@/types/user'
3
4export const login = (data: LoginParams) =>
5  request.post<unknown, LoginResult>('/auth/login', data)
6
7export const fetchUserInfo = () =>
8  request.get<unknown, UserInfo>('/user/profile')
9

Step 09 · 组件开发实践

**① 统一使用 ****<script setup lang="ts">**,配合 defineProps 泛型声明:

1<script setup lang="ts">
2interface Props {
3  title: string
4  status?: 'idle' | 'loading' | 'done'
5}
6const props = withDefaults(defineProps<Props>(), { status: 'idle' })
7
8const emit = defineEmits<{
9  confirm: [id: number]
10}>()
11</script>
12
13<template>
14  <section class="task-card">
15    <h3>{{ title }}</h3>
16    <button :disabled="props.status === 'loading'" @click="emit('confirm', 1)">
17      {{ props.status === 'loading' ? '处理中…' : '确认' }}
18    </button>
19  </section>
20</template>
21
22<style scoped lang="scss">
23.task-card { /* 样式隔离,配合 BEM  CSS Modules */ }
24</style>
25

② 逻辑复用抽成 Composable(这是 Vue 3 区别于 Vue 2 mixins 的核心红利):

src/composables/usePagination.ts

1import { ref, reactive } from 'vue'
2
3export function usePagination(fetcher: (page: number, size: number) => Promise<any[]>) {
4  const list = ref<any[]>([])
5  const loading = ref(false)
6  const pager = reactive({ page: 1, size: 20, total: 0 })
7
8  async function load() {
9    loading.value = true
10    try {
11      list.value = await fetcher(pager.page, pager.size)
12    } finally {
13      loading.value = false
14    }
15  }
16
17  return { list, loading, pager, load }
18}
19

③ 样式策略:组件内用 scoped;主题级变量收敛到 variables.scss 的 CSS 自定义属性,支持暗色模式一行切换。


Step 10 · 代码规范与提交约束

create-vue 已生成 ESLint 9 扁平配置(eslint.config.js),再补两道保险:

① Git Hooks(提交前自动检查 + 格式化):

1pnpm add -D lint-staged
2npx husky init
3
1// package.json
2{
3  "lint-staged": {
4    "*.{ts,vue}": ["eslint --fix", "prettier --write"]
5  }
6}
7
1# .husky/pre-commit
2pnpm lint-staged
3

② VS Code 保存自动修复.vscode/settings.json,团队共享):

1{
2  "editor.formatOnSave": true,
3  "editor.defaultFormatter": "esbenp.prettier-vscode",
4  "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" },
5  "eslint.useFlatConfig": true
6}
7

Step 11 · 构建、验收与部署

1pnpm build          # 产物输出到 dist/
2pnpm preview        # 本地起静态服务预览生产包 —— 必做!很多问题只在生产包暴露
3

部署前验收清单

  • pnpm build 无 TS 报错、无 chunk 超大警告
  • preview 环境跑通核心链路(登录 → 主要页面 → 刷新不 404)
  • History 模式已配置服务器回退(Nginx 示例如下)
  • 静态资源走 CDN,开启 gzip/brotli
1server {
2  listen 80;
3  root /usr/share/nginx/html;
4  index index.html;
5
6  location / {
7    try_files $uri $uri/ /index.html;   # History 路由回退,关键!
8  }
9  location /api/ {
10    proxy_pass http://backend:8080/;    # 生产代理转发
11  }
12}
13

Step 12 · 性能优化要点(按需启用)

手段做法
路由/组件懒加载() => import(...),配合 defineAsyncComponent
第三方库分包manualChunks 拆分 vue 生态 / UI 库 / 图表库
图片资源小图转 base64(assetsInlineLimit),大图用 WebP + 懒加载
渲染优化v-once、v-memo、长列表虚拟滚动;避免在模板里写复杂表达式
依赖预构建Vite 自动处理;大型 CJS 库确认识别正常(看启动日志)
体积分析pnpm add -D rollup-plugin-visualizer,构建后看 treemap 找大头

常见坑速查

现象原因 / 解法
刷新页面 404History 模式未配 try_files 回退
环境变量是 undefined变量名没有 VITE_ 前缀,或改完 .env 没重启 dev server
代理不生效 / 仍跨域proxy 的 key 必须和请求的 baseURL 前缀一致
@ 别名 TS 报红vite.config.ts 和 tsconfig 要两边都配
ESLint 规则不生效ESLint 9 时代认准 eslint.config.js,旧的 .eslintrc 已废弃
生产包能跑但白屏多半是 base 路径问题(部署在子目录时需设 base: '/子目录/')

一页纸总结

1pnpm create vue@latest         官方脚手架,TS + Router + Pinia + ESLint 全勾选
2    
3重构目录(api / composables / stores / views 分层)
4    
5vite.config.ts(别名 @ + 代理 /api + 分包)+ .env 多环境
6    
7封装 request.ts(拦截器统一 Token 与错误)
8    
9<script setup> + Composable 写业务,Pinia 只管跨页共享状态
10    
11husky + lint-staged 守住提交质量
12    
13build  preview 验收  Nginx 部署(记得 try_files)
14

按这份指南走下来,你得到的不只是一个能跑的 Demo,而是一套类型安全、职责清晰、可持续迭代的工程底座。


前端框架vue3,vite 开发前端项目实践步骤指南》 是转载文章,点击查看原文


相关推荐


Java Jersey 实战指南:用 JAX-RS 注解写清晰的 REST API
唐青枫2026/7/24

简介 Jersey 是 Jakarta RESTful Web Services 规范的一种实现。 老名字常叫 JAX-RS,新包名是: jakarta.ws.rs Jersey 自己的核心包名通常是: org.glassfish.jersey 简单理解: Jakarta REST / JAX-RS 是规范 Jersey 是实现 Spring Boot 提供 Jersey 自动配置和 starter Jersey 的开发方式是用注解把 Java 类声明成 HTTP 资源: @Path("/


数据结构之双链表
无忧.芙桃2026/7/16

本篇目标: 1. 学会关于双链表的相关操作 2. 了解链表和顺序表的区别和优点 一、双链表接口实现 1. 双向链表的结构 • 单链表的结点中保存了指向后继结点的地址,所以单链表中找当前结点的后继结点很容易,但要获取当前结点的前驱结点就很麻烦,就只能从头开始往后遍历获取,时间复杂度为 O(n);所以单链表中只有当前结点的指针 pos 时(没有头指针),想要在 pos 之前插入结点和删除 pos 位置结点都是无法实现的。 • 双向链表相比单链表最大的特征是每个结点中多了一个前驱指针,


Android 面试系列:Kotlin 协程的 delay 到底发生在哪个线程?
潜龙勿用之化骨龙2026/7/8

在开发中,我们经常写出这样的代码: mainScope.launch { log("start") delay(1000) log("end") } 这段代码看起来非常简单,但它隐藏了一个非常经典的问题: delay 这 1 秒到底发生在哪个线程? 主线程前后都在执行,那中间谁在“等时间”? 结论 delay 从来不占用线程等待,它是一次“挂起 + 时间注册 + 调度恢复 + 状态机推进”的过程。 中间没有任何业务线程在 sleep,也没有线程在阻塞计时。


别再只会用 cron:Linux systemd Timer 定时任务实战详解
唐青枫2026/6/30

简介 Linux 上提到定时任务,最先想到的通常是 cron。 cron 足够简单,也足够稳定,但任务一旦涉及日志、启动依赖、超时控制、错过后补跑、运行用户和资源限制,单独一行 crontab 很快就会变得难以维护。 systemd Timer 提供了另一套方案: .timer 负责决定什么时候执行 .service 负责决定执行什么、以什么方式执行 例如,每天凌晨备份一次应用数据,可以拆成两个单元: myapp-backup.timer | | 到达触发时间


火山 DTS 正式支持 MySQL 同步到 Milvus , 解决业务库到向量库最后一公里
火山引擎Agent社区2026/6/21

这两年,大模型、智能问答越来越多地落到实际业务里。很多企业在推进过程中慢慢发现,影响 AI 应用落地效率的,除了模型本身能力之外,数据链路是否能顺畅跑通,也同样非常关键。 目前,企业大部分的业务数据库依然在关系型数据库中,而AI应用对支撑语义检索、相似召回的向量数据库有着更强的依赖。怎么把结构化业务数据稳定、持续地同步到向量数据库,正在成为不少企业建设 AI 数据底座时绕不开的问题。 现在,火山引擎 DTS 正式支持 MySQL 同步到 Milvus,帮助企业快速打通从业务数据库到向量数据库的数


计算机网络基础:在 P2P 对等方中搜索对象
梁辰兴2026/6/13

📌目录 ⚖️ 在P2P对等方中搜索对象:去中心化网络的信息发现机制🎯 一、P2P搜索问题概述:去中心化带来的挑战(一)搜索问题的本质(二)搜索算法设计目标(三)搜索算法的分类体系 📦 二、无结构P2P网络中的搜索机制(一)泛洪查询机制(二)随机漫步搜索(三)迭代加深搜索(四)Gossip协议搜索(五)向量时钟与语义搜索 🌐 三、分布式哈希表:结构化搜索的突破(一)DHT的基本原理(二)Chord算法详解(三)CAN算法详解(四)Kademlia算法详解(五)Pastry与T


真正值钱的 AI 小工具,可能只是帮人少打一遍字
深海恶霸Grace2026/6/6

我有个朋友是做财务兼采购的。 他最近有个特别烦的工作: 整理各种报价单信息。 有时候是供应商发来的截图。 有时候是一张图片。 有时候干脆就是一段文字描述。 最后这些东西都要被他重新整理进 Excel。 项目名称、规格、数量、单位、单价、总价。 听起来不难。 但真正做起来,很折磨。 因为这不是一道复杂题。 这是重复劳动。 你得盯着图片看一眼,再切到 Excel 里打一条。 再回来看一眼,再打一条。 遇到数字多一点、截图糊一点、格式乱一点的时候,眼睛真的会看花。 最烦的是,录完之后还不能放心。 因为


Sqoop 安装完整教程(基于 WSL2 + Ubuntu 24.04)
穆金秋2026/5/30

本教程详细介绍了在WSL2+Ubuntu24.04环境下安装配置Sqoop1.4.7的完整流程: 环境准备 Java8+、Hadoop3.3.6、MySQL8.0.45已安装验证命令:java -version/hadoop version/mysql --version 安装步骤 下载Sqoop1.4.7并解压到/usr/local配置环境变量(SQOOP_HOME和PATH)安装MySQL JDBC驱动到Sqoop/lib目录解决依赖问题(commons-lang等jar包)


Gogs: 打造属于你自己的轻量级 Git 服务
修己xj2026/5/8

在软件开发的世界里,Git 已经成为版本控制的事实标准。GitHub、GitLab 等平台提供了强大的托管服务,但有时候,我们需要一个完全属于自己的私有 Git 仓库——可能是为了代码安全,可能是为了定制化需求,可能是为了集成到现有服务中,也可能只是想在自己的服务器上搭建一个个人代码库。开源gitlab有点重,最近我在GitHub上发现了一个轻量级项目Gogs。 什么是 Gogs? Gogs 是一个用 Go 语言编写的自助 Git 托管服务。它的目标是以最简单、最轻松的方式搭建一个简单、稳定且


【系统架构师案例题-知识点】数据库与缓存设计
roman_日积跬步-终至千里2026/4/28

本文聚焦系统架构师案例题中的数据库与缓存设计,重点说明关系型数据库设计、NoSQL 选型、分库分表、读写分离、缓存策略、缓存故障模式以及缓存与数据库一致性问题,并结合电商、支付、内容平台、搜索系统、推荐系统等真实软件行业场景说明这些技术为什么会出现、各自解决什么问题、工程上该如何取舍。 阅读时可以按三个层次把握:先理解数据为什么会成为瓶颈,再理解数据库和缓存分别解决哪一类问题,最后把题干中的业务信号翻译成卷面表达。 一、先建立整体认识 数据库与缓存设计的核心,不是“会不会背名词”,而是看清系统到

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读