Vue.js 和 Vue 3 全面详解指南

news2025/3/31 2:50:47

1. Vue.js 基础介绍

1.1 什么是 Vue.js

Vue.js(简称 Vue)是一个用于构建用户界面的渐进式 JavaScript 框架。与其他框架不同,Vue 被设计为可以逐步采用。Vue 的核心库只关注视图层,易于上手,便于与其他库或既有项目整合。

Vue 由尤雨溪(Evan You)在 2014 年创建。尤雨溪曾在 Google 工作,参与了 AngularJS 的开发,后来他希望提取 Angular 中他认为精华的部分,构建一个更轻量级的框架,于是创建了 Vue。

1.2 Vue.js 的特点

  1. 渐进式框架:可以逐步将 Vue 集成到项目中,无需一次全部采用。

  2. 响应式系统:Vue 提供了响应式且组件化的视图组件,当数据变化时,视图会自动更新。

  3. 虚拟 DOM:Vue 使用虚拟 DOM(Virtual DOM)技术提高渲染性能。

  4. 组件化开发:鼓励将应用拆分为独立可复用的组件,构建出大型应用。

  5. 轻量级:Vue 的体积小巧,压缩后仅约 20KB(Vue 2)或 10KB(Vue 3)。

  6. 易学易用:相较于其他前端框架,Vue 的学习曲线更平缓,API 设计简单直观。

  7. 丰富的工具链:Vue 提供了完整的开发工具链,如 Vue CLI、Vite、DevTools 等。

1.3 Vue.js 应用场景

Vue 可适用于多种场景:

  1. 单页应用(SPA):使用 Vue Router 构建单页应用,避免页面刷新提升用户体验。

  2. 多页应用:将 Vue 组件集成到传统的多页面应用中。

  3. 移动端应用:结合 Cordova、Capacitor 或 NativeScript 构建移动应用。

  4. 桌面应用:结合 Electron 构建桌面应用。

  5. 服务端渲染:使用 Nuxt.js 或手动配置 Vue SSR。

  6. 静态站点生成:使用 VuePress 或 Gridsome 生成静态站点。

1.4 Vue.js 版本历史

  • Vue 1.0:2015 年 10 月发布,奠定了 Vue 的基础架构。
  • Vue 2.0:2016 年 9 月发布,引入虚拟 DOM,提升性能。
  • Vue 2.6:2019 年 2 月发布,添加了 Composition API RFC 等特性。
  • Vue 3.0:2020 年 9 月发布,全新的架构,更好的性能和更小的体积。
  • Vue 3.2:2021 年 8 月发布,引入 <script setup> 语法。
  • Vue 3.3:2023 年 5 月发布,改进了 TypeScript 支持和宏性能。
  • Vue 3.4:2023 年 12 月发布,改进了渲染器和编译器性能。

2. Vue 2 与 Vue 3 比较

2.1 核心架构变化

特性 Vue 2 Vue 3
响应式系统 Object.defineProperty Proxy
代码组织 Options API 为主 Options API + Composition API
模板编译 模板编译为渲染函数 改进的模板编译策略,更好的静态提升
虚拟 DOM 基本实现 重写,更快的挂载和更新速度
TypeScript 支持 有限支持 完全支持,代码库用 TS 重写
Tree-Shaking 有限支持 全面支持,更小的打包体积

2.2 API 变化

Vue 3 新增的 API

  • Composition API(setup, ref, reactive 等)
  • Teleport 组件
  • Fragments(片段)
  • Suspense 组件
  • createApp 替代 new Vue()
  • 多个根节点支持
  • emits 选项

Vue 3 移除的 API

  • $on, $off, $once 事件 API
  • 过滤器(Filters)
  • $children 实例属性
  • $destroy 实例方法

2.3 性能对比

Vue 3 相比 Vue 2 在性能上有显著提升:

  1. 更小的包体积:Vue 3 核心库体积比 Vue 2 减小了约 41%,最小化和压缩后仅约 10KB。

  2. 更快的初始渲染:Vue 3 初始渲染速度比 Vue 2 快约 55%。

  3. 更高效的更新:由于优化的虚拟 DOM 和编译时提示,Vue 3 的更新性能比 Vue 2 快约 133%。

  4. 内存占用更低:Vue 3 减少了约 54% 的内存使用量。

2.4 生态系统适配

Vue 3 发布后,主要生态系统库逐步适配:

  • Vue Router:4.x 版本支持 Vue 3
  • Vuex:4.x 版本支持 Vue 3
  • Pinia:新一代状态管理库,专为 Vue 3 设计
  • Vite:新一代构建工具,原生支持 Vue 3
  • Nuxt:3.x 版本支持 Vue 3
  • UI 库:Element Plus、Vuetify 3、Quasar 2 等

2.5 迁移策略

从 Vue 2 迁移到 Vue 3 的建议策略:

  1. 渐进式迁移:使用 Vue 2.7(带有部分 Vue 3 特性)作为过渡。
  2. 使用迁移构建版本:Vue 3 提供了兼容 Vue 2 API 的构建版本。
  3. 使用迁移工具:Vue 团队提供了迁移助手工具。
  4. 分阶段迁移:先更新依赖,再更新代码风格,最后优化架构。
  5. 新项目直接使用 Vue 3:新项目建议直接采用 Vue 3。

3. 环境搭建与项目结构

3.1 安装 Vue

有多种方式可以在项目中使用 Vue:

3.1.1 直接引入

最简单的方法是通过 CDN 引入 Vue:

<!-- Vue 2 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>

<!-- Vue 3 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37/dist/vue.global.js"></script>

对于生产环境,应使用压缩版本:

<!-- Vue 2 生产版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>

<!-- Vue 3 生产版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37"></script>
3.1.2 使用 npm

推荐使用 npm 管理依赖:

# Vue 2
npm install vue@2

# Vue 3
npm install vue@next
3.1.3 使用 Vue CLI

Vue CLI 是一个官方的项目脚手架工具:

# 安装 Vue CLI
npm install -g @vue/cli

# 创建一个新项目
vue create my-project

# 选择 Vue 2 或 Vue 3 作为默认预设
3.1.4 使用 Vite

Vite 是一个新一代的前端构建工具,由 Vue 团队开发:

# 使用 npm
npm init vite@latest my-vue-app -- --template vue

# 使用 yarn
yarn create vite my-vue-app --template vue

# 使用 pnpm
pnpm create vite my-vue-app -- --template vue

3.2 项目结构

典型的 Vue 项目结构(基于 Vue CLI 或 Vite 创建):

my-vue-project/
├── .vscode/                # VSCode 配置
├── node_modules/           # npm 依赖
├── public/                 # 静态资源,不经过 webpack 处理
│   ├── favicon.ico         # 网站图标
│   └── index.html          # HTML 模板
├── src/                    # 源代码
│   ├── assets/             # 资源文件(会被打包)
│   ├── components/         # 组件
│   ├── router/             # 路由配置(Vue Router)
│   ├── store/              # 状态管理(Vuex/Pinia)
│   ├── views/              # 视图/页面组件
│   ├── App.vue             # 根组件
│   └── main.js             # 入口文件
├── .browserslistrc         # 浏览器兼容性配置
├── .eslintrc.js            # ESLint 配置
├── .gitignore              # Git 忽略文件
├── babel.config.js         # Babel 配置
├── package.json            # 项目配置和依赖
├── README.md               # 项目说明文档
└── vue.config.js           # Vue CLI 配置文件

Vite 项目结构略有不同,通常没有 vue.config.js,而是 vite.config.js

3.3 开发工具

有多种工具可帮助 Vue 开发:

3.3.1 Vue DevTools

Vue DevTools 是一个浏览器扩展,可以帮助调试 Vue 应用:

  • 检查组件树
  • 查看组件状态
  • 跟踪事件
  • 分析性能
  • 时间旅行调试(Vuex/Pinia)
3.3.2 IDE 支持

Visual Studio Code 是最受欢迎的 Vue 开发 IDE,推荐以下扩展:

  • Volar (Vue 3)
  • Vetur (Vue 2)
  • ESLint
  • Prettier
  • Vue VSCode Snippets
3.3.3 其他工具
  • Vue CLI GUI:Vue CLI 的图形界面
  • Vue Devtools Standalone:独立应用版 Vue Devtools
  • Vite:快速的开发服务器和构建工具
  • Nuxt DevTools:Nuxt.js 开发工具

3.4 配置文件

3.4.1 Vue CLI 配置

vue.config.js 文件可配置 Vue CLI 项目:

module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/',
  outputDir: 'dist',
  assetsDir: 'static',
  productionSourceMap: false,
  devServer: {
    port: 8080,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
      }
    }
  },
  css: {
    loaderOptions: {
      sass: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  }
}
3.4.2 Vite 配置

vite.config.js 文件配置 Vite 项目:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  },
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false
  }
})

4. Vue 核心概念

4.1 声明式渲染

Vue.js 的核心是声明式渲染系统,允许我们声明式地将数据渲染为 DOM:

<div id="app">
  {
  { message }}
</div>
// Vue 2
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

// Vue 3
Vue.createApp({
  data() {
    return {
      message: 'Hello Vue!'
    }
  }
}).mount('#app')

声明式渲染的优势:

  • 代码简洁易读
  • 关注数据而非 DOM 操作
  • 自动更新视图
  • 可维护性更高

4.2 响应式系统

Vue 的响应式系统使得数据与视图保持同步:

4.2.1 Vue 2 响应式原理

Vue 2 使用 Object.defineProperty 实现响应式:

let data = { message: 'Hello' }
let vm = {}

Object.defineProperty(vm, 'message', {
  get() {
    return data.message
  },
  set(newValue) {
    data.message = newValue
    updateView() // 更新视图
  }
})

function updateView() {
  console.log('视图更新:', vm.message)
}

// 修改属性触发视图更新
vm.message = 'Hello Vue!'

Vue 2 响应式系统限制:

  • 无法检测到对象属性的添加或删除
  • 无法检测数组索引的变化和长度的变化
  • 需要使用 Vue.set() 或 this.$set() 添加新属性
4.2.2 Vue 3 响应式原理

Vue 3 使用 Proxy 实现响应式:

let data = { message: 'Hello' }

const handler = {
  get(target, key) {
    track(target, key) // 依赖跟踪
    return Reflect.get(target, key)
  },
  set(target, key, value) {
    const result = Reflect.set(target, key, value)
    trigger(target, key) // 触发更新
    return result
  }
}

const proxy = new Proxy(data, handler)

// 修改属性触发视图更新
proxy.message = 'Hello Vue 3!'
// 添加新属性也能触发视图更新
proxy.newProperty = 'New Value'

Vue 3 响应式系统优势:

  • 可以检测对象属性的添加和删除
  • 可以检测数组索引和长度的变化
  • 可以监听 Map, Set, WeakMap, WeakSet
  • 性能更好,消耗更少

4.3 指令系统

Vue 指令是带有 v- 前缀的特殊 HTML 属性,用于在模板中应用特殊的响应式行为:

4.3.1 常用内置指令
  • v-bind: 动态绑定属性
  • v-on: 绑定事件监听器
  • v-if: 条件性渲染元素
  • v-for: 基于数组渲染列表
  • v-model: 表单输入绑定
  • v-show: 切换元素的可见性
  • v-slot: 插槽内容分发
  • v-once: 一次性插值
  • v-pre: 跳过编译
  • v-cloak: 隐藏未编译的模板
  • v-text: 设置文本内容
  • v-html: 设置 HTML 内容
4.3.2 指令参数和修饰符

指令可以带参数和修饰符:

<!-- 参数 -->
<a v-bind:href="url">链接</a>
<button v-on:click="doSomething">点击</button>

<!-- 修饰符 -->
<form v-on:submit.prevent="onSubmit">表单</form>
<input v-model.trim="message">
4.3.3 自定义指令

可以注册自定义指令:

Vue 2:

// 全局注册
Vue.directive('focus', {
  inserted: function(el) {
    el.focus()
  }
})

// 局部注册
new Vue({
  directives: {
    focus: {
      inserted: function(el) {
        el.focus()
      }
    }
  }
})

Vue 3:

// 全局注册
const app = Vue.createApp({})
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

// 局部注册
export default {
  directives: {
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}

自定义指令钩子函数:

Vue 2 Vue 3 描述
bind beforeMount 指令绑定到元素时调用
inserted mounted 元素插入父节点时调用
update - 元素更新时调用(去除)
componentUpdated updated 组件和子组件更新时调用
unbind unmounted 指令与元素解绑时调用

4.4 生命周期

Vue 组件有一系列的生命周期钩子,允许在特定阶段执行代码:

4.4.1 Vue 2 生命周期
new Vue({
  beforeCreate() {
    // 实例初始化后,数据观测和事件配置之前
  },
  created() {
    // 实例创建完成后调用,此时数据已经可用
  },
  beforeMount() {
    // 挂载开始之前被调用,render 函数首次调用
  },
  mounted() {
    // 实例挂载到 DOM 后调用,可访问 DOM 元素
  },
  beforeUpdate() {
    // 数据更改导致虚拟 DOM 重新渲染前调用
  },
  updated() {
    // 虚拟 DOM 重新渲染后调用
  },
  activated() {
    // keep-alive 组件激活时调用
  },
  deactivated() {
    // keep-alive 组件停用时调用
  },
  beforeDestroy() {
    // 实例销毁前调用
  },
  destroyed() {
    // 实例销毁后调用
  },
  errorCaptured() {
    // 捕获子孙组件错误时调用
  }
})
4.4.2 Vue 3 生命周期
export default {
  // 选项式 API 生命周期
  beforeCreate() { /* ... */ },
  created() { /* ... */ },
  beforeMount() { /* ... */ },
  mounted() { /* ... */ },
  beforeUpdate() { /* ... */ },
  updated() { /* ... */ },
  beforeUnmount() { /* ... */ }, // 替代 beforeDestroy
  unmounted() { /* ... */ },     // 替代 destroyed
  activated() { /* ... */ },
  deactivated() { /* ... */ },
  errorCaptured() { /* ... */ },
  renderTracked() { /* ... */ },   // 新增:跟踪虚拟 DOM 重新渲染时调用
  renderTriggered() { /* ... */ }  // 新增:虚拟 DOM 重新渲染被触发时调用
}
4.4.3 Vue 3 组合式 API 生命周期钩子
import { 
  onBeforeMount, 
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
  onErrorCaptured,
  onRenderTracked,
  onRenderTriggered
} from 'vue'

export default {
  setup() {
    // 注意:没有 beforeCreate 和 created 对应的钩子
    // setup 本身在 beforeCreate 之后、created 之前执行
    
    onBeforeMount(() => { /* ... */ })
    onMounted(() => { /* ... */ })
    onBeforeUpdate(() => { /* ... */ })
    onUpdated(() => { /* ... */ })
    onBeforeUnmount(() => { /* ... */ })
    onUnmounted(() => { /* ... */ })
    onActivated(() => { /* ... */ })
    onDeactivated(() => { /* ... */ })
    onErrorCaptured(() => { /* ... */ })
    onRenderTracked(() => { /* ... */ })
    onRenderTriggered(() => { /* ... */ })
  }
}

4.5 Vue 实例属性和方法

Vue 实例提供了许多有用的属性和方法:

4.5.1 Vue 2 实例属性和方法

实例属性

  • $data: Vue 实例监视的数据对象
  • $props: 当前组件接收的 props
  • $el: Vue 实例使用的根 DOM 元素
  • $options: 当前 Vue 实例的初始化选项
  • $parent: 父实例
  • $root: 根 Vue 实例
  • $children: 当前实例的直接子组件
  • $slots: 访问插槽内容
  • $scopedSlots: 访问作用域插槽
  • $refs: 持有注册过 ref 的所有 DOM 元素和组件实例
  • $isServer: 当前 Vue 实例是否运行于服务端
  • $attrs: 包含父作用域中非 prop 的属性绑定
  • $listeners: 包含父作用域中的事件监听器

实例方法

  • $watch(): 观察 Vue 实例变化的一个表达式或计算属性函数
  • $set(): 全局 Vue.set 的别名
  • $delete(): 全局 Vue.delete 的别名
  • $on(): 监听当前实例上的自定义事件
  • $once(): 监听一个自定义事件,但只触发一次
  • $off(): 移除自定义事件监听器
  • $emit(): 触发当前实例上的事件
  • $mount(): 手动挂载一个未挂载的实例
  • $forceUpdate(): 强制 Vue 实例重新渲染
  • $nextTick(): 将回调延迟到下次 DOM 更新循环之后执行
  • $destroy(): 完全销毁一个实例
4.5.2 Vue 3 实例属性和方法

Vue 3 移除了部分实例属性和方法,如 $on, $off, $once, $children 等。其余大部分属性保持不变,但获取方式可能不同:

// 选项式 API 中访问实例属性
export default {
  mounted() {
    console.log(this.$data)
    console.log(this.$el)
  }
}

// 组合式 API 中访问实例属性
import { getCurrentInstance } from 'vue'

export default {
  setup() {
    const instance = getCurrentInstance()
    
    // 在 setup 中访问实例(仅在开发环境可用)
    console.log(instance.data)
    console.log(instance.proxy.$el) // 需要通过 proxy 访问
  }
}

5. Vue 实例详解

5.1 创建 Vue 实例

5.1.1 Vue 2 创建实例

在 Vue 2 中,通过 new Vue() 创建实例:

const vm = new Vue({
  // 选项
})
5.1.2 Vue 3 创建应用

在 Vue 3 中,通过 createApp() 创建应用实例:

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

Vue 3 的设计更加模块化,全局 API 移至应用实例:

// Vue 2
Vue.component('my-component', { /* ... */ })
Vue.directive('my-directive', { /* ... */ })
Vue.mixin({ /* ... */ })

// Vue 3
const app = createApp(App)
app.component('my-component', { /* ... */ })
app.directive('my-directive', { /* ... */ })
app.mixin({ /* ... */ })

5.2 数据与方法

5.2.1 data 选项

Vue 实例的核心是 data 选项,它是一个对象或函数:

// Vue 2 根实例:对象语法
new Vue({
  data: {
    message: 'Hello'
  }
})

// Vue 2 组件:函数语法
Vue.component('my-component', {
  data() {
    return {
      message: 'Hello'
    }
  }
})

// Vue 3:始终使用函数语法
export default {
  data() {
    return {
      message: 'Hello'
    }
  }
}

为什么组件的 data 必须是函数? 为了确保每个组件实例有独立的数据副本,防止多个组件实例共享同一个数据对象。

5.2.2 methods 选项

methods 选项用于添加方法:

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    reset() {
      this.count = 0
    }
  }
}

方法中的 this 自动绑定到 Vue 实例。

注意事项:

  • 不要使用箭头函数定义 method,因为箭头函数没有自己的 this
  • 方法可以在模板中直接调用,也可以用于事件处理

5.3 计算属性

计算属性是基于响应式依赖缓存的。只有相关依赖发生变化时才会重新计算:

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    // 基本用法
    fullName() {
      return this.firstName + ' ' + this.lastName
    },
    
    // 带 getter 和 setter
    fullNameWithSetter: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(newValue) {
        const names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  }
}

计算属性的优势:

  • 缓存基于依赖,只有依赖变化时才重新计算
  • 声明式编程,更简洁易读
  • 自动跟踪依赖关系

5.4 侦听器

侦听器用于观察和响应 Vue 实例上的数据变动:

export default {
  data() {
    return {
      question: '',
      answer: 'Questions usually contain a question mark. ;-)'
    }
  },
  watch: {
    // 基本用法
    question(newQuestion, oldQuestion) {
      if (newQuestion.includes('?')) {
        this.getAnswer()
      }
    },
    
    // 深度侦听
    someObject: {
      handler(newValue, oldValue) {
        console.log('someObject changed')
      },
      deep: true
    },
    
    // 立即执行
    otherProperty: {
      handler(newValue, oldValue) {
        console.log('otherProperty changed')
      },
      immediate: true
    }
  },
  methods: {
    getAnswer() {
      this.answer = 'Thinking...'
      setTimeout(() => {
        this.answer = 'I think you should...'
      }, 1000)
    }
  }
}

侦听器特性:

  • 可以执行异步操作
  • 可以访问新值和旧值
  • 可以深度侦听对象变化
  • 可以在创建后立即执行

5.5 组件间通信

Vue 提供了多种组件通信方式:

5.5.1 Props 向下传递数据

父组件向子组件传递数据:

// 子组件
export default {
  props: {
    title: String,
    likes: Number,
    isPublished: Boolean,
    commentIds: Array,
    author: Object,
    callback: Function,
    contactsPromise: Promise
  }
}

// 父组件
<blog-post
  title="My journey with Vue"
  :likes="42"
  :is-published="true"
></blog-post>
5.5.2 自定义事件向上传递数据

子组件向父组件传递数据:

// 子组件
export default {
  methods: {
    incrementCounter() {
      this.$emit('increment', 1)
    }
  }
}

// 父组件
<button-counter @increment="incrementTotal"></button-counter>
5.5.3 其他通信方式
  • refs:直接访问子组件
  • provide/inject:祖先组件向所有子孙组件注入数据
  • EventBus:Vue 2 中创建一个事件总线(不推荐)
  • Vuex/Pinia:专门的状态管理解决方案
  • mitt/tiny-emitter:Vue 3 中的事件库替代 EventBus

6. 模板语法与渲染

6.1 插值

6.1.1 文本插值

使用双大括号语法插入文本:

<span>Message: {
  { msg }}</span>
6.1.2 原始 HTML

使用 v-html 指令插入 HTML:

<div v-html="rawHtml"></div>

安全警告:使用 v-html 可能导致 XSS 攻击,只对可信内容使用,永不用于用户提供的内容。

6.1.3 属性绑定

使用 v-bind 指令绑定 HTML 属性:

<div v-bind:id="dynamicId"></div>
<!-- 简写 -->
<div :id="dynamicId"></div>

<!-- 布尔属性 -->
<button :disabled="isButtonDisabled">Button</button>

<!-- 多个属性绑定 -->
<div v-bind="{ id: 'container', class: 'wrapper' }"></div>

6.2 JavaScript 表达式

Vue 模板中支持完整的 JavaScript 表达式:

{
  { number + 1 }}

{
  { ok ? 'YES' : 'NO' }}

{
  { message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

限制:每个绑定只能包含单个表达式,不支持语句或控制流。

6.3 指令详解

指令是带有 v- 前缀的特殊 attribute,指令的值是单个 JavaScript 表达式。

6.3.1 参数

指令可以接收参数,在指令名之后以冒号表示:

<a v-bind:href="url">链接</a>
<button v-on:click="doSomething">点击</button>
6.3.2 动态参数

可以用方括号括起来的 JavaScript 表达式作为指令的参数:

<a v-bind:[attributeName]="url">链接</a>
<button v-on:[eventName]="doSomething">点击</button>
6.3.3 修饰符

修饰符是以点开头的特殊后缀,表示指令应该以特殊方式绑定:

<!-- 阻止默认行为 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 键盘事件 -->
<input v-on:keyup.enter="submit">

<!-- 表单修饰符 -->
<input v-model.trim="msg">

6.4 缩写

Vue 为最常用的指令提供了缩写:

<!-- 完整语法 -->
<a v-bind:href="url">链接</a>
<button v-on:click="doSomething">点击</button>

<!-- 缩写 -->
<a :href="url">链接</a>
<button @click="doSomething">点击</button>

<!-- 动态参数缩写 -->
<a :[key]="url">链接</a>
<button @[event]="doSomething">点击</button>

6.5 模板中的条件与循环

6.5.1 条件渲染

使用 v-if, v-else-if, v-else 进行条件渲染:

<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else-if="type === 'C'">C</div>
<div v-else>Not A/B/C</div>

使用 v-show 控制元素显示:

<h1 v-show="ok">Hello!</h1>

v-ifv-show 对比:

  • v-if 是"真正"的条件渲染,元素会被销毁和重建
  • v-show 只是切换 CSS display 属性
  • v-if 有更高的切换开销,v-show 有更高的初始渲染开销
  • 频繁切换使用 v-show,条件很少改变使用 v-if
6.5.2 列表渲染

使用 v-for 基于数组渲染列表:

<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {
  { index }} - {
  { item.text }}
  </li>
</ul>

v-for 也可以遍历对象属性:

<ul>
  <li v-for="(value, key, index) in object" :key="key">
    {
  { index }}. {
  { key }}: {
  { value }}
  </li>
</ul>

v-forv-if 不应在同一元素上使用,因为 v-forv-if 优先级更高。

6.6 过滤器 (Vue 2)

Vue 2 支持过滤器,可用于文本格式化:

<!-- 在双花括号中 -->
{
  { message | capitalize }}

<!-- 在 v-bind 中 -->
<div v-bind:id="rawId | formatId"></div>
filters: {
  capitalize(value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

注意:Vue 3 已移除过滤器,推荐使用方法或计算属性代替。

7. 计算属性与侦听器

7.1 计算属性详解

计算属性是基于其响应式依赖进行缓存的。

7.1.1 基本用法
export default {
  data() {
    return {
      message: 'Hello'
    }
  },
  computed: {
    // 计算属性的 getter
    reversedMessage() {
      // `this` 指向组件实例
      return this.message.split('').reverse().join('')
    }
  }
}
7.1.2 计算属性缓存 vs 方法

计算属性和方法的区别:

  • 计算属性基于依赖缓存,依赖不变时不会重新计算
  • 方法在每次重新渲染时都会执行
  • 对于计算开销大的操作,应优先使用计算属性
// 计算属性(有缓存)
computed: {
  expensiveOperation() {
    console.log('Computing expensive operation')
    return this.items.filter(item => item.important).map(...)
  }
}

// 方法(无缓存)
methods: {
  expensiveMethod() {
    console.log('Running expensive method')
    return this.items.filter(item => item.important).map(...)
  }
}
7.1.3 计算属性的 setter

计算属性默认只有 getter,但也可以提供 setter:

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        const names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1] || ''
      }
    }
  }
}

使用 setter:

// 此时会调用 setter: this.firstName 和 this.lastName 会相应更新
this.fullName = 'Jane Smith'

7.2 侦听器详解

侦听器适用于需要在数据变化时执行异步或开销较大的操作。

7.2.1 基本用法
export default {
  data() {
    return {
      question: '',
      answer: 'Questions usually contain a question mark. ;-)'
    }
  },
  watch: {
    // 每当 question 发生变化时,该函数将会执行
    question(newQuestion, oldQuestion) {
      if (newQuestion.includes('?')) {
        this.getAnswer()
      }
    }
  },
  methods: {
    getAnswer() {
      this.answer = 'Thinking...'
      axios
        .get('https://api.example.com/answer')
        .then(response => {
          this.answer = response.data.answer
        })
        .catch(error => {
          this.answer = 'Error! Could not reach the API. ' + error
        })
    }
  }
}
7.2.2 深度侦听

默认情况下,侦听器只会监听顶层属性的变化。使用 deep 选项进行深度侦听:

export default {
  data() {
    return {
      user: {
        name: 'John',
        profile: {
          age: 30,
          address: {
            city: 'New York'
          }
        }
      }
    }
  },
  watch: {
    user: {
      handler(newValue, oldValue) {
        console.log('User object changed')
      },
      deep: true
    },
    // 也可以直接监听嵌套属性
    'user.profile.age'(newValue, oldValue) {
      console.log('Age changed from', oldValue, 'to', newValue)
    }
  }
}
7.2.3 立即执行

使用 immediate 选项可以使侦听器在创建时立即执行:

watch: {
  question: {
    handler(newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.debouncedGetAnswer()
    },
    immediate: true
  }
}
7.2.4 停止侦听

在选项式 API 中,侦听器会在组件销毁时自动停止。如果需要提前停止,可以使用 $watch 返回的取消函数:

const unwatch = this.$watch('question', (newQuestion) => {
  // ...
})

// 当不再需要观察时,调用取消函数
unwatch()

在组合式 API 中,watch 函数返回停止侦听的函数:

import { ref, watch } from 'vue'

export default {
  setup() {
    const question = ref('')
    
    // 开始侦听
    const stopWatching = watch(question, (newQuestion) => {
      // ...
    })
    
    // 停止侦听
    stopWatching()
  }
}

7.3 计算属性 vs 侦听器

计算属性和侦听器的选择原则:

  • 计算属性:适用于根据现有数据派生出新数据

    • 更加声明式
    • 有缓存机制
    • 适合同步计算
  • 侦听器:适用于响应数据变化

    • 支持异步操作
    • 可以执行副作用(如修改 DOM、发送网络请求)
    • 可以访问变化前后的值
    • 可以监视多个数据源
7.3.1 何时使用计算属性
// 使用计算属性(更好)
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

// 使用侦听器(不推荐)
watch: {
  firstName(newVal) {
    this.fullName = newVal + ' ' + this.lastName
  },
  lastName(newVal) {
    this.fullName = this.firstName + ' ' + newVal
  }
}
7.3.2 何时使用侦听器
// 使用侦听器
watch: {
  searchQuery(newQuery) {
    this.isLoading = true
    this.debouncedGetResults(newQuery)
  }
},
methods: {
  // 防抖函数
  debounce(fn, delay) {
    let timeout
    return function(...args) {
      clearTimeout(timeout)
      timeout = setTimeout(() => fn.apply(this, args), delay)
    }
  },
  created() {
    this.debouncedGetResults = this.debounce(this.getResults, 300)
  },
  getResults(query) {
    axios.get('/api/search', { params: { query } })
      .then(response => {
        this.results = response.data
        this.isLoading = false
      })
  }
}

8. Class 与 Style 绑定

8.1 绑定 HTML Class

8.1.1 对象语法
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
data() {
  return {
    isActive: true,
    hasError: false
  }
}

渲染结果:

<div class="active"></div>

也可以绑定一个对象:

<div :class="classObject"></div>
data() {
  return {
    classObject: {
      active: true,
      'text-danger': false
    }
  }
}

或使用计算属性:

computed: {
  classObject() {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}
8.1.2 数组语法
<div :class="[activeClass, errorClass]"></div>
data() {
  return {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
}

渲染结果:

<div class="active text-danger"></div>

数组中使用条件:

<div :class="[isActive ? activeClass : '', errorClass]"></div>

更简洁的方式是在数组中使用对象语法:

<div :class="[{ active: isActive }, errorClass]"></div>
8.1.3 与组件结合使用

当在自定义组件上使用 class 时,这些类将被添加到组件的根元素上:

<!-- 假设这是父组件模板 -->
<my-component class="baz"></my-component>
// 组件定义
Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})

渲染结果:

<p class="foo bar baz">Hi</p>

8.2 绑定内联样式

8.2.1 对象语法
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data() {
  return {
    activeColor: 'red',
    fontSize: 30
  }
}

通常绑定一个样式对象会更清晰:

<div :style="styleObject"></div>
data() {
  return {
    styleObject: {
      color: 'red',
      fontSize: '13px'
    }
  }
}
8.2.2 数组语法

可以将多个样式对象应用到同一个元素上:

<div :style="[baseStyles, overridingStyles]"></div>
data() {
  return {
    baseStyles: {
      color: 'blue',
      fontSize: '16px'
    },
    overridingStyles: {
      fontWeight: 'bold',
      border: '1px solid black'
    }
  }
}
8.2.3 自动前缀

使用 :style 时,Vue 会自动为需要添加浏览器前缀的 CSS 属性添加适当的前缀:

<div :style="{ display: 'flex' }"></div>

渲染结果(取决于浏览器):

<div style="display: -webkit-flex; display: flex;"></div>
8.2.4 多重值

从 Vue 2.3 开始,可以为样式属性提供一个包含多个值的数组,只会渲染数组中浏览器支持的最后一个值:

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

渲染结果(在支持 flex 的浏览器中):

<div style="display: flex;"></div>

9. 条件渲染

9.1 v-if 指令

v-if 指令用于条件性地渲染一块内容,只有当表达式为 truthy 时,内容才会被渲染:

<h1 v-if="awesome">Vue is awesome!</h1>

9.2 v-else 和 v-else-if

v-elsev-else-if 必须紧跟在 v-ifv-else-if 元素之后:

<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else-if="type === 'C'">C</div>
<div v-else>Not A/B/C</div>

9.3 在 <template> 上使用 v-if

对于需要同时条件渲染多个元素,可以使用不可见的 <template> 元素包裹,最终渲染结果将不包含 <template> 元素:

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

9.4 v-show 指令

v-show 也用于条件性显示元素,但元素始终会被渲染并保留在 DOM 中,只是简单地切换 CSS 的 display 属性:

<h1 v-show="ok">Hello!</h1>

注意v-show 不支持在 <template> 元素上使用,也不支持 v-else

9.5 v-if vs v-show

  • v-if

    • "真正"的条件渲染(创建和销毁元素)
    • 有更高的切换开销
    • 在运行时条件很少改变时使用
    • 支持 <template>v-elsev-else-if
  • v-show

    • 元素始终被渲染,只是切换 CSS display 属性
    • 有更高的初始渲染开销
    • 需要频繁切换时使用
    • 不支持 <template>v-else

9.6 v-if 与 v-for 一起使用

不推荐同时使用 v-ifv-for:当它们同时存在于一个元素上时,v-for 的优先级高于 v-if

下面的代码,v-if 将分别重复运行于每个 v-for 循环中:

<!-- 不推荐 -->
<ul>
  <li v-for="user in users" v-if="user.active" :key="user.id">
    {
  { user.name }}
  </li>
</ul>

两个建议的替代方案:

  1. 使用计算属性过滤:
<!-- 推荐 -->
<ul>
  <li v-for="user in activeUsers" :key="user.id">
    {
  { user.name }}
  </li>
</ul>
computed: {
  activeUsers() {
    return this.users.filter(user => user.active)
  }
}
  1. 使用 <template>v-forv-if 移到外层:
<!-- 推荐 -->
<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.active">
      {
  { user.name }}
    </li>
  </template>
</ul>

10. 列表渲染

10.1 v-for 基础用法

10.1.1 遍历数组

使用 v-for 指令基于一个数组来渲染一个列表:

<ul>
  <li v-for="item in items" :key="item.id">
    {
  { item.text }}
  </li>
</ul>

也可以访问当前项的索引:

<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {
  { index }} - {
  { item.text }}
  </li>
</ul>
10.1.2 遍历对象

可以用 v-for 遍历对象的属性:

<ul>
  <li v-for="value in object" :key="value">
    {
  { value }}
  </li>
</ul>

可以提供第二个参数为键名:

<ul>
  <li v-for="(value, key) in object" :key="key">
    {
  { key }}: {
  { value }}
  </li>
</ul>

还可以提供第三个参数为索引:

<ul>
  <li v-for="(value, key, index) in object" :key="key">
    {
  { index }}. {
  { key }}: {
  { value }}
  </li>
</ul>
10.1.3 遍历数字范围

v-for 也可以接受整数,会重复对应次数:

<div>
  <span v-for="n in 10" :key="n">{
  { n }} </span>
</div>

注意这里的 n 是从 1 开始,而不是从 0 开始。

10.2 维护状态与 key

Vue 默认使用"就地更新"的策略,如果数据项的顺序被改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是更新每个元素。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一的 key 属性:

<div v-for="item in items" :key="item.id">
  <!-- 内容 -->
</div>

key 的最佳实践

  • 使用唯一标识符(如 id)作为 key
  • 不要使用索引作为 key(除非列表是静态的且不会重新排序)
  • 尽量不要使用随机值作为 key,这会导致性能低下

10.3 数组更新检测

10.3.1 变更方法

Vue 能够检测响应式数组的变更方法,这些方法会触发视图更新:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

示例:

// 这些方法会触发视图更新
this.items.push({ id: 4, text: 'New Item' })
this.items.pop()
10.3.2 替换数组

有些方法不会改变原始数组,而是返回一个新数组,例如:

  • filter()
  • concat()
  • slice()

当使用这些方法时,可以用新数组替换旧数组:

// 替换整个数组(Vue 能够检测)
this.items = this.items.filter(item => item.text.match(/Foo/))
10.3.3 注意事项

由于 JavaScript 的限制,Vue 不能检测到数组的以下变动:

  1. 利用索引直接设置一个数组项:

    // 这不会触发视图更新(Vue 2)
    this.items[index] = newValue
    
  2. 修改数组的长度:

    // 这不会触发视图更新(Vue 2)
    this.items.length = newLength
    

解决方案(Vue 2):

// 使用 Vue.set
Vue.set(this.items, index, newValue)
// 或
this.$set(this.items, index, newValue)

// 使用 splice
this.items.splice(index, 1, newValue)

// 修改长度
this.items.splice(newLength)

在 Vue 3 中,使用 Proxy 解决了这些问题,直接修改索引或长度会触发更新。

10.4 显示过滤/排序后的结果

显示一个数组的过滤或排序副本,而不实际改变原始数据:

<ul>
  <li v-for="n in evenNumbers" :key="n">{
  { n }}</li>
</ul>
data() {
  return {
    numbers: [1, 2, 3, 4, 5]
  }
},
computed: {
  evenNumbers() {
    return this.numbers.filter(n => n % 2 === 0)
  }
}

对于复杂的情况,也可以使用方法:

<ul>
  <li v-for="n in even(numbers)" :key="n">{
  { n }}</li>
</ul>
data() {
  return {
    numbers: [1, 2, 3, 4, 5]
  }
},
methods: {
  even(numbe

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2322991.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网渗透技术 Docker逃逸技术(提权)研究 CSMSF

目录 如何通过上传的webshell判断当前环境是否是物理环境还是Docker环境 方法一&#xff1a;检查文件系统 方法二&#xff1a;查看进程 方法三&#xff1a;检查网络配置 方法四&#xff1a;检查环境变量 方法五&#xff1a;检查挂载点 总结 2. 如果是Docker环境&#x…

生活电子常识——cmd不能使用anaconda的python环境,导致输入python打开应用商店

前言 电脑已经安装了anaconda,从自带的Anaconda Prompt (Anaconda3)中是可以识别python环境的&#xff0c;然而切换到cmd时&#xff0c;突然发现cmd中无法识别anaconda的python环境&#xff0c;竟然打开了应用商店让我安装Python&#xff0c;这当然是不对的。 解决 这是因为…

如何在linux中部署dns服务 主备dns (详细全过程)

环境centos 7.9 主DNS&#xff1a;192.168.60.131 备DNS&#xff1a;192.168.60.134 我以 chenxingyu0.com 指向 192.168.60.200为例 首先是主dns #!/bin/bash# 检查是否为 root 用户 if [ "$(id -u)" ! "0" ]; thenecho "请使用…

word写latex-Mathtype安装成功-方法

MathType安装报错 想在word写latexMathtype, 网上搜教程安装&#xff0c; 结果一直报错一直删重来&#xff0c; 一直报错一直删了重来 一直报错一直删了重来来来&#xff0c; 就这么反反复复一直不好 网上的教程都是教你不是删mathtype, 就是删office 时代变了啊&#x…

【踩坑日记】springboot 打包后实现类无法找到

试过了所有改什么目录 依赖 clean都以失败告终 最后将实现类的文件名从Impl改成impl宣布成功 记得使用idea自带的重构

deepseek(2)——deepseek 关键技术

1 Multi-Head Latent Attention (MLA) MLA的核心在于通过低秩联合压缩来减少注意力键&#xff08;keys&#xff09;和值&#xff08;values&#xff09;在推理过程中的缓存&#xff0c;从而提高推理效率&#xff1a; c t K V W D K V h t c_t^{KV} W^{DKV}h_t ctKV​WDKVht​…

Linux (Centos7)安装Mongodb4.0.28

一、官网下载安装包上传到服务器系统 官网&#xff1a;https://www.mongodb.com/try/download/community 放在/opt/software目录下&#xff1a; 二、解压至/usr/local目录下&#xff0c;并重新命名为mongodb [rootlocalhost software]# tar -zxvf mongodb-linux-x86_64-rhel7…

数据库设计-笔记4

1.操作词汇简介 insert&#xff1a;用于向表中插入新记录。 delete&#xff1a;用于从表中删除记录。 update&#xff1a;用于修改表中已有的记录。 select&#xff1a;用于从表中检索数据。 2.代码基础(增删改&#xff09; -- 修改表中的信息 -- 修改表名 alter table s…

基于python的图书管理系统设计与实现

摘要 21世纪的今天&#xff0c;随着计算机技术和网络技术的的不断推广发展和应用&#xff0c;图书馆管理方式也应该随之而更新&#xff0c;借由人力进行繁杂重复的图书管理工作已经不再可取&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0…

RAG专栏:向量数据库

一、数据库分类 键值数据库&#xff08;Key-Value&#xff09;&#xff1a;通常用于简单的数据存储&#xff0c;通过键来快速访问数据。文档数据库&#xff08;Document&#xff09;&#xff1a;用于存储文档结构的数据&#xff0c;如 JSON 格式。图数据库&#xff08;Graph&a…

【GPUStack】【dify】【RAGflow】:本地部署GPUStack并集成到dify和RAGflow

目录 Nvidia-Driver CUDA NVIDIA Container Toolkit&#xff08;新版本的docker不用安装&#xff0c;自带&#xff09; Docker 部署GPUStack Text Embeddings 部署模型库模型 测试 部署开源模型&#xff08;modelscope&#xff09; dify 集成 RAGflow集成 Nvidia-Dri…

逼用户升级Win11,微软开始给Win10限速

随着Windows10的支持时间越来越短&#xff0c;微软也加大了对Win10用户的驱赶力度。 最近&#xff0c;微软官宣了将要在今年6月份降低OneNote for Windows 10的同步速度。软件也将和Windows10在今年的10月14日一同停止支持和维护。 这将影响实时协作和多设备访问。 对OneNote…

HarmonyOs-ArkUI List组件

列表是一个复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;使得列表内容超出其范围的时候&#xff0c;就会自动变为可以滚动。列表适合用来展现同类数据类型。 List组件支持使用&#xff0c;条件渲染&#xff0c;循环渲染&#xff0c;懒加载等渲染控制方式生成子组件…

基于YOLOv8深度学习的PCB缺陷检测识别系统【python源码+GUI界面+数据集+训练代码+登录界面】

目录 一、界面全貌展示 二、前言摘要 三、GUI界面演示 &#xff08;一&#xff09;用户加载自定义模型 &#xff08;二&#xff09;单张图像检测 &#xff08;三&#xff09;检测图像文件夹 &#xff08;四&#xff09;检测视频 &#xff08;五&#xff09;摄像头检测 …

鸿蒙生态圈暗战:数字孪生三强争霸谁将主宰消费电子未来?

IDC数据显示&#xff0c;2025年Q1华为以38.7%份额领跑中国折叠屏市场&#xff0c;Pura X首月销量突破120万台。这款搭载HarmonyOS 5的旗舰&#xff0c;通过灵犀通信技术实现5G A网络下载速率提升30%&#xff0c;并在离线环境下完成厘米级导航。其爆款逻辑背后&#xff0c;是鸿蒙…

react 15-16-17-18各版本的核心区别、底层原理及演进逻辑的深度解析

一、React 15&#xff08;2016&#xff09; 核心架构&#xff1a;Stack Reconciler&#xff08;栈协调器&#xff09; 工作原理&#xff1a; 同步递归渲染&#xff1a;采用深度优先遍历方式递归处理 Virtual DOM&#xff0c;形成不可中断的调用栈渲染流程&#xff1a;1. 触发 …

CMS迁移中SEO优化整合步骤详解

内容概要 在CMS迁移过程中&#xff0c;系统化的规划与执行是保障SEO排名稳定性的核心。首先需明确迁移流程的关键阶段&#xff0c;包括数据备份、URL适配、元数据同步及安全配置等环节。其中&#xff0c;数据备份不仅需覆盖原始数据库与静态资源&#xff0c;还需验证备份文件的…

数据结构初阶-二叉树链式

目录 1.概念与结构 2.二叉数链式的实现 2.1遍历规则 2.2申请内存空间 2.3手动构建一棵二叉树 2.4二叉树结点的个数 2.5二叉树叶子结点的个数 2.6二叉树第K层结点个数 2.7二叉树的高度 2.8二叉树中查找值为x的结点 2.9二叉树的销毁 3.层序遍历 3.1概念 3.2层序遍历…

Springboot 集成 Flowable 6.8.0

1. 创建 Spring Boot 项目 通过 Spring Initializr&#xff08;https://start.spring.io/ &#xff09;创建一个基础的 Spring Boot 项目&#xff0c;添加以下依赖&#xff1a; Spring WebSpring Data JPAMySQL DriverLombok&#xff08;可选&#xff0c;用于简化代码&#x…

协作机械臂需要加安全墙吗? 安全墙 光栅 干涉区

安全墙是什么 文章目录 安全墙是什么简介1. 物理安全墙1.1 定义&#xff1a;1.2 作用机制&#xff1a;1.3 应用场景&#xff1a; 2. 虚拟安全墙2.2 定义&#xff1a;2.3 作用机制&#xff1a;2.3 应用场景&#xff1a; 3. 安全毛毯3.1 工作原理&#xff1a;3.2 特点3.3 应用场景…