☰
Vue 3 极简入门:2小时掌握组合式API与项目实战
2026/10/1 17:06:09 网站建设 项目流程

在实际前端开发中,Vue 3 以其更优的性能、更好的 TypeScript 支持和更灵活的组合式 API,已成为新项目的首选。但对于初学者或需要快速上手一个新项目的开发者来说,面对官方文档的全面性和社区教程的碎片化,往往感到无从下手,不知道哪些是核心,哪些可以后续再学。本文旨在提供一个“极简版”的 Vue 3 学习路径,目标是在 2 小时内,让你理解 Vue 3 的核心概念,并能够动手搭建一个可运行、可扩展的项目骨架。我们将聚焦于“即学即用”的部分,跳过深奥的原理和边缘特性,直接进入编码实践,并附上完整的项目源码供参考和调试。

本文适合有一定 HTML、CSS、JavaScript 基础,但 Vue 3 经验为零或接触不多的开发者。通过本文,你将学会如何从零开始配置开发环境、理解 Vue 3 的核心语法、编写一个包含基础交互的组件,并最终构建和运行一个完整的单页应用。学完后,你将有能力基于此骨架开发简单的后台管理系统、数据看板或移动端 H5 页面。

1. 理解 Vue 3 的核心:组合式 API 与响应式系统

在开始写代码之前,必须先理解 Vue 3 与 Vue 2 最根本的区别:组合式 API (Composition API)和响应式系统的重构。这决定了你编写 Vue 3 代码的思维方式。

1.1 为什么是组合式 API?

Vue 2 的选项式 API (Options API) 将代码按照data、methods、computed、生命周期等选项进行组织。这在组件简单时很清晰,但当组件逻辑复杂后,同一功能的代码会被分散到不同选项块中,导致阅读和维护困难。例如,一个处理用户表单的逻辑,其数据、方法和监听器可能散落在各处。

组合式 API 的核心思想是:将同一逻辑关注点的代码组织在一起。你可以自由地将响应式状态、计算属性、函数等组合成一个可复用的“逻辑组合”,并且这个组合可以轻松地在多个组件间复用。这带来了更好的逻辑复用、更灵活的代码组织和更强的类型推断支持。

1.2 响应式系统的基石:ref和reactive

Vue 3 的响应式系统基于 ES6 的 Proxy 重构,提供了两个创建响应式数据的核心函数:

  • ref: 用于创建一个响应式的引用,可以包装任何类型的值。在模板中访问时,.value会被自动解包。它更适合包装基本类型(如字符串、数字)或需要保持引用稳定的对象。
    import { ref } from 'vue'; const count = ref(0); // 创建一个响应式引用,初始值为0 console.log(count.value); // 访问值需要使用 .value count.value++; // 修改值
  • reactive: 用于创建一个响应式的对象。它返回的是原始对象的 Proxy 代理。只能用于对象类型(Object, Array, Map, Set)。
    import { reactive } from 'vue'; const user = reactive({ name: 'Alice', age: 25 }); console.log(user.name); // 直接访问属性,无需 .value user.age = 26; // 直接修改

简单记忆:基本类型或需要替换整个引用时用ref,复杂的对象或集合用reactive。在模板中,ref会自动解包,所以你写{{ count }}而不是{{ count.value }}。

1.3 生命周期钩子的变化

Vue 3 的生命周期钩子名称前都加了on前缀,并且需要在setup()函数或<script setup>中使用。

Vue 2 选项式 APIVue 3 组合式 API (在 setup 中)触发时机
beforeCreate使用setup()本身替代组件实例初始化之前
created使用setup()本身替代组件实例创建完成
beforeMountonBeforeMount挂载开始之前
mountedonMounted组件挂载到 DOM 后
beforeUpdateonBeforeUpdate响应式数据变更,DOM 更新前
updatedonUpdated响应式数据变更,DOM 更新后
beforeUnmountonBeforeUnmount组件卸载前
unmountedonUnmounted组件卸载后

注意:在组合式 API 中,setup()函数执行时机约等于beforeCreate和created,所以在这两个钩子中执行的逻辑,直接写在setup()顶部即可。

2. 环境准备与项目初始化

我们将使用 Vite 作为构建工具,它比传统的 Vue CLI 启动更快、热更新更迅速,是 Vue 3 官方推荐的构建工具。

2.1 安装 Node.js 与包管理器

首先确保你的系统安装了 Node.js (版本 14.18+, 16+ 更佳)。你可以在终端中运行以下命令检查:

node -v npm -v

如果未安装,请前往 Node.js 官网下载 LTS 版本进行安装。npm 会随 Node.js 一同安装。你也可以选择安装yarn或pnpm,本文使用npm。

2.2 使用 Vite 创建 Vue 3 项目

打开终端,进入你希望创建项目的目录,执行以下命令:

npm create vue@latest

这个命令会安装并执行create-vue,这是 Vue 官方的项目脚手架工具。接下来会有一系列交互式选项:

  1. Project name: 输入你的项目名,例如vue3-quick-start。
  2. Add TypeScript?: 对于快速上手,可以选择No。但在实际项目中,强烈建议使用 TypeScript。
  3. Add JSX Support?: 选择No。JSX 是另一种模板写法,我们先学习单文件组件。
  4. Add Vue Router for Single Page Application?: 选择Yes。即使是简单项目,路由也是常见需求。
  5. Add Pinia for state management?: 选择Yes。Pinia 是 Vue 官方推荐的状态管理库,比 Vuex 更简单。
  6. Add Vitest for Unit Testing?: 选择No。快速上手阶段可以跳过测试。
  7. Add an End-to-End Testing Solution?: 选择No。
  8. Add ESLint for code quality?: 选择Yes。代码规范有助于团队协作。
  9. Add Prettier for code formatting?: 选择Yes。

选择完成后,脚手架会自动创建项目结构并安装依赖。进入项目目录并安装剩余依赖:

cd vue3-quick-start npm install

2.3 项目结构速览

创建完成后,你的项目目录结构大致如下:

vue3-quick-start/ ├── node_modules/ # 项目依赖包 ├── public/ # 静态资源(不会被Vite处理) ├── src/ # 源代码目录 │ ├── assets/ # 图片、字体等资源 │ ├── components/ # 可复用组件 │ ├── router/ # 路由配置 (由create-vue生成) │ ├── stores/ # Pinia状态存储 (由create-vue生成) │ ├── views/ # 页面级组件 │ ├── App.vue # 根组件 │ └── main.js # 应用入口文件 ├── .eslintrc.cjs # ESLint配置 ├── .prettierrc # Prettier配置 ├── index.html # HTML入口模板 ├── package.json # 项目配置和依赖 ├── vite.config.js # Vite配置 └── README.md

这个结构清晰地区分了组件、页面、路由和状态,是开发 Vue 3 单页应用的典型结构。

3. 编写第一个 Vue 3 组件:计数器与待办列表

我们将通过编写两个经典示例组件来实践组合式 API:一个计数器和一个简单的待办事项列表。

3.1 单文件组件与<script setup>语法糖

Vue 3 推荐使用单文件组件 (.vue文件),并提供了<script setup>语法糖,让组合式 API 的写法更简洁。在src/components/下创建HelloWorld.vue文件。

传统setup()函数写法:

<script> import { ref } from 'vue'; export default { setup() { const count = ref(0); function increment() { count.value++; } return { count, increment }; // 必须返回模板中需要使用的变量和方法 } } </script>

<script setup>语法糖写法:

<script setup> import { ref } from 'vue'; const count = ref(0); function increment() { count.value++; } // 无需 return,模板中可直接使用 count 和 increment </script>

<script setup>中的代码会被编译成组件setup()函数的内容,任何顶层绑定(变量、函数、import)都能直接在模板中使用。这极大地简化了代码。

3.2 完整组件示例:计数器与待办列表

我们将创建一个包含两个功能的组件。替换src/components/HelloWorld.vue的内容如下:

<template> <div class="hello-world"> <h2>Vue 3 极简示例</h2> <!-- 1. 计数器示例 --> <section class="counter-demo"> <h3>计数器</h3> <p>当前计数:{{ count }}</p> <button @click="increment">+1</button> <button @click="decrement">-1</button> <button @click="reset">重置</button> <p>计数是 {{ isEvenOrOdd }}</p> </section> <!-- 2. 待办列表示例 --> <section class="todo-demo"> <h3>待办事项</h3> <div> <input v-model="newTodo" @keyup.enter="addTodo" placeholder="输入后按回车添加" /> <button @click="addTodo">添加</button> </div> <ul v-if="todos.length > 0"> <li v-for="(todo, index) in todos" :key="todo.id"> <span :class="{ done: todo.completed }" @click="toggleTodo(todo.id)"> {{ todo.text }} </span> <button @click="removeTodo(todo.id)">删除</button> </li> </ul> <p v-else>暂无待办事项,添加一条吧!</p> <p>总计:{{ totalTodos }} 项,已完成:{{ completedTodos }} 项</p> </section> </div> </template> <script setup> import { ref, computed, reactive } from 'vue'; // --- 计数器逻辑 --- const count = ref(0); const increment = () => count.value++; const decrement = () => count.value--; const reset = () => (count.value = 0); // 计算属性:根据 count 的奇偶性返回字符串 const isEvenOrOdd = computed(() => (count.value % 2 === 0 ? '偶数' : '奇数')); // --- 待办列表逻辑 --- // 使用 reactive 创建响应式对象 const todoState = reactive({ newTodo: '', // 绑定输入框 todos: [ // 待办数组 { id: 1, text: '学习 Vue 3 基础', completed: true }, { id: 2, text: '理解组合式 API', completed: false }, { id: 3, text: '完成这个示例项目', completed: false }, ], }); // 生成唯一ID的简单方法 let nextId = 4; const addTodo = () => { const text = todoState.newTodo.trim(); if (text) { todoState.todos.push({ id: nextId++, text, completed: false, }); todoState.newTodo = ''; // 清空输入框 } }; const removeTodo = (id) => { const index = todoState.todos.findIndex((todo) => todo.id === id); if (index > -1) { todoState.todos.splice(index, 1); } }; const toggleTodo = (id) => { const todo = todoState.todos.find((t) => t.id === id); if (todo) { todo.completed = !todo.completed; } }; // 计算属性:总项数和完成数 const totalTodos = computed(() => todoState.todos.length); const completedTodos = computed( () => todoState.todos.filter((t) => t.completed).length ); </script> <style scoped> .hello-world { font-family: Arial, sans-serif; padding: 20px; max-width: 600px; margin: 0 auto; } section { margin-bottom: 30px; padding: 15px; border: 1px solid #eee; border-radius: 8px; } button { margin: 0 5px; padding: 5px 10px; cursor: pointer; } ul { list-style: none; padding-left: 0; } li { padding: 8px; border-bottom: 1px solid #ddd; display: flex; justify-content: space-between; align-items: center; } .done { text-decoration: line-through; color: #888; } input { padding: 5px; margin-right: 10px; } </style>

3.3 关键代码与配置详解

  1. <script setup>: 这是 Vue 3 的组合式 API 语法糖。所有顶层导入和声明都自动暴露给模板。
  2. ref与reactive:
    • count使用ref,因为它是一个基本类型的数字。
    • todoState使用reactive,因为它是一个包含多个属性的复杂对象。在模板中访问其属性时,直接使用todoState.newTodo和todoState.todos。
  3. computed: 用于声明依赖其他响应式状态的计算属性。isEvenOrOdd、totalTodos、completedTodos都是计算属性,它们会根据依赖的count或todoState.todos自动更新。
  4. v-model:v-model="todoState.newTodo"是双向数据绑定的语法糖,它相当于:value="todoState.newTodo" @input="todoState.newTodo = $event.target.value"。
  5. v-for与:key: 循环渲染列表时必须为每一项提供一个唯一的key,这里使用todo.id。这能帮助 Vue 高效地更新虚拟 DOM。
  6. @click与@keyup.enter: Vue 使用@作为v-on:的简写,用于监听 DOM 事件。
  7. <style scoped>:scoped属性使得样式只作用于当前组件,避免了全局样式污染。

4. 集成路由与状态管理,并运行项目

我们已经有了一个功能组件,现在需要将它集成到应用中,并了解如何通过路由访问它,以及如何使用 Pinia 进行简单的状态管理。

4.1 配置路由并显示组件

脚手架已经为我们生成了路由配置。打开src/router/index.js,你会看到类似以下内容:

import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: () => import('../views/AboutView.vue') } ] }) export default router

我们添加一个指向HelloWorld组件的路由。首先,在src/views/目录下创建DemoView.vue,作为页面容器:

<!-- src/views/DemoView.vue --> <template> <div class="demo-view"> <HelloWorld /> </div> </template> <script setup> import HelloWorld from '@/components/HelloWorld.vue'; </script>

然后,在src/router/index.js的路由数组中添加一个新路由:

{ path: '/demo', name: 'demo', component: () => import('../views/DemoView.vue') // 使用懒加载 }

最后,修改src/App.vue,添加一个导航链接到我们的演示页:

<template> <nav> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/demo">Vue 3 Demo</router-link> <!-- 新增链接 --> </nav> <router-view/> </template>

4.2 使用 Pinia 管理全局状态

假设我们想让计数器的值在所有组件间共享。Pinia 非常适合做这个。

首先,查看src/stores/counter.js,脚手架可能已经生成了一个示例。我们修改它以匹配我们的计数器逻辑:

// src/stores/counter.js import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useCounterStore = defineStore('counter', () => { // state const count = ref(0) // getters (计算属性) const doubleCount = computed(() => count.value * 2) const isEvenOrOdd = computed(() => (count.value % 2 === 0 ? '偶数' : '奇数')) // actions (方法) function increment() { count.value++ } function decrement() { count.value-- } function reset() { count.value = 0 } return { count, doubleCount, isEvenOrOdd, increment, decrement, reset } })

这是一个使用组合式 API 风格定义的 Store。现在,在HelloWorld.vue组件中使用这个 Store 来替换本地的计数器逻辑:

<!-- 在 HelloWorld.vue 的 <script setup> 部分修改 --> <script setup> import { ref, computed, reactive } from 'vue'; import { useCounterStore } from '@/stores/counter'; // 导入 Store // --- 计数器逻辑(使用 Pinia)--- const counterStore = useCounterStore(); // 现在可以直接使用 counterStore.count, counterStore.increment 等 // --- 待办列表逻辑(保持不变)--- // ... 保持之前的 todoState 和其相关函数 ... </script> <template> <!-- 修改模板中计数器的部分 --> <section class="counter-demo"> <h3>计数器 (使用 Pinia)</h3> <p>当前计数:{{ counterStore.count }}</p> <p>双倍计数:{{ counterStore.doubleCount }}</p> <button @click="counterStore.increment">+1</button> <button @click="counterStore.decrement">-1</button> <button @click="counterStore.reset">重置</button> <p>计数是 {{ counterStore.isEvenOrOdd }}</p> </section> <!-- ... 其余模板保持不变 ... --> </template>

这样,计数器的状态就被提升到了全局的 Store 中。你可以在任何组件中导入useCounterStore来访问和修改这个状态。

4.3 运行与构建项目

在项目根目录下,运行开发服务器:

npm run dev

Vite 会启动一个本地开发服务器,通常在http://localhost:5173。打开浏览器访问该地址,点击导航栏中的 “Vue 3 Demo”,你应该能看到我们编写的计数器与待办列表应用。尝试操作按钮和输入框,体验响应式更新。

当你完成开发,需要构建生产版本时,运行:

npm run build

构建产物将生成在dist目录下,你可以将其部署到任何静态文件服务器。

5. 常见问题与排查路径

在学习和开发过程中,你可能会遇到以下典型问题。

5.1 开发服务器无法启动或端口占用

问题现象可能原因检查与解决方式
npm run dev报错或无法访问1. 端口被占用 (默认 5173)
2. Node.js 版本过低
3. 依赖未安装或损坏
1. 检查端口占用netstat -ano | findstr :5173(Windows) 或lsof -i :5173(Mac/Linux),或修改vite.config.js中的server.port。
2. 确认 Node.js 版本node -v,升级到 LTS 版本。
3. 删除node_modules和package-lock.json,重新运行npm install。

5.2 组件模板渲染错误或数据不更新

问题现象可能原因检查与解决方式
模板中显示[object Object]直接在模板中渲染了一个对象使用{{ JSON.stringify(someObject) }}调试,或访问对象的特定属性{{ someObject.key }}。
数据修改了但视图不更新1. 数据不是响应式的
2. 直接修改了数组索引或对象属性
1. 确保使用ref或reactive创建响应式数据。
2. 对于reactive对象,确保使用push、splice等方法修改数组,或直接为对象属性赋值。对于ref包装的数组/对象,修改.value或使用.value.push()。
console.log输出正确但页面不变在异步操作(如定时器、网络请求)中修改响应式数据,Vue 可能无法追踪确保在异步回调中正确访问.value(对于ref),或使用reactive包装的对象。

5.3 路由或 Pinia 相关错误

问题现象可能原因检查与解决方式
路由跳转后页面空白或 4041. 路由配置错误
2. 生产环境部署路径问题
1. 检查router/index.js中的path和component导入是否正确。
2. 如果部署到子路径,需配置createWebHistory(‘/your-sub-path/’)和 Vite 的base配置。
访问 Pinia store 时报getActivePinia错误在 Pinia 实例被挂载到应用之前就尝试使用 store确保在main.js中正确安装 Pinia:app.use(createPinia()),并且useCounterStore()的调用发生在组件setup或<script setup>中,而不是模块顶层。

5.4 样式不生效或作用域问题

问题现象可能原因检查与解决方式
<style scoped>内的样式不生效选择器深度不够,被 scoped 添加的属性选择器覆盖对于想影响子组件的样式,使用:deep()深度选择器,例如:deep(.child-class) { color: red; }。
引入的第三方组件库样式丢失Vite 构建时未处理检查是否需要在main.js中导入组件的样式文件,例如import ‘element-plus/dist/index.css’。

6. 最佳实践与下一步学习方向

掌握了以上基础后,遵循以下实践能让你的 Vue 3 项目更健壮、更易维护。

6.1 组件设计与代码组织

  1. 单一职责:每个组件只做一件事。如果组件变得庞大(超过 200 行),考虑拆分成更小的子组件或使用组合式函数抽离逻辑。
  2. 使用组合式函数复用逻辑:将可复用的响应式逻辑(如获取数据、监听窗口大小)封装成组合式函数。例如,创建一个useMousePosition.js文件,使用ref和onMounted、onUnmounted来追踪鼠标位置,然后在多个组件中导入使用。
  3. 明确的 Props 和 Emits:使用defineProps和defineEmits来明确组件接收的参数和触发的事件,并尽可能使用 TypeScript 定义类型。
    <script setup> const props = defineProps({ title: { type: String, required: true }, count: Number }); const emit = defineEmits(['update:count', 'success']); </script>

6.2 状态管理策略

  1. 按需使用 Pinia:不是所有状态都需要放到全局 Store。组件内部状态就用ref/reactive;需要跨组件共享且层级较深时,再考虑 Pinia。
  2. 模块化 Store:将不同业务域的状态拆分到不同的 Store 文件中(如userStore.js、productStore.js),避免单个 Store 过于臃肿。
  3. 谨慎使用持久化:对于需要持久化的状态(如用户 token),可以使用pinia-plugin-persistedstate这类插件,但要注意敏感信息的安全。

6.3 性能与生产环境考量

  1. 组件懒加载:对于路由组件,使用() => import(‘…’)语法实现懒加载,减少初始包体积。
  2. 列表渲染优化:始终为v-for提供唯一的key。对于超长列表,考虑使用虚拟滚动库(如vue-virtual-scroller)。
  3. 避免不必要的响应式:对于永远不会改变的数据(如常量配置),不要用ref/reactive包装,直接使用普通变量即可。
  4. 构建分析:使用npm run build -- --report或rollup-plugin-visualizer分析构建产物体积,优化过大的依赖。

6.4 下一步深入学习路径

完成这个极简入门后,你可以按以下路径系统学习:

  1. 核心深入:官方文档的《深入响应式系统》、《渲染机制》、《组合式函数》章节。
  2. TypeScript 集成:在项目中引入 TypeScript,学习如何为 Props、Emits、Composable、Pinia Store 定义类型,这将极大提升开发体验和代码可靠性。
  3. 生态工具:
    • Vue Router: 学习嵌套路由、路由守卫、路由元信息、动态路由。
    • Pinia: 学习 Store 间通信、插件开发、服务端渲染 (SSR) 支持。
    • VueUse: 一个强大的 Vue 组合式工具集合,提供了大量开箱即用的逻辑函数。
  4. UI 组件库:根据项目需求选择一款 UI 库,如 Element Plus、Ant Design Vue、Vuetify 等,学习其按需引入和主题定制。
  5. 高级模式:学习 Teleport、Suspense、渲染函数、自定义指令等高级特性。
  6. 工程化:配置更完善的 ESLint + Prettier + Husky 提交前检查,学习单元测试 (Vitest) 和组件测试 (Vue Test Utils)。

这个极简项目源码已经包含了路由和状态管理的基础集成,你可以以此为基础,逐步添加新的页面、组件和业务逻辑,在实践中不断巩固和扩展你的 Vue 3 技能栈。记住,理解核心概念(响应式、组合式 API)比记忆所有 API 更重要,遇到问题时,优先查阅官方文档。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询