Vue.js 详解教程:从入门到精通
Vue.js 是一款流行的、渐进式 JavaScript 框架,用于构建用户界面。它易于上手,同时也能够驱动复杂的单页应用(SPA)。本教程将带你深入 Vue.js 的世界,从基础概念到进阶实践,助你成为 Vue.js 开发的能手。
第一部分:初识 Vue.js
1.1 什么是 Vue.js?
Vue.js 是由尤雨溪(Evan You)创建的一个开源 JavaScript 框架,其核心目标是构建用户界面。与其他重量级框架(如 Angular)不同,Vue 被设计为可以自底向上逐层应用。它的核心库只关注视图层,可以轻松地与其他库或已有项目整合。同时,Vue 也提供了配套的生态系统,比如 Vue Router(路由)、Pinia(状态管理)、Vite(构建工具)等,用于构建更复杂的单页应用。
渐进式框架 是 Vue 的一个重要特性。这意味着你可以只使用 Vue 的核心库来增强现有的 HTML 页面,或者构建一个完整的单页应用,甚至将其作为桌面应用或移动应用的开发框架(借助 Electron 或 NativeScript-Vue 等)。你可以根据项目需求,逐步引入和使用 Vue 的不同部分。
1.2 为什么选择 Vue.js?
选择 Vue.js 有许多理由:
- 易学易用: Vue 的 API 设计简洁直观,文档详尽,对于有 HTML, CSS 和 JavaScript 基础的前端开发者来说,学习曲线非常平缓。你可以很快上手并开始开发。
- 高性能: Vue 使用了虚拟 DOM (Virtual DOM),通过对比新旧虚拟 DOM 树的差异来最小化对实际 DOM 的操作,从而提高了渲染性能。在 Vue 3 中,通过编译时优化、Proxy 的响应式系统等,性能得到了进一步提升。
- 灵活性: Vue 允许你以多种方式组织代码,无论是使用 Options API 还是 Composition API,无论是集成到现有项目还是构建全新的 SPA,Vue 都能适应。
- 丰富的生态系统: Vue 提供了官方维护的配套库,如 Vue Router、Pinia、Vite、Vue Test Utils 等,覆盖了构建现代 Web 应用所需的各个方面。社区也贡献了大量优秀的第三方库和组件库。
- 活跃的社区: Vue 拥有庞大的全球社区,提供了大量的教程、案例和技术支持。
1.3 Vue 2 vs Vue 3
本教程主要基于 Vue 3 进行讲解,因为它是 Vue 的最新稳定版本,带来了许多改进和新特性:
- 性能提升: 更小的包体积,更快的虚拟 DOM,更高的渲染性能,更低的内存占用。
- 更好的 TypeScript 支持: 使用 TypeScript 重写,提供了更完善的类型推导。
- Composition API: 一种新的编写组件逻辑的方式,提供了更好的代码组织和逻辑复用能力。
- Proxy 响应式系统: 替代了 Vue 2 的
Object.defineProperty
,能够监听属性的添加和删除,以及数组索引和长度的变化。 - 新的生命周期钩子命名。
- Teleport、Fragments、Suspense 等新特性。
虽然 Vue 2 在许多现有项目中仍然被使用,但对于新项目,强烈建议使用 Vue 3。
第二部分:环境搭建与 Hello World
开始学习 Vue.js 的最快方式是通过 CDN 引入,但对于实际项目开发,我们通常使用构建工具。
2.1 使用 CDN 快速体验
这是一个最简单的 Vue 3 “Hello World” 示例:
“`html
“`
将这段代码保存为 .html
文件并在浏览器中打开,你将看到 “Hello Vue!” 的字样。
<script src="https://unpkg.com/vue@3/dist/vue.global.js">
:通过 CDN 引入 Vue 3 库。<div id="app">
:这是 Vue 应用将要挂载的 DOM 元素。{{ message }}
:这是 Vue 的模板语法,用于将message
数据绑定到视图。当message
的值改变时,这里的内容会自动更新。createApp({...}).mount('#app')
:创建 Vue 应用实例并将其挂载到#app
元素上。data()
函数返回应用的状态对象。
2.2 使用 构建工具 (Vite + create-vue)
对于大多数实际项目,我们会使用构建工具来利用 Vue 的单文件组件(Single File Components, SFCs)、模块化、预处理器等特性。Vue 官方推荐使用 Vite 作为构建工具,并通过 create-vue
脚手架来快速创建项目。
前提: 确保你安装了 Node.js (推荐 LTS 版本)。
- 打开终端或命令行工具。
-
运行以下命令创建一个新的 Vue 项目:
“`bash
npm create vue@latest或者使用 yarn
yarn create vue@latest
或者使用 pnpm
pnpm create vue@latest
``
cd your-project-name
3. 按照提示选择项目选项(例如,是否使用 TypeScript, JSX, Vue Router, Pinia, Vitest 进行单元测试, Cypress 进行 E2E 测试, ESLint/Prettier 进行代码规范)。对于初学者,可以先选择 N 来跳过大部分可选功能,只创建一个最基本的 Vue 项目。
4. 进入项目目录:5. 安装依赖:
npm install(或
yarn install,
pnpm install)
npm run dev
6. 启动开发服务器:(或
yarn dev,
pnpm dev`)
这将在本地启动一个开发服务器,通常在 http://localhost:5173/
。打开浏览器访问该地址,你将看到默认的 Vue 欢迎页面。
使用构建工具创建的项目结构通常包含 src
目录,其中包含了应用的核心代码,特别是 .vue
扩展名的单文件组件。
第三部分:核心概念深度解析
现在我们来深入了解 Vue.js 的几个核心概念。
3.1 响应式系统 (Reactivity System)
响应式是 Vue 最强大的特性之一。当 Vue 应用的状态(数据)发生变化时,视图会自动更新以反映这些变化,而无需手动操作 DOM。
Vue 3 中的响应式: Vue 3 使用 JavaScript 的 Proxy 对象来实现响应式。这意味着 Vue 可以拦截对数据的访问和修改,并在数据变化时通知相关的视图进行更新。
在 Composition API 中:
-
ref()
:用于创建一个响应式引用。它接收一个内部值,返回一个包含.value
属性的对象,该属性指向内部值。修改.value
会触发响应式更新。适用于基本数据类型(字符串、数字、布尔值等)和对象。“`javascript
import { ref } from ‘vue’const count = ref(0) // 创建一个响应式引用
console.log(count.value) // 访问值
count.value++ // 修改值,触发更新
``
reactive()`:用于创建一个响应式对象或数组。它接收一个对象或数组,并返回一个原始对象的 Proxy。对返回的响应式对象的属性进行修改会触发更新。适用于对象和数组。
*“`javascript
import { reactive } from ‘vue’const state = reactive({
name: ‘Vue’,
version: 3
}) // 创建一个响应式对象
console.log(state.name) // 访问属性
state.version = 4 // 修改属性,触发更新
state.newProp = ‘Added’ // 新增属性,也会触发更新 (Proxy 的优势)const list = reactive([1, 2, 3]) // 创建一个响应式数组
list.push(4) // 数组操作也会触发更新
“`
在 Options API 中:
-
data()
:组件的响应式数据通常在data
函数中定义。该函数必须返回一个对象。Vue 会遍历这个对象的属性,并使用Object.defineProperty
(Vue 2)或 Proxy(Vue 3)将其转换为响应式。javascript
export default {
data() {
return {
message: 'Hello Options API',
items: [1, 2]
}
}
}
需要注意的是,在 Options API 中,直接添加新属性到data
返回的对象中(Vue 2)或数组中通过索引直接修改(Vue 2)可能不是响应式的。Vue 3 使用 Proxy 解决了大部分这类问题,但在某些边缘情况下,或者为了兼容 Vue 2 写法,可能仍然需要使用Vue.set
(Vue 2) 或$el.$forceUpdate
等,但 Composition API 结合reactive
是更推荐的方式。
3.2 模板语法 (Template Syntax)
Vue 使用基于 HTML 的模板语法,允许你声明式地将 DOM 绑定到底层实例的数据。
-
文本插值: 使用双大括号
{{ }}
将数据绑定到文本内容。html
<span>Message: {{ message }}</span>
插值内部可以是任何 JavaScript 表达式,但只能是单行表达式。
* 原始 HTML: 如果你想渲染 HTML 而不是纯文本,可以使用v-html
指令。html
<div v-html="rawHtml"></div>
注意: 使用v-html
存在 XSS 攻击风险,只在你确定 HTML 内容是安全的情况下使用。 -
属性绑定: 使用
v-bind
指令将元素的属性绑定到数据。缩写为:
。html
<img v-bind:src="imageUrl">
<a :href="linkUrl">Link</a>
<button :disabled="isButtonDisabled">Button</button>
* 动态参数: 属性绑定、事件绑定等指令的参数也可以是动态的。html
<a :[attributeName]="url"> ... </a>
<button @[eventName]="handler"> ... </button>
* 条件渲染: 使用v-if
、v-else-if
、v-else
根据条件渲染元素。“`html
Welcome, user!
Please log in.
ABC``
v-if会完全销毁/重建元素及其组件。
v-show
* **条件显示:** 使用根据条件切换元素的 CSS
display` 属性。html
<p v-show="isVisible">This paragraph is visible.</p>
v-show
总是渲染元素,只是通过 CSS 控制其显示与隐藏。频繁切换显示/隐藏使用v-show
性能更好,而条件不常改变时使用v-if
更佳。
* 列表渲染: 使用v-for
遍历数组或对象的元素,并渲染一个列表。“`html
- {{ item.text }}
- {{ index }}. {{ key }}: {{ value }}
{{ n }}
``
key
**的重要性:** 使用
v-for时,总是推荐为列表项提供一个唯一的
key属性。Vue 使用
key来跟踪每个节点的身份,从而更有效地复用和重新排序元素,而不是从头渲染。避免使用数组索引作为
key,除非列表内容永不改变或顺序不会打乱。
v-on
* **事件处理:** 使用指令监听 DOM 事件并执行 JavaScript 代码。缩写为
@`。“`html
``
.stop
**事件修饰符:** Vue 提供了事件修饰符来处理常见的 DOM 事件细节,如(阻止事件冒泡)、
.prevent(阻止默认行为)、
.capture(使用捕获模式)、
.self(只在事件源是元素本身时触发)、
.once(只触发一次)、
.passive` (提高移动设备滚动性能)。html
<a @click.prevent="doSomething">Prevent Default</a>
<div @click.stop="doSomethingElse">Stop Propagation</div>
* 表单输入绑定: 使用v-model
指令在表单输入元素或自定义组件上创建双向数据绑定。“`html
Message is: {{ message }}
Selected: {{ selected }}
``
v-model是
v-bind:value和
v-on:input`(或其他事件,取决于元素类型)的语法糖。
3.3 计算属性与侦听器 (Computed Properties and Watchers)
-
计算属性 (
computed
): 用于处理复杂的逻辑,以派生出新的数据。计算属性是基于它们的响应式依赖进行缓存的。只有当其依赖的响应式数据发生改变时,才会重新计算。Composition API:
“`javascript
import { ref, computed } from ‘vue’const firstName = ref(‘John’)
const lastName = ref(‘Doe’)const fullName = computed(() => {
return firstName.value + ‘ ‘ + lastName.value
})console.log(fullName.value) // John Doe
firstName.value = ‘Jane’
console.log(fullName.value) // Jane Doe (会自动更新)
**Options API:**
javascript
export default {
data() {
return {
firstName: ‘John’,
lastName: ‘Doe’
}
},
computed: {
fullName() {
return this.firstName + ‘ ‘ + this.lastName
}
}
}
计算属性默认只有 getter,也可以提供 setter:
javascript
const fullName = computed({
get() {
return firstName.value + ‘ ‘ + lastName.value
},
set(newValue) {
const names = newValue.split(‘ ‘)
firstName.value = names[0]
lastName.value = names[names.length – 1]
}
})fullName.value = ‘Jane Smith’ // 调用 setter,firstName 变为 ‘Jane’, lastName 变为 ‘Smith’
“`
何时使用计算属性? 当你需要根据现有响应式数据派生出新的响应式数据时,优先考虑计算属性。它具有缓存机制,效率更高。 -
侦听器 (
watch
): 用于观察和响应响应式数据的变化。当你想在数据变化时执行一些“副作用”,比如异步操作、复杂的数据变化逻辑、或者根据数据的变化与其他系统交互时,可以使用侦听器。Composition API:
“`javascript
import { ref, watch } from ‘vue’const question = ref(”)
const answer = ref(‘Thinking…’)watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.includes(‘?’)) {
answer.value = ‘Thinking…’
try {
const res = await fetch(‘https://yesno.wtf/api’)
answer.value = (await res.json()).answer
} catch (error) {
answer.value = ‘Error! Could not reach the API. ‘ + error
}
}
})
你可以侦听单个 `ref`,也可以侦听多个 `ref` 或 `reactive` 对象(以数组形式)。
javascript
watch([firstName, lastName], (newValues, oldValues) => {
console.log(‘Names changed:’, newValues)
})
``
deep: true
你可以设置来深度侦听对象或数组内部的变化,或者设置
immediate: true` 在侦听器创建时立即执行一次回调。Options API:
javascript
export default {
data() {
return {
question: '',
answer: 'Thinking...'
}
},
watch: {
// 侦听 question 的变化
question(newQuestion, oldQuestion) {
if (newQuestion.includes('?')) {
this.getAnswer()
}
}
},
methods: {
async getAnswer() {
this.answer = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
this.answer = (await res.json()).answer
} catch (error) {
this.answer = 'Error! Could not reach the API. ' + error
}
}
}
}
何时使用侦听器? 当你需要在某个数据变化时执行异步操作或复杂的、有副作用的逻辑时,使用侦听器。计算属性用于派生新数据并具备缓存,而侦听器用于观察数据变化并执行操作。避免在侦听器中修改被侦听的数据,除非你明确需要这个反馈循环。
第四部分:组件化开发 (Component-Based Development)
组件是 Vue 应用的基石。它们是可复用的 Vue 实例,拥有自己的状态(data)、模板和逻辑。组件化让应用结构清晰、易于维护和扩展。
4.1 单文件组件 (Single File Components – SFCs)
在 Vue 项目中,最常见的组织组件的方式是使用单文件组件(.vue
文件)。一个 .vue
文件通常包含三个顶层块:
<template>
:包含组件的 HTML 模板。<script>
:包含组件的 JavaScript 逻辑(Composition API 或 Options API)。<style>
:包含组件的 CSS 样式。可以添加scoped
属性使样式只应用于当前组件。
示例 MyComponent.vue
:
“`vue
或者使用 Options API:
vue
“`
4.2 注册与使用组件
在 .vue
文件中,你需要在父组件中导入并注册子组件,然后才能在模板中使用它。
示例 ParentComponent.vue:
“`vue
Parent Component
“`
Options API 中的注册:
“`vue
Parent Component
“`
4.3 组件间的通信
组件通信是组件化开发中的关键部分。主要有三种方式:
-
Props (父向子): 父组件通过属性(prop)向子组件传递数据。
子组件 (ChildComponent.vue
):“`vue
Message from parent: {{ parentMessage }}
``
defineProps是 Composition API 的宏,无需导入,在
``` -
Events (子向父): 子组件通过触发事件 (
$emit
或defineEmits
) 向父组件发送消息。
子组件 (ChildComponent.vue
):```vue
``
defineEmits` 是 Composition API 的宏,用于声明组件会触发哪些事件,这有助于提高代码可读性和类型安全。父组件 (
ParentComponent.vue
):```vue
Received from child: {{ childMessage }}
``` -
Slots (内容分发): 父组件可以在子组件的标签内放入内容,子组件通过
<slot>
插槽来决定这些内容在何处显示。
子组件 (LayoutComponent.vue
):vue
<template>
<div class="layout">
<header>
<slot name="header"></slot> <!-- 具名插槽 -->
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot> <!-- 具名插槽 -->
</footer>
</div>
</template>
父组件 (ParentComponent.vue
):```vue
This is the main content.
<!-- 具名插槽内容,使用 v-slot 指令,缩写为 # --> <template v-slot:header> <!-- <template #header> --> <h1>Page Title</h1> </template> <template #footer> <p>© 2023</p> </template>
```
插槽是实现高可复用组件(如布局组件、容器组件)的强大方式。
第五部分:生命周期钩子 (Lifecycle Hooks)
每个 Vue 组件在创建、挂载、更新和销毁过程中都会经过一系列的阶段。在这些阶段,Vue 提供了生命周期钩子函数,允许你在特定时机执行自定义逻辑。
Vue 3 Composition API 生命周期钩子 (在 <script setup>
或 setup()
函数中使用):
onBeforeMount
: 组件在 DOM 挂载之前调用。onMounted
: 组件挂载到 DOM 后调用。常用于执行需要访问 DOM 的操作、发送初始 AJAX 请求、集成第三方库等。onBeforeUpdate
: 数据变化导致组件更新之前调用。onUpdated
: 组件完成 DOM 更新后调用。onBeforeUnmount
: 组件卸载(unmount)之前调用。清理定时器、移除事件监听器等。onUnmounted
: 组件从 DOM 卸载后调用。
Vue 3 Options API 生命周期选项:
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeUnmount
unmounted
示例 (Composition API):
```vue
```
示例 (Options API):
```vue
```
理解生命周期对于正确地执行初始化、更新和清理逻辑至关重要。
第六部分:路由与状态管理
对于构建复杂的单页应用,通常需要客户端路由和集中的状态管理。
6.1 Vue Router
Vue Router 是 Vue 官方的路由库。它可以将组件映射到不同的 URL 路径,并处理导航。
核心概念:
- 路由映射 (Route Records): 定义 URL 路径和组件的对应关系。
- 路由器实例 (Router Instance): 通过路由映射创建,并配置路由模式 (history 或 hash)。
<router-view>
: 渲染当前匹配到的组件的占位符。<router-link>
: 创建导航链接,类似于 HTML 的<a>
标签,但它会智能地处理导航而不会触发页面重新加载。
基本用法示例:
- 安装 Vue Router:
npm install vue-router@4
-
创建路由文件 (
router/index.js
):```javascript
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue' // 假设有 AboutView 组件const router = createRouter({
// 使用 history 模式,需要服务器端配置支持
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: AboutView // 或者使用动态导入实现懒加载: () => import('../views/AboutView.vue')
}
]
})export default router
``
main.js`) 中使用路由器:
3. 在主应用文件 (```javascript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // 导入路由器const app = createApp(App)
app.use(router) // 使用路由器
app.mount('#app')
``
App.vue
4. 在应用根组件 () 中使用
和
`: vue
<template>
<div>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view></router-view> <!-- 渲染匹配到的组件 -->
</div>
</template>
6.2 状态管理 (Pinia 或 Vuex)
随着应用规模的增长,不同组件之间共享状态(数据)变得越来越困难。Vue 官方推荐使用 Pinia 或 Vuex 来进行集中式状态管理。
- Vuex (Vue 2 时代主流,Vue 3 也兼容): 提供了 Store 的概念,包含 state (状态)、mutations (同步修改状态)、actions (异步操作或复杂同步操作)、getters (基于状态派生新数据) 和 modules (模块化)。
- Pinia (Vue 3 推荐): 设计更简单,提供了更好的 TypeScript 支持,没有 mutations 的概念 (直接在 actions 中修改状态),模块化是内置的。
Pinia 基本概念:
- Store: 定义一个独立的 Store,包含 state, getters, actions。
- State: 响应式的状态数据。
- Getters: 类似于组件的计算属性,从 state 中派生数据。
- Actions: 类似于组件的方法,用于修改 state,可以包含异步逻辑。
Pinia 基本用法示例:
- 安装 Pinia:
npm install pinia
-
创建 Store 文件 (
stores/counter.js
):```javascript
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
incrementBy(amount) {
this.count += amount
}
}
})
``
main.js`) 中使用 Pinia:
3. 在主应用文件 (```javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const app = createApp(App)
const pinia = createPinia()app.use(pinia) // 使用 Pinia
app.mount('#app')
```
4. 在组件中使用 Store:```vue
Count: {{ counter.count }}
Double Count: {{ counter.doubleCount }}
``
useStore()
在 Composition API 中,直接调用hook 并在