Vue.js 详解教程 – wiki基地


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




Vue 3 Hello World


{{ message }}


“`

将这段代码保存为 .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 版本)。

  1. 打开终端或命令行工具。
  2. 运行以下命令创建一个新的 Vue 项目:

    “`bash
    npm create vue@latest

    或者使用 yarn

    yarn create vue@latest

    或者使用 pnpm

    pnpm create vue@latest

    ``
    3. 按照提示选择项目选项(例如,是否使用 TypeScript, JSX, Vue Router, Pinia, Vitest 进行单元测试, Cypress 进行 E2E 测试, ESLint/Prettier 进行代码规范)。对于初学者,可以先选择 N 来跳过大部分可选功能,只创建一个最基本的 Vue 项目。
    4. 进入项目目录:
    cd your-project-name5. 安装依赖:npm install(或yarn install,pnpm install)
    6. 启动开发服务器:
    npm run dev(或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-ifv-else-ifv-else 根据条件渲染元素。

    “`html

    Welcome, user!

    Please log in.

    A
    B
    C

    ``v-if会完全销毁/重建元素及其组件。
    * **条件显示:** 使用
    v-show根据条件切换元素的 CSSdisplay` 属性。

    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


    ``
    **事件修饰符:** Vue 提供了事件修饰符来处理常见的 DOM 事件细节,如
    .stop(阻止事件冒泡)、.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-modelv-bind:valuev-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

“`

Options API 中的注册:

“`vue

“`

4.3 组件间的通信

组件通信是组件化开发中的关键部分。主要有三种方式:

  1. Props (父向子): 父组件通过属性(prop)向子组件传递数据。
    子组件 (ChildComponent.vue):

    “`vue



    ``defineProps是 Composition API 的宏,无需导入,在
    ```

  2. Events (子向父): 子组件通过触发事件 ($emitdefineEmits) 向父组件发送消息。
    子组件 (ChildComponent.vue):

    ```vue



    ``defineEmits` 是 Composition API 的宏,用于声明组件会触发哪些事件,这有助于提高代码可读性和类型安全。

    父组件 (ParentComponent.vue):

    ```vue


    ```

  3. 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


    ```
    插槽是实现高可复用组件(如布局组件、容器组件)的强大方式。

第五部分:生命周期钩子 (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> 标签,但它会智能地处理导航而不会触发页面重新加载。

基本用法示例:

  1. 安装 Vue Router:npm install vue-router@4
  2. 创建路由文件 (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
    ``
    3. 在主应用文件 (
    main.js`) 中使用路由器:

    ```javascript
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router' // 导入路由器

    const app = createApp(App)

    app.use(router) // 使用路由器

    app.mount('#app')
    ``
    4. 在应用根组件 (
    App.vue) 中使用`:

    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 基本用法示例:

  1. 安装 Pinia:npm install pinia
  2. 创建 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
    }
    }
    })
    ``
    3. 在主应用文件 (
    main.js`) 中使用 Pinia:

    ```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


    ``
    在 Composition API 中,直接调用
    useStore()hook 并在

    滚动至顶部