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 的特点
-
渐进式框架:可以逐步将 Vue 集成到项目中,无需一次全部采用。
-
响应式系统:Vue 提供了响应式且组件化的视图组件,当数据变化时,视图会自动更新。
-
虚拟 DOM:Vue 使用虚拟 DOM(Virtual DOM)技术提高渲染性能。
-
组件化开发:鼓励将应用拆分为独立可复用的组件,构建出大型应用。
-
轻量级:Vue 的体积小巧,压缩后仅约 20KB(Vue 2)或 10KB(Vue 3)。
-
易学易用:相较于其他前端框架,Vue 的学习曲线更平缓,API 设计简单直观。
-
丰富的工具链:Vue 提供了完整的开发工具链,如 Vue CLI、Vite、DevTools 等。
1.3 Vue.js 应用场景
Vue 可适用于多种场景:
-
单页应用(SPA):使用 Vue Router 构建单页应用,避免页面刷新提升用户体验。
-
多页应用:将 Vue 组件集成到传统的多页面应用中。
-
移动端应用:结合 Cordova、Capacitor 或 NativeScript 构建移动应用。
-
桌面应用:结合 Electron 构建桌面应用。
-
服务端渲染:使用 Nuxt.js 或手动配置 Vue SSR。
-
静态站点生成:使用 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 在性能上有显著提升:
-
更小的包体积:Vue 3 核心库体积比 Vue 2 减小了约 41%,最小化和压缩后仅约 10KB。
-
更快的初始渲染:Vue 3 初始渲染速度比 Vue 2 快约 55%。
-
更高效的更新:由于优化的虚拟 DOM 和编译时提示,Vue 3 的更新性能比 Vue 2 快约 133%。
-
内存占用更低: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 的建议策略:
- 渐进式迁移:使用 Vue 2.7(带有部分 Vue 3 特性)作为过渡。
- 使用迁移构建版本:Vue 3 提供了兼容 Vue 2 API 的构建版本。
- 使用迁移工具:Vue 团队提供了迁移助手工具。
- 分阶段迁移:先更新依赖,再更新代码风格,最后优化架构。
- 新项目直接使用 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 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() { /* ... */ }, // 替代 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-if 与 v-show 对比:
v-if是"真正"的条件渲染,元素会被销毁和重建v-show只是切换 CSSdisplay属性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-for 与 v-if 不应在同一元素上使用,因为 v-for 比 v-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-else 和 v-else-if 必须紧跟在 v-if 或 v-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-else和v-else-if
-
v-show:
- 元素始终被渲染,只是切换 CSS
display属性 - 有更高的初始渲染开销
- 需要频繁切换时使用
- 不支持
<template>和v-else
- 元素始终被渲染,只是切换 CSS
9.6 v-if 与 v-for 一起使用
不推荐同时使用 v-if 和 v-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>
两个建议的替代方案:
- 使用计算属性过滤:
<!-- 推荐 -->
<ul><li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
</ul>
computed: {activeUsers() {return this.users.filter(user => user.active)}
}
- 使用
<template>和v-for把v-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 不能检测到数组的以下变动:
-
利用索引直接设置一个数组项:
// 这不会触发视图更新(Vue 2) this.items[index] = newValue -
修改数组的长度:
// 这不会触发视图更新(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相关文章:
Vue.js 和 Vue 3 全面详解指南
1. Vue.js 基础介绍 1.1 什么是 Vue.js Vue.js(简称 Vue)是一个用于构建用户界面的渐进式 JavaScript 框架。与其他框架不同,Vue 被设计为可以逐步采用。Vue 的核心库只关注视图层,易于上手,便于与其他库或既有项目整合。 Vue 由尤雨溪(Evan You)在 2014 年创建。尤雨…...
OpenRAND可重复的随机数生成库
OpenRAND 是一个 C++ 库,旨在通过提供强大且可复制的随机数生成解决方案来促进可重复的科学研究。它是一个简单的仅头文件库,性能可移植,统计稳健,并且易于集成到任何 HPC 计算项目中。 特征 跨平台支持:OpenRAND 旨在跨各种平台无缝工作,包括 CPU 和 GPU。其仅标题库设计…...
内网渗透技术 Docker逃逸技术(提权)研究 CSMSF
目录 如何通过上传的webshell判断当前环境是否是物理环境还是Docker环境 方法一:检查文件系统 方法二:查看进程 方法三:检查网络配置 方法四:检查环境变量 方法五:检查挂载点 总结 2. 如果是Docker环境&#x…...
生活电子常识——cmd不能使用anaconda的python环境,导致输入python打开应用商店
前言 电脑已经安装了anaconda,从自带的Anaconda Prompt (Anaconda3)中是可以识别python环境的,然而切换到cmd时,突然发现cmd中无法识别anaconda的python环境,竟然打开了应用商店让我安装Python,这当然是不对的。 解决 这是因为…...
如何在linux中部署dns服务 主备dns (详细全过程)
环境centos 7.9 主DNS:192.168.60.131 备DNS:192.168.60.134 我以 chenxingyu0.com 指向 192.168.60.200为例 首先是主dns #!/bin/bash# 检查是否为 root 用户 if [ "$(id -u)" ! "0" ]; thenecho "请使用…...
Oracle到达梦数据库迁移:技术要点与实践分享
一、达梦数据库简介 达梦数据库(DM,Dameng Database)是国内自主研发的具有自主知识产权的大型通用数据库管理系统,具备以下显著特点: 1.高性能:高效的存储与计算分离架构:达梦数据库采用先进的存储与计算分离架构,能够根据业务需求灵活分配存储和计算资源,大大提高了…...
word写latex-Mathtype安装成功-方法
MathType安装报错 想在word写latexMathtype, 网上搜教程安装, 结果一直报错一直删重来, 一直报错一直删了重来 一直报错一直删了重来来来, 就这么反反复复一直不好 网上的教程都是教你不是删mathtype, 就是删office 时代变了啊&#x…...
【踩坑日记】springboot 打包后实现类无法找到
试过了所有改什么目录 依赖 clean都以失败告终 最后将实现类的文件名从Impl改成impl宣布成功 记得使用idea自带的重构...
deepseek(2)——deepseek 关键技术
1 Multi-Head Latent Attention (MLA) MLA的核心在于通过低秩联合压缩来减少注意力键(keys)和值(values)在推理过程中的缓存,从而提高推理效率: c t K V W D K V h t c_t^{KV} W^{DKV}h_t ctKVWDKVht…...
Linux (Centos7)安装Mongodb4.0.28
一、官网下载安装包上传到服务器系统 官网:https://www.mongodb.com/try/download/community 放在/opt/software目录下: 二、解压至/usr/local目录下,并重新命名为mongodb [rootlocalhost software]# tar -zxvf mongodb-linux-x86_64-rhel7…...
数据库设计-笔记4
1.操作词汇简介 insert:用于向表中插入新记录。 delete:用于从表中删除记录。 update:用于修改表中已有的记录。 select:用于从表中检索数据。 2.代码基础(增删改) -- 修改表中的信息 -- 修改表名 alter table s…...
基于python的图书管理系统设计与实现
摘要 21世纪的今天,随着计算机技术和网络技术的的不断推广发展和应用,图书馆管理方式也应该随之而更新,借由人力进行繁杂重复的图书管理工作已经不再可取,人们对于信息科学化的认识,已由低层次向高层次发展࿰…...
10-- 网络攻击防御原理全景解析 | 从单包攻防到DDoS军团作战(包你看一遍全记住)
🛡️ 网络攻击防御原理全景解析 | 从单包攻防到DDoS军团作战 如果你也对网络工程师的内容感兴趣的话,欢迎看我的最新文章9–BGP路由黑洞(超万字大解析):网络世界的“百慕大三角“逃生指南(BGP路由配置实验含路由黑洞,…...
RAG专栏:向量数据库
一、数据库分类 键值数据库(Key-Value):通常用于简单的数据存储,通过键来快速访问数据。文档数据库(Document):用于存储文档结构的数据,如 JSON 格式。图数据库(Graph&a…...
【GPUStack】【dify】【RAGflow】:本地部署GPUStack并集成到dify和RAGflow
目录 Nvidia-Driver CUDA NVIDIA Container Toolkit(新版本的docker不用安装,自带) Docker 部署GPUStack Text Embeddings 部署模型库模型 测试 部署开源模型(modelscope) dify 集成 RAGflow集成 Nvidia-Dri…...
第12章:优化并发_《C++性能优化指南》notes
优化并发 一、并发基础与优化核心知识点二、关键代码示例与测试三、关键优化策略总结四、性能测试方法论多选题设计题答案与详解多选题答案: 设计题答案示例 一、并发基础与优化核心知识点 线程 vs 异步任务 核心区别:std::thread直接管理线程…...
逼用户升级Win11,微软开始给Win10限速
随着Windows10的支持时间越来越短,微软也加大了对Win10用户的驱赶力度。 最近,微软官宣了将要在今年6月份降低OneNote for Windows 10的同步速度。软件也将和Windows10在今年的10月14日一同停止支持和维护。 这将影响实时协作和多设备访问。 对OneNote…...
HarmonyOs-ArkUI List组件
列表是一个复杂的容器,当列表项达到一定数量,使得列表内容超出其范围的时候,就会自动变为可以滚动。列表适合用来展现同类数据类型。 List组件支持使用,条件渲染,循环渲染,懒加载等渲染控制方式生成子组件…...
基于YOLOv8深度学习的PCB缺陷检测识别系统【python源码+GUI界面+数据集+训练代码+登录界面】
目录 一、界面全貌展示 二、前言摘要 三、GUI界面演示 (一)用户加载自定义模型 (二)单张图像检测 (三)检测图像文件夹 (四)检测视频 (五)摄像头检测 …...
鸿蒙生态圈暗战:数字孪生三强争霸谁将主宰消费电子未来?
IDC数据显示,2025年Q1华为以38.7%份额领跑中国折叠屏市场,Pura X首月销量突破120万台。这款搭载HarmonyOS 5的旗舰,通过灵犀通信技术实现5G A网络下载速率提升30%,并在离线环境下完成厘米级导航。其爆款逻辑背后,是鸿蒙…...
react 15-16-17-18各版本的核心区别、底层原理及演进逻辑的深度解析
一、React 15(2016) 核心架构:Stack Reconciler(栈协调器) 工作原理: 同步递归渲染:采用深度优先遍历方式递归处理 Virtual DOM,形成不可中断的调用栈渲染流程:1. 触发 …...
计算机网络 --应用层
计算机网络 --应用层 一、应用层概述 1. 功能 应用层为应用程序通信提供直接服务,这种服务是用户能够直接感知到的数据通信服务。核心功能包括: 文件传输:实现不同设备间文件的传输操作。访问管理:对用户访问资源等进行管理。电…...
CMS迁移中SEO优化整合步骤详解
内容概要 在CMS迁移过程中,系统化的规划与执行是保障SEO排名稳定性的核心。首先需明确迁移流程的关键阶段,包括数据备份、URL适配、元数据同步及安全配置等环节。其中,数据备份不仅需覆盖原始数据库与静态资源,还需验证备份文件的…...
数据结构初阶-二叉树链式
目录 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(https://start.spring.io/ )创建一个基础的 Spring Boot 项目,添加以下依赖: Spring WebSpring Data JPAMySQL DriverLombok(可选,用于简化代码&#x…...
协作机械臂需要加安全墙吗? 安全墙 光栅 干涉区
安全墙是什么 文章目录 安全墙是什么简介1. 物理安全墙1.1 定义:1.2 作用机制:1.3 应用场景: 2. 虚拟安全墙2.2 定义:2.3 作用机制:2.3 应用场景: 3. 安全毛毯3.1 工作原理:3.2 特点3.3 应用场景…...
HTML5 SVG:图形绘制的现代标准
HTML5 SVG:图形绘制的现代标准 引言 随着互联网技术的发展,网页的交互性和美观性日益受到重视。HTML5 SVG作为一种强大的图形绘制技术,在网页设计中发挥着重要作用。本文将深入探讨HTML5 SVG的原理、应用场景以及如何在实际项目中运用。 一、HTML5 SVG简介 1.1 什么是SV…...
洛谷题单1-B2025 输出字符菱形-python-流程图重构
题目描述 用 * 构造一个对角线长 5 5 5 个字符,倾斜放置的菱形。 输入格式 没有输入要求。 输出格式 如样例所示。用 * 构成的菱形。 输入输出样例 #1 输入 #1 输出 #1 **** *********方式-前半区推导,后半区逆序 代码 class Solution:static…...
springboot+mybatisplus
1.什么是springboot? Spring Boot是一个用于快速构建Spring应用程序的框架。它旨在帮助开发人员快速搭建Spring框架,减少配置和繁琐的工作。Spring Boot继承了原有Spring框架的优秀基因,使Spring在使用中更加方便快捷。 在Spring Boot中集成ActiveMQ,需要导入相应的starter…...
《TypeScript 面试八股:高频考点与核心知识点详解》
“你好啊!能把那天没唱的歌再唱给我听吗? ” 前言 因为主包还是主要学习js,ts浅浅的学习了一下,在简历中我也只会写了解,所以我写一些比较基础的八股,如果是想要更深入的八股的话还是建议找别人的。 Ts基…...
