欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > Vue.js 和 Vue 3 全面详解指南

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

2025/3/31 12:33:49 来源:https://blog.csdn.net/weixin_51040479/article/details/146528162  浏览:    关键词:Vue.js 和 Vue 3 全面详解指南

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 2Vue 3
响应式系统Object.definePropertyProxy
代码组织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 = newValueupdateView() // 更新视图}
})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 2Vue 3描述
bindbeforeMount指令绑定到元素时调用
insertedmounted元素插入父节点时调用
update-元素更新时调用(去除)
componentUpdatedupdated组件和子组件更新时调用
unbindunmounted指令与元素解绑时调用

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() { /* ... */ }, // 替代 beforeDestroyunmounted() { /* ... */ },     // 替代 destroyedactivated() { /* ... */ },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 和 setterfullNameWithSetter: {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-posttitle="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: {// 计算属性的 getterreversedMessage() {// `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: {// getterget() {return this.firstName + ' ' + this.lastName},// setterset(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 = truethis.debouncedGetResults(newQuery)}
},
methods: {// 防抖函数debounce(fn, delay) {let timeoutreturn 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.datathis.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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词