兴趣岛
前端

Vue3入门到项目实战

2026/4/30

前阵子有个朋友拉我进群,说“你们搞前端的,是不是天天在背API,像在背新华字典”。我笑回一句:字典背不背不重要,重要的是别在项目上线前夜才发现字典丢了。Vue3刚出来的时候,我也踩过一堆坑。模板里莫名其妙报警告,控制台冒红字像过年放鞭炮。最离谱的一次,一个数据改了,页面愣是没反应,最后发现是因为用了错误的方式声明响应式,白忙活一下午。从那时起我就在想:Vue3到底怎么学才不“虚”?光看文档像读说明书,干项目又像直接上高速。其实两者之间,差的就是一套能从入门直通实战的路线。

第一次用Vue3重构老项目时,我把选项式API一股脑换成组合式API,结果代码像被猫抓过的毛线团。ref、reactive、computed、watch全混在一起,哪里都生效,哪里都可能崩。后来我给自己定了规矩:简单状态用ref,对象结构优先用reactive;模板里别写太多逻辑,逻辑都收进函数里;副作用明确来源和清理时机。慢慢地,页面不再“抽风”,同事问我是不是偷偷换了框架。其实只是把理解对齐了:Vue3不是在逼你用新语法,而是在逼你理清数据流向。一旦想清楚“谁改、怎么改、改了影响谁”,坑就少了一大半。

很多人问,Vue3的组件到底怎么拆才算合理。我见过两个极端:一个是所有东西塞进一个文件,像把厨房、卧室和厕所放在同一个房间;另一个是拆到每个按钮都有一个文件,导入路径长得像绕口令。实战里,我喜欢按“功能边界”拆。列表页有它自己的请求、状态和交互,就归到一起;弹窗能独立跑,就单独拎出去;公共的表格、表单、校验规则,打成可复用的模块。项目结构也顺势清晰:views放页面,components放UI,composables放逻辑,utils放工具,router和pinia各管一路。拆得合理,改需求才不会像拆炸弹——剪哪根线都会炸。

上线前的联调往往是“照妖镜”。我曾在一个订单项目里,因为没处理好异步竞态,用户连续点两次,金额被算了两遍。Vue3本身不负责防手抖,也不替你管请求顺序,这些都得你自己补。于是我把请求封装成队列,关键操作加锁,后台返回前把按钮置灰。另一个教训是环境配置:开发时跑得飞快,一到预发就各种跨域和路径不对。后来我把环境变量、路由守卫、错误边界统一收口,项目像穿了件防风外套,冷不丁的怪风没那么容易灌进来。工具链稳了,人才能稳。

写到这里,算是把开头那句“字典丢了”的担忧摊开来说:Vue3的入门不是背完概念就结束,而是用项目把概念盘活。你会在真实场景里理解ref和reactive的取舍,明白什么时候用watch而不是computed,体会到组合式函数怎么把重复代码变成复利。项目也不一定要多庞大,一个带登录、列表、详情、权限校验的中后台,足够把Vue3的核心跑通。从配置到路由,从状态到组件通信,从请求封装到错误处理,一步步搭起来,信心是一行行代码长出来的。

接下来的内容,我会沿着这条路线展开:先搭环境与项目骨架,再把常用语法和响应式系统落到具体例子,然后演示一个完整的中后台项目怎么从零写到上线,最后聊工程化、性能与维护。希望你不只“会用”,还能“用稳”。


一、环境与项目骨架

学Vue3,先把工具链理顺。现在主流是Vite,开发启动快,构建也干脆。安装完Node.js后,用命令行初始化项目:

npm create vue@latest

它会问你是否用TypeScript、Router、Pinia、单元测试等。新手建议Router和Pinia都选上,TypeScript也打开,早期多写几行类型,后面少问几小时“为什么是undefined”。项目生成后,目录大致长这样:

src/
  assets/
  components/
  views/
  router/
  stores/
  composables/
  utils/
  App.vue
  main.ts

入口文件main.ts里,会用createApp挂载根组件,并注册Router和Pinia。这时候先别急着写业务,跑起来再说:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(router)
app.use(createPinia())
app.mount('#app')

运行npm run dev,浏览器弹出欢迎页,第一步就算站稳了。


二、语法与响应式系统

Vue3的核心变化之一是响应式系统的重写。Object.defineProperty换成了Proxy,对象和数组的变化都能捕获。但对我们来说,更直观的改变是API风格。

ref与reactive

ref适合基本类型和简单对象,模板里会自动解包,不用写.value

<script setup>
import { ref } from 'vue'

const count = ref(0)
function add() {
  count.value++
}
</script>

<template>
  <div>{{ count }}</div>
  <button @click="add">加一</button>
</template>

reactive更适合对象结构,尤其表单、表格数据:

import { reactive } from 'vue'

const form = reactive({
  name: '',
  age: null as number | null
})

两者可以混用,但别在同一个对象里来回切换,否则容易混乱。

computed与watch

computed是做“衍生状态”的,比如一个总价:

import { computed } from 'vue'

const total = computed(() => {
  return list.value.reduce((sum, item) => sum + item.price * item.count, 0)
})

watch则用于“副作用”,比如路由变化拉数据,或者本地缓存回填:

import { watch } from 'vue'

watch(() => route.params.id, (newId) => {
  if (newId) fetchDetail(newId)
}, { immediate: true })

注意清理副作用:定时器、事件监听、异步请求,在组件卸载或重新触发前要停掉,避免内存泄漏。

组合式函数(composables)

把重复逻辑抽离成函数,是Vue3最顺手的一招。比如封装一个表格列表:

// composables/useTable.ts
import { ref, onMounted } from 'vue'

export function useTable(fetchFn) {
  const list = ref([])
  const loading = ref(false)

  const load = async (params) => {
    loading.value = true
    try {
      list.value = await fetchFn(params)
    } finally {
      loading.value = false
    }
  }

  onMounted(() => load())

  return { list, loading, load }
}

在组件里:

import { useTable } from '@/composables/useTable'

const { list, loading, load } = useTable(api.getList)

逻辑被“拎”出来,组件只剩编排,协作起来清晰很多。


三、项目实战:中后台示例

纸上谈兵容易“翻车”,我们直接动手做一个精简但完整的中后台:登录、权限、列表、详情、搜索与分页。

路由与权限控制

路由配置放在router/index.ts,用meta字段标记权限:

{
  path: '/order',
  name: 'Order',
  component: () => import('@/views/OrderList.vue'),
  meta: { requiresAuth: true, roles: ['admin', 'operator'] }
}

全局路由守卫做拦截:

router.beforeEach((to, from, next) => {
  const user = store.user
  if (to.meta.requiresAuth && !user.token) {
    next('/login')
  } else if (to.meta.roles && !to.meta.roles.includes(user.role)) {
    next('/403')
  } else {
    next()
  }
})

权限粒度可以更细,比如按钮级用指令或函数控制显隐,这里先保主干。

状态管理

用Pinia替代Vuex,写法更“函数风”。一个用户store:

// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: '',
    role: '' as string,
    profile: null as any
  }),
  actions: {
    login(data) {
      // 调用接口,存token
    },
    logout() {
      this.token = ''
      this.role = ''
    }
  }
})

在组件里直接解构使用:

const user = useUserStore()
const handleLogout = () => user.logout()

状态集中,调试也方便,Vue DevTools里能看到变化轨迹。

列表页:请求、搜索、分页

列表页最琐碎,也最能检验代码质量。把请求、参数、表格状态封装在一起:

// composables/useOrderList.ts
import { ref, watch } from 'vue'

export function useOrderList() {
  const list = ref([])
  const loading = ref(false)
  const pagination = ref({ page: 1, size: 20, total: 0 })
  const filters = reactive({ status: '', keyword: '' })

  const fetch = async () => {
    loading.value = true
    try {
      const res = await api.getOrders({ ...filters, ...pagination.value })
      list.value = res.data
      pagination.value.total = res.total
    } finally {
      loading.value = false
    }
  }

  watch(() => [filters.status, filters.keyword], () => {
    pagination.value.page = 1
    fetch()
  })

  return { list, loading, pagination, filters, fetch }
}

页面只负责模板和事件绑定:

<template>
  <div>
    <input v-model="filters.keyword" placeholder="搜索" />
    <select v-model="filters.status">
      <option value="">全部</option>
      <option value="pending">待处理</option>
    </select>

    <el-table :data="list" v-loading="loading">
      <!-- 列定义 -->
    </el-table>

    <el-pagination
      @current-change="(p) => { pagination.page = p; fetch() }"
      :current-page="pagination.page"
      :page-size="pagination.size"
      :total="pagination.total"
    />
  </div>
</template>

逻辑和视图分离后,改搜索、加字段、调接口都不容易“牵一发动全身”。

错误与边界处理

中后台少不了兜底。全局异常捕获可以用app.config.errorHandler,接口层统一处理错误码,重要操作加二次确认与日志。页面级别可以用<KeepAlive>缓存列表状态,减少重复请求。


四、工程化与上线

项目写得顺,还得发得稳。Vite配置里区分环境,.env.development.env.production放不同接口前缀和调试开关。

构建时注意分包:路由懒加载、第三方库抽离,减少首屏体积。比如:

{
  path: '/report',
  component: () => import('@/views/Report.vue')
}

配置build.rollupOptions做更细粒度拆分。对性能敏感的地方,用v-oncev-memo减少不必要的重渲染。大列表用虚拟滚动,避免卡顿。

上线前检查:路由权限跑通、必填校验不漏、加载态友好、错误提示明确。用工具跑一遍Lighthouse,解决明显的性能告警。日志和监控接入,能帮你更快定位“只在用户手机上出现”的问题。


五、结语

从Vue3入门到项目实战,最怕的不是语法多,而是“学了却用不起来”。把知识点落到一个能跑通的项目里,环境、路由、状态、请求、组件拆分、错误处理、性能优化,一环扣一环,理解才会扎实。你不需要一次性记住所有API,只需要在真实场景中反复使用,把决策变成习惯。

项目不在大,在于闭环;技术不在新,在于可控。Vue3给了我们更细粒度的控制力和更清晰的组合方式,剩下的,就是用一次次的实践把它“盘”熟。当你能从容地加需求、修Bug、调性能,而不再担心“会不会又踩坑”,入门才算真正完成,实战才算刚刚开始。

Vue3入门到项目实战 | 兴趣岛