☰
Vue 3 极简速成:从零构建任务管理器,掌握组合式API与Pinia
2026/10/5 14:33:42 网站建设 项目流程

最近在带新人上手前端项目时,发现很多同学对 Vue 3 望而却步,觉得组合式 API、响应式系统、构建工具等概念复杂,网上教程要么太旧(还在讲 Options API),要么太散(只讲某个 API)。其实,Vue 3 的核心思想非常清晰,上手也很快。本文旨在用最短的路径,带你从零搭建一个可运行的 Vue 3 项目,理解其核心概念,并完成一个具备增删改查功能的小应用。学完后,你将能独立配置开发环境、编写组件、管理状态,并具备继续深入学习的能力。

1. Vue 3 核心概念速览:为什么是现在学它的最佳时机?

在深入代码之前,我们需要理解 Vue 3 带来的根本性变化及其设计哲学。这能帮助你在后续编码时,不仅知道“怎么做”,更明白“为什么这么做”。

1.1 组合式 API (Composition API) vs 选项式 API (Options API)

这是 Vue 3 最标志性的特性。传统的选项式 API 将代码按照data、methods、computed、lifecycle等选项进行组织。对于小型组件,这很清晰。但当组件逻辑变得复杂时,与同一个功能相关的代码(例如,一个“用户搜索”功能,可能涉及响应式数据、计算属性、方法、生命周期钩子)会被拆分到不同的选项块中,导致阅读和维护时需要不断上下滚动,逻辑关注点分散。

组合式 API 则允许你使用导入的函数(如ref,reactive,computed,onMounted)来定义组件的逻辑。你可以像编写普通 JavaScript 函数一样,将相关联的代码组织在一起。这使得:

  • 逻辑复用更强大:可以轻松地将一组相关的响应式状态和函数提取为“组合式函数”,在多个组件间复用。
  • 代码组织更灵活:不再受限于固定的选项模板,可以按功能而非选项类型来组织代码。
  • 更好的 TypeScript 支持:组合式函数能提供更完善的类型推导。

简单来说,选项式 API 是“按选项类型分文件柜”,组合式 API 是“按业务功能打包行李”。Vue 3 完全支持两种写法,但组合式 API 是未来的主流和官方推荐。

1.2 响应式系统的革新:ref与reactive

Vue 3 的响应式系统基于 ES6 的Proxy重写,性能更好,能力更强。它主要通过两个函数暴露给开发者:

  • ref():用于定义响应式的基本类型值(如string,number,boolean)。它返回一个带有.value属性的响应式对象。在模板中会自动解包,无需写.value;在 JavaScript 中则需要。
    import { ref } from 'vue' const count = ref(0) // 创建一个响应式引用,初始值为0 console.log(count.value) // 访问值 count.value++ // 修改值
  • reactive():用于定义响应式的对象。它返回一个对象的响应式代理。
    import { reactive } from 'vue' const state = reactive({ count: 0, name: 'Vue' }) console.log(state.count) // 直接访问属性 state.count++ // 直接修改属性

关键选择:通常,基础类型用ref,对象用reactive。但在组合式函数中,统一使用ref有时更便于保持返回值结构的一致性。

1.3 其他重要新特性

  • <script setup>语法糖:一种更简洁的编写单文件组件的方式。它是在<script>标签上添加setup属性,让代码更接近纯粹的 JavaScript,大幅减少模板代码。本文实战部分将全程使用它。
  • Teleport:可以将组件的一部分模板“传送”到 DOM 中的其他位置(如 body 末尾),常用于模态框、通知框。
  • Fragments:组件可以拥有多个根节点。
  • 更好的性能:更小的打包体积、更快的渲染速度、更高效的重渲染机制。

理解了这些,我们就有了坚实的理论基础。接下来,从环境搭建开始实战。

2. 环境准备与项目创建

我们将使用 Vue 官方的构建工具 Vite 来创建项目,它比传统的 Webpack 更快、更轻量。

2.1 环境要求

  • Node.js:请确保已安装 Node.js (版本 14.18+, 16+ 或更高)。在终端运行node -v检查。
  • 包管理器:可以使用 npm (随 Node.js 安装) 或 yarn、pnpm。本文使用 npm 进行演示。
  • 代码编辑器:推荐使用 Visual Studio Code,并安装 Vue 官方扩展 “Volar”。

2.2 使用 Vite 创建 Vue 3 项目

打开终端(命令行),执行以下命令:

# 使用 npm 6.x npm create vue@latest my-vue3-app # 使用 npm 7+, 需要额外的双横线 npm create vue@latest my-vue3-app # 使用 yarn yarn create vue my-vue3-app # 使用 pnpm pnpm create vue my-vue3-app

执行后,你会看到一个交互式的项目配置向导。我们做如下选择:

  • Project name: 直接回车(使用my-vue3-app)或输入你的项目名。
  • Add TypeScript?:No(本文为简化,先不使用 TS,但实际项目强烈建议使用)。
  • Add JSX Support?:No。
  • Add Vue Router for Single Page Application development?:No(本文先聚焦核心,后续可自行添加)。
  • Add Pinia for state management?:Yes(这是 Vue 官方推荐的状态管理库,类似 Vuex,但更简单,我们稍后会用到)。
  • Add Vitest for Unit Testing?:No。
  • Add an End-to-End Testing Solution?:> None。
  • Add ESLint for code quality?:Yes(代码规范检查,建议开启)。
  • Add Prettier for code formatting?:Yes(代码格式化,建议开启)。

配置完成后,进入项目目录并安装依赖:

cd my-vue3-app npm install

2.3 项目结构初探

创建完成后,项目目录结构如下:

my-vue3-app/ ├── node_modules/ # 项目依赖包 ├── public/ # 静态资源(如图标) │ └── favicon.ico ├── src/ # 源代码目录 │ ├── assets/ # 资源文件(如图片、样式) │ ├── components/ # 可复用组件 │ │ └── HelloWorld.vue │ ├── stores/ # Pinia 状态管理 store(因为我们选择了 Pinia) │ │ └── counter.js │ ├── App.vue # 应用根组件 │ └── main.js # 应用入口文件 ├── .gitignore ├── index.html # HTML 模板 ├── package.json # 项目配置和依赖 ├── vite.config.js # Vite 配置 └── ... (其他配置文件)

现在,可以启动开发服务器了:

npm run dev

终端会输出本地服务器地址(通常是http://localhost:5173)。用浏览器打开它,你将看到 Vue 的默认欢迎页面。我们的速成之旅,将从改造这个页面开始。

3. 核心语法与<script setup>初体验

让我们先清理默认页面,创建一个最简单的计数器组件,来熟悉组合式 API 和<script setup>。

3.1 改造App.vue

打开src/App.vue文件,用以下代码完全替换:

<!-- src/App.vue --> <script setup> import { ref } from 'vue' import MyCounter from './components/MyCounter.vue' const appTitle = ref('Vue 3 极简速成 Demo') </script> <template> <div class="app"> <h1>{{ appTitle }}</h1> <p>这是一个使用 Vue 3 Composition API 和 `<script setup>` 构建的简单应用。</p> <MyCounter /> </div> </template> <style scoped> .app { font-family: Arial, sans-serif; max-width: 800px; margin: 40px auto; padding: 20px; text-align: center; } h1 { color: #42b883; /* Vue 主题色 */ } </style>

代码解析:

  • <script setup>:声明使用组合式 API 的语法糖。在这里面声明的顶级变量、函数、import 都会自动暴露给模板。
  • import { ref } from 'vue':从 Vue 中导入ref函数。
  • const appTitle = ref('...'):创建一个响应式的字符串引用。在模板中直接用{{ appTitle }}使用,无需.value。
  • <MyCounter />:引入并使用了我们即将创建的子组件。

3.2 创建第一个组件:MyCounter.vue

在src/components目录下,新建文件MyCounter.vue,并写入:

<!-- src/components/MyCounter.vue --> <script setup> import { ref, computed, onMounted } from 'vue' // 1. 使用 ref 定义响应式数据 const count = ref(0) const step = ref(1) // 2. 使用 computed 定义计算属性 const doubleCount = computed(() => count.value * 2) const countParity = computed(() => count.value % 2 === 0 ? '偶数' : '奇数') // 3. 定义方法 function increment() { count.value += step.value } function decrement() { count.value -= step.value } function reset() { count.value = 0 step.value = 1 } // 4. 使用生命周期钩子 onMounted(() => { console.log('计数器组件已挂载!初始值为:', count.value) }) </script> <template> <div class="counter"> <h2>计数器示例</h2> <div class="display"> <p>当前值: <strong>{{ count }}</strong></p> <p>双倍值: <strong>{{ doubleCount }}</strong></p> <p>奇偶性: <strong>{{ countParity }}</strong></p> </div> <div class="controls"> <label> 步长: <input type="number" v-model.number="step" min="1" /> </label> <button @click="decrement">-</button> <button @click="increment">+</button> <button @click="reset">重置</button> </div> <p class="hint">尝试修改步长,然后点击加减按钮。</p> </div> </template> <style scoped> .counter { border: 2px solid #42b883; border-radius: 10px; padding: 20px; margin-top: 20px; background-color: #f9f9f9; } .display { margin-bottom: 15px; font-size: 1.1em; } .controls { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 15px; } .controls button { padding: 8px 16px; font-size: 1.2em; cursor: pointer; background-color: #42b883; color: white; border: none; border-radius: 5px; } .controls button:hover { background-color: #33a06f; } .controls input { padding: 5px; width: 60px; text-align: center; margin-right: 10px; } .hint { font-size: 0.9em; color: #666; } </style>

核心知识点:

  1. 响应式数据:count和step通过ref创建。注意在 JavaScript 中修改需要.value,在模板中则不需要。
  2. 计算属性:doubleCount和countParity通过computed创建。它们依赖响应式数据,并缓存结果,只有依赖变化时才重新计算。
  3. 方法:直接声明函数即可,模板中通过@click等指令绑定。
  4. 生命周期:通过onMounted等函数注册生命周期钩子。
  5. 模板语法:
    • {{ }}:文本插值。
    • v-model.number="step":双向数据绑定。.number修饰符确保输入值被转为数字类型。
    • @click:事件监听。

保存所有文件,浏览器中的页面会自动更新。你现在应该能看到一个功能完整的计数器,可以修改步长并进行加减操作。控制台会输出组件挂载的日志。

4. 实战:构建一个简易任务管理器 (Todo List)

现在,我们运用所学,构建一个更实用的功能:任务管理器。它将涵盖列表渲染、条件渲染、事件处理、组件通信等核心概念。

4.1 设计数据结构与状态

首先,在src/components目录下创建TodoList.vue。

一个任务(Todo)对象通常包含:id(唯一标识)、text(内容)、completed(是否完成)。我们将任务列表定义为响应式数组。

<!-- src/components/TodoList.vue --> <script setup> import { ref, computed } from 'vue' // 定义任务类型 // 使用一个简单的自增ID生成器 let nextId = 0 function generateId() { return nextId++ } // 响应式任务列表 const todos = ref([ { id: generateId(), text: '学习 Vue 3 基础', completed: true }, { id: generateId(), text: '完成速成实战项目', completed: false }, { id: generateId(), text: '阅读官方文档', completed: false } ]) // 响应式的新任务文本 const newTodoText = ref('') </script> <template> <div class="todo-list"> <h2>我的任务清单</h2> <!-- 后续步骤将填充这里 --> </div> </template> <style scoped> .todo-list { border: 2px solid #35495e; border-radius: 10px; padding: 20px; margin-top: 30px; background-color: #fff; } </style>

4.2 实现添加任务功能

在<script setup>中添加添加任务的方法,并在模板中创建输入表单。

<script setup> // ... 之前的代码保持不变 // 添加新任务的方法 function addTodo() { const trimmedText = newTodoText.value.trim() if (!trimmedText) { alert('任务内容不能为空!') return } todos.value.push({ id: generateId(), text: trimmedText, completed: false }) newTodoText.value = '' // 清空输入框 } </script> <template> <div class="todo-list"> <h2>我的任务清单</h2> <!-- 添加新任务表单 --> <div class="add-todo"> <input type="text" v-model="newTodoText" @keyup.enter="addTodo" placeholder="输入新任务,按回车添加" /> <button @click="addTodo">添加</button> </div> <!-- 任务列表区域 --> <div class="list-container"> <p v-if="todos.length === 0">暂无任务,添加一个吧!</p> <ul v-else> <!-- 我们将在这里渲染任务列表 --> </ul> </div> </div> </template> <style scoped> /* ... 之前的样式保持不变 ... */ .add-todo { display: flex; gap: 10px; margin-bottom: 20px; } .add-todo input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 5px; font-size: 1em; } .add-todo button { padding: 10px 20px; background-color: #42b883; color: white; border: none; border-radius: 5px; cursor: pointer; } .add-todo button:hover { background-color: #33a06f; } .list-container { min-height: 100px; } </style>

知识点:

  • v-model绑定输入框。
  • @keyup.enter监听回车键事件。
  • v-if/v-else进行条件渲染。

4.3 渲染任务列表与状态切换

现在渲染任务列表,并为每个任务添加完成状态切换和删除功能。

<script setup> // ... 之前的代码保持不变 // 切换任务完成状态 function toggleTodo(todo) { todo.completed = !todo.completed } // 删除任务 function deleteTodo(id) { // 过滤掉 id 不匹配的任务,实现删除 todos.value = todos.value.filter(todo => todo.id !== id) } </script> <template> <div class="todo-list"> <h2>我的任务清单</h2> <!-- ... 添加任务表单保持不变 ... --> <!-- 任务列表区域 --> <div class="list-container"> <p v-if="todos.length === 0">暂无任务,添加一个吧!</p> <ul v-else> <li v-for="todo in todos" :key="todo.id" :class="{ completed: todo.completed }"> <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo)" /> <span class="todo-text">{{ todo.text }}</span> <button class="delete-btn" @click="deleteTodo(todo.id)">×</button> </li> </ul> </div> </div> </template> <style scoped> /* ... 之前的样式保持不变 ... */ ul { list-style: none; padding: 0; } li { display: flex; align-items: center; padding: 12px; margin-bottom: 8px; background-color: #f8f9fa; border-radius: 6px; border-left: 4px solid #42b883; } li.completed { opacity: 0.7; border-left-color: #ccc; } li.completed .todo-text { text-decoration: line-through; color: #888; } .todo-text { flex: 1; margin-left: 10px; text-align: left; } .delete-btn { background: none; border: none; color: #ff6b6b; font-size: 1.5em; cursor: pointer; line-height: 1; padding: 0 5px; } .delete-btn:hover { color: #ff4757; } </style>

知识点:

  • v-for="todo in todos":列表渲染。
  • :key="todo.id":为每个迭代项提供唯一的 key,Vue 用它来高效更新 DOM。
  • :class="{ completed: todo.completed }":动态绑定 CSS 类。
  • :checked:属性绑定。
  • @change和@click:事件处理。

4.4 添加统计功能与筛选器

最后,我们添加一个统计区域,显示任务总数、完成数,并提供一个筛选器来查看所有/进行中/已完成的任务。

<script setup> import { ref, computed } from 'vue' // ... 之前的 todos, newTodoText, 方法等保持不变 ... // 定义筛选类型 const filter = ref('all') // 'all', 'active', 'completed' // 计算属性:根据筛选器过滤后的任务列表 const filteredTodos = computed(() => { switch (filter.value) { case 'active': return todos.value.filter(todo => !todo.completed) case 'completed': return todos.value.filter(todo => todo.completed) default: return todos.value } }) // 计算属性:统计信息 const stats = computed(() => { const total = todos.value.length const completed = todos.value.filter(todo => todo.completed).length const active = total - completed return { total, completed, active } }) </script> <template> <div class="todo-list"> <h2>我的任务清单</h2> <!-- 统计信息 --> <div class="stats"> <span>总计: <strong>{{ stats.total }}</strong></span> <span>进行中: <strong>{{ stats.active }}</strong></span> <span>已完成: <strong>{{ stats.completed }}</strong></span> </div> <!-- 筛选器 --> <div class="filters"> <button :class="{ active: filter === 'all' }" @click="filter = 'all'" >全部</button> <button :class="{ active: filter === 'active' }" @click="filter = 'active'" >进行中</button> <button :class="{ active: filter === 'completed' }" @click="filter = 'completed'" >已完成</button> </div> <!-- ... 添加任务表单保持不变 ... --> <!-- 任务列表区域:现在渲染 filteredTodos --> <div class="list-container"> <p v-if="filteredTodos.length === 0"> {{ filter === 'all' ? '暂无任务,添加一个吧!' : filter === 'active' ? '没有进行中的任务。' : '没有已完成的任务。' }} </p> <ul v-else> <li v-for="todo in filteredTodos" :key="todo.id" :class="{ completed: todo.completed }"> <!-- ... 每个任务的复选框、文本、删除按钮保持不变 ... --> </li> </ul> </div> </div> </template> <style scoped> /* ... 之前的样式保持不变 ... */ .stats { display: flex; justify-content: space-around; margin-bottom: 15px; padding: 10px; background-color: #e9ecef; border-radius: 5px; font-size: 0.9em; } .filters { display: flex; justify-content: center; gap: 10px; margin-bottom: 15px; } .filters button { padding: 6px 12px; border: 1px solid #ddd; background-color: white; border-radius: 4px; cursor: pointer; } .filters button.active { background-color: #42b883; color: white; border-color: #42b883; } </style>

4.5 在 App.vue 中集成组件

最后,在src/App.vue中引入并使用我们的TodoList组件。

<!-- src/App.vue 更新后 --> <script setup> import { ref } from 'vue' import MyCounter from './components/MyCounter.vue' import TodoList from './components/TodoList.vue' // 引入新组件 const appTitle = ref('Vue 3 极简速成 Demo') </script> <template> <div class="app"> <h1>{{ appTitle }}</h1> <p>这是一个使用 Vue 3 Composition API 和 `<script setup>` 构建的简单应用。</p> <MyCounter /> <TodoList /> <!-- 使用任务列表组件 --> </div> </template> <style scoped> .app { font-family: Arial, sans-serif; max-width: 800px; margin: 40px auto; padding: 20px; text-align: center; } h1 { color: #42b883; } </style>

保存所有文件,你的浏览器中现在应该展示一个包含计数器和完整任务管理器的应用了!你可以添加任务、标记完成、删除任务,并使用筛选器查看不同状态的任务列表。

5. 状态管理入门:使用 Pinia

当应用变大,多个组件需要共享状态时,我们就需要状态管理。Vue 3 官方推荐使用Pinia。它比 Vuex 更简单、类型安全,且与组合式 API 是天作之合。我们在创建项目时已经安装了 Pinia,现在来使用它。

5.1 创建一个 Store

Store 是一个保存状态和业务逻辑的实体。我们在src/stores目录下创建一个todoStore.js。

// src/stores/todoStore.js import { defineStore } from 'pinia' import { ref, computed } from 'vue' // defineStore 的第一个参数是 store 的唯一 ID export const useTodoStore = defineStore('todo', () => { // State: 相当于组件中的 data const todos = ref([]) let nextId = 0 // Getters: 相当于组件中的 computed const completedTodos = computed(() => todos.value.filter(todo => todo.completed)) const activeTodos = computed(() => todos.value.filter(todo => !todo.completed)) const stats = computed(() => ({ total: todos.value.length, completed: completedTodos.value.length, active: activeTodos.value.length })) // Actions: 相当于组件中的 methods function addTodo(text) { const trimmedText = text.trim() if (!trimmedText) return todos.value.push({ id: nextId++, text: trimmedText, completed: false }) } function toggleTodo(id) { const todo = todos.value.find(t => t.id === id) if (todo) { todo.completed = !todo.completed } } function deleteTodo(id) { todos.value = todos.value.filter(todo => todo.id !== id) } function clearCompleted() { todos.value = todos.value.filter(todo => !todo.completed) } // 必须返回 state 和 actions return { todos, completedTodos, activeTodos, stats, addTodo, toggleTodo, deleteTodo, clearCompleted } })

5.2 在组件中使用 Store

现在,我们修改TodoList.vue组件,使其使用 Pinia Store 来管理状态。

<!-- src/components/TodoList.vue (使用 Pinia 版本) --> <script setup> import { ref, computed } from 'vue' import { useTodoStore } from '../stores/todoStore' // 导入 store // 在 setup 中调用 useTodoStore() 来获取 store 实例 const todoStore = useTodoStore() // 本地状态:筛选器和输入框文本 const filter = ref('all') const newTodoText = ref('') // 使用 store 中的计算属性 const filteredTodos = computed(() => { switch (filter.value) { case 'active': return todoStore.activeTodos case 'completed': return todoStore.completedTodos default: return todoStore.todos } }) // 使用 store 中的 actions function addTodo() { todoStore.addTodo(newTodoText.value) newTodoText.value = '' } function toggleTodo(id) { todoStore.toggleTodo(id) } function deleteTodo(id) { todoStore.deleteTodo(id) } function clearCompleted() { todoStore.clearCompleted() } </script> <template> <div class="todo-list"> <h2>我的任务清单 (Pinia 状态管理)</h2> <!-- 统计信息,数据来自 store --> <div class="stats"> <span>总计: <strong>{{ todoStore.stats.total }}</strong></span> <span>进行中: <strong>{{ todoStore.stats.active }}</strong></span> <span>已完成: <strong>{{ todoStore.stats.completed }}</strong></span> <button v-if="todoStore.stats.completed > 0" @click="clearCompleted" class="clear-btn"> 清除已完成 </button> </div> <!-- ... 筛选器、添加表单、任务列表的模板部分与之前几乎完全相同 ... --> <!-- 只需将之前的方法调用替换为调用 store 的 actions,数据绑定替换为 store 的 state/getters --> <!-- 例如:v-for="todo in filteredTodos", @change="toggleTodo(todo.id)" --> <div class="filters"> <button :class="{ active: filter === 'all' }" @click="filter = 'all'">全部</button> <button :class="{ active: filter === 'active' }" @click="filter = 'active'">进行中</button> <button :class="{ active: filter === 'completed' }" @click="filter = 'completed'">已完成</button> </div> <div class="add-todo"> <input type="text" v-model="newTodoText" @keyup.enter="addTodo" placeholder="输入新任务,按回车添加" /> <button @click="addTodo">添加</button> </div> <div class="list-container"> <p v-if="filteredTodos.length === 0"> {{ filter === 'all' ? '暂无任务,添加一个吧!' : filter === 'active' ? '没有进行中的任务。' : '没有已完成的任务。' }} </p> <ul v-else> <li v-for="todo in filteredTodos" :key="todo.id" :class="{ completed: todo.completed }"> <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo.id)" /> <span class="todo-text">{{ todo.text }}</span> <button class="delete-btn" @click="deleteTodo(todo.id)">×</button> </li> </ul> </div> </div> </template> <style scoped> /* ... 样式保持不变,可添加 .clear-btn 的样式 ... */ .clear-btn { margin-left: 15px; padding: 3px 8px; font-size: 0.8em; background-color: #ff6b6b; color: white; border: none; border-radius: 3px; cursor: pointer; } .clear-btn:hover { background-color: #ff4757; } </style>

关键变化:

  1. 组件不再自己管理todos数组和nextId,这些状态移到了 Store 中。
  2. 组件通过useTodoStore()获取 store 实例,然后访问其状态 (todoStore.todos) 和调用其方法 (todoStore.addTodo())。
  3. 逻辑更加清晰,状态管理被抽离,组件只负责视图和用户交互。

现在,如果有另一个组件也需要读取或修改任务列表,它只需要导入并使用同一个useTodoStore(),状态自然就是共享的。这就是 Pinia 的强大之处。

6. 常见问题与排查思路

在学习和使用 Vue 3 的过程中,你可能会遇到一些典型问题。这里列出几个高频问题及其解决方法。

问题现象可能原因解决思路
模板中数据不更新1. 数据不是响应式的。
2. 直接修改了数组/对象的属性,未触发响应。
1. 确保使用ref或reactive创建数据。
2. 对于reactive对象,直接赋值新属性不会响应。应使用Object.assign或解构。对于数组,避免使用索引直接赋值,使用push、splice或返回新数组。
ref值在 JS 中访问报错或不对忘记了.value。在<script setup>的 JavaScript 部分访问ref变量,必须使用.value。在模板中则不需要。
组件引入后无法使用1. 组件未正确导入或注册。
2. 使用了选项式 API 但未在components选项中注册。
1. 检查导入路径是否正确。
2. 在<script setup>中,import 的组件会自动注册。如果使用普通的<script>,需要在components选项中注册。
Pinia store 获取不到数据1. Store 未在应用级别挂载。
2. 在setup()外使用了 store。
1. 确保在main.js中正确创建并安装了 Pinia 插件。
2. 必须在setup()函数或<script setup>中调用useXxxStore()。
Vite 开发服务器无法启动1. 端口被占用。
2. Node.js 版本过低。
3. 依赖安装不完整。
1. 尝试npm run dev -- --port 3000指定其他端口。
2. 升级 Node.js 到 LTS 版本。
3. 删除node_modules和package-lock.json,重新运行npm install。
<script setup>中定义的方法在模板中找不到方法未在顶层声明,或包裹在了其他作用域内。确保所有需要暴露给模板的变量、函数都在<script setup>的顶层作用域中定义。

7. 最佳实践与下一步学习建议

恭喜你完成了 Vue 3 的核心速成!为了写出更健壮、可维护的代码,请遵循以下最佳实践,并规划你的下一步学习路径。

7.1 编码最佳实践

  1. 优先使用<script setup>:对于新项目和新组件,这是官方推荐的写法,更简洁,TypeScript 支持更好。
  2. 合理的组件拆分:当一个组件变得过于庞大(逻辑复杂、模板过长)时,考虑将其拆分为多个更小、职责更单一的组件。
  3. 使用组合式函数抽离逻辑:将可复用的响应式逻辑(如获取数据、监听窗口大小)提取到独立的组合式函数中(通常放在composables/目录下)。
  4. 为列表渲染始终提供key:v-for指令中的:key必须是唯一且稳定的标识符(如数据库 ID),不要使用数组索引。
  5. 谨慎使用v-if和v-for在同一元素:Vue 3 中这会导致错误。如果需要条件渲染列表,可以在外层包裹一个<template>标签使用v-for,在内层元素使用v-if。
  6. 状态管理选择:对于简单的父子组件通信,使用props和emit。对于跨层级、非父子组件共享状态,或状态逻辑复杂时,使用 Pinia。
  7. 样式作用域:在单文件组件中,使用<style scoped>来限制样式只作用于当前组件,避免全局污染。

7.2 工程化建议

  1. 启用 ESLint 和 Prettier:我们在创建项目时已经选择了它们。它们能自动规范代码格式和发现潜在错误,保持团队代码风格一致。
  2. 使用 TypeScript:对于任何严肃的项目,强烈建议使用 TypeScript。它能提供强大的类型检查,减少运行时错误,提升开发体验。创建项目时可以选择添加。
  3. 路由管理:当应用有多个页面时,需要引入 Vue Router。使用npm install vue-router安装,并配置路由表。
  4. HTTP 请求库:推荐使用axios或fetch进行网络请求。可以将其封装成统一的请求模块,便于管理拦截器、基地址等。
  5. 构建与部署:使用npm run build进行生产构建,生成的静态文件位于dist目录,可以部署到任何静态文件服务器或云服务。

7.3 下一步学习路线

你已经掌握了 Vue 3 的核心。要成为一名合格的 Vue 开发者,建议按以下顺序深入学习:

  1. 官方文档:通读 Vue 3 官方文档的基础和深入组件章节,这是最权威的学习资料。
  2. Vue Router:学习如何构建单页面应用 (SPA),管理路由、导航守卫、懒加载等。
  3. 状态管理进阶:深入学习 Pinia,了解 Store 的模块化、持久化、插件等高级用法。
  4. 组合式 API 进阶:学习watch、watchEffect、provide/inject、自定义指令、Teleport、Suspense 等高级特性。
  5. 生态工具:学习使用 Vue Devtools 进行调试,了解 Vite 的更多配置,学习单元测试 (如 Vitest) 和端到端测试 (如 Cypress)。
  6. UI 组件库:根据项目需求,学习使用一个成熟的 UI 组件库,如 Element Plus、Ant Design Vue、Vuetify 等,能极大提升开发效率。
  7. 实战项目:找一个完整的项目点子(如博客系统、后台管理前端),综合运用所学知识进行开发,这是巩固技能的最佳方式。

本文的完整项目源码已包含计数器、任务管理器(含 Pinia 版本)等所有示例代码,你可以基于此进行扩展和练习。记住,编程是门实践的艺术,多写、多思考、多查阅文档,你很快就能驾驭 Vue 3。如果在实践中遇到具体问题,CSDN 社区和官方文档是你最好的朋友。

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

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

立即咨询