Vue2 基础用法
Vue2 基础用法
目录
- 创建Vue实例
- 插值表达式
- 响应式原理
- Vue指令
- 条件渲染 v-show 和 v-if
- v-else 和 v-else-if
- 事件处理 v-on
- v-on调用传参
- 双向绑定 v-bind
- 列表渲染 v-for
- v-for的key属性
- v-model双向绑定
- 指令修饰符
- v-bind操作class
- v-model应用于其他表单元素
- 计算属性
- 计算属性的完整写法
- watch监视器
- watch完整写法
- 生命周期
- 初始化渲染和获取焦点
- mounted获取焦点
1. 创建Vue实例
Vue.js的核心是创建一个Vue实例来管理应用程序的数据和行为。
基本语法
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue实例</title><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>
<body><div id="app"><h1>{{ message }}</h1><p>{{ user.name }}</p></div><script>// 创建Vue实例const vm = new Vue({el: '#app', // 挂载点data: { // 数据对象message: 'Hello Vue.js!',user: {name: '张三',age: 25}},methods: { // 方法对象greet() {alert('欢迎使用Vue.js!');}}});</script>
</body>
</html>
Vue实例的选项
new Vue({// 1. 挂载点 - 指定Vue实例管理的DOM元素el: '#app',// 2. 数据 - 定义响应式数据data: {message: 'Hello World',count: 0},// 3. 方法 - 定义事件处理函数methods: {increment() {this.count++;}},// 4. 计算属性computed: {doubleCount() {return this.count * 2;}},// 5. 侦听器watch: {count(newVal, oldVal) {console.log(`count从${oldVal}变为${newVal}`);}}
});
2. 插值表达式
插值表达式(双花括号语法)是Vue.js中最基本的数据绑定方式。
基本用法
<div id="app"><!-- 文本插值 --><p>{{ message }}</p><!-- 表达式计算 --><p>{{ number + 1 }}</p><!-- 函数调用 --><p>{{ message.split('').reverse().join('') }}</p><!-- 三元运算符 --><p>{{ ok ? 'YES' : 'NO' }}</p><!-- 对象属性访问 --><p>{{ user.name }} - {{ user.age }}岁</p>
</div><script>
new Vue({el: '#app',data: {message: 'Hello Vue',number: 10,ok: true,user: {name: '李四',age: 30}}
});
</script>
插值表达式注意事项
<div id="app"><!-- ✅ 正确:简单表达式 --><p>{{ message.toUpperCase() }}</p><!-- ❌ 错误:语句 --><p>{{ var a = 1 }}</p><!-- ❌ 错误:流程控制 --><p>{{ if (ok) { return message } }}</p><!-- ✅ 正确:使用三元运算符 --><p>{{ ok ? message : 'Not OK' }}</p>
</div>
3. 响应式原理
Vue.js的响应式系统是其核心特性,能够自动追踪依赖并在数据变化时更新视图。
响应式数据
<div id="app"><h2>响应式数据演示</h2><p>计数器: {{ count }}</p><p>用户名: {{ user.name }}</p><p>数组: {{ items.join(', ') }}</p><button @click="updateData">更新数据</button>
</div><script>
new Vue({el: '#app',data: {count: 0,user: {name: '王五'},items: ['苹果', '香蕉', '橙子']},methods: {updateData() {// 这些操作都会触发视图更新this.count++;this.user.name = '赵六';this.items.push('葡萄');}}
});
</script>
响应式原理说明
Vue.js通过以下机制实现响应式:
- 数据劫持: 使用
Object.defineProperty()
重新定义data中的属性 - 依赖收集: 在getter中收集依赖
- 派发更新: 在setter中通知所有依赖进行更新
// 简化版响应式原理
function defineReactive(obj, key, val) {Object.defineProperty(obj, key, {get() {console.log('获取值:', val);return val;},set(newVal) {if (newVal !== val) {console.log('设置值:', newVal);val = newVal;// 触发更新updateView();}}});
}
4. Vue指令
Vue指令是带有v-
前缀的特殊属性,用于在模板中声明式地将数据绑定到DOM。
常用指令概览
<div id="app"><!-- v-text: 更新元素的文本内容 --><p v-text="message"></p><!-- v-html: 更新元素的innerHTML --><div v-html="htmlContent"></div><!-- v-show: 条件性显示元素 --><p v-show="isVisible">这段文字是否显示</p><!-- v-if: 条件性渲染元素 --><p v-if="isShow">条件渲染</p><!-- v-for: 基于数据多次渲染元素 --><ul><li v-for="item in list" :key="item.id">{{ item.name }}</li></ul><!-- v-on: 监听DOM事件 --><button v-on:click="handleClick">点击我</button><!-- v-bind: 动态绑定属性 --><img v-bind:src="imageSrc" v-bind:alt="imageAlt"><!-- v-model: 表单元素双向绑定 --><input v-model="inputValue">
</div><script>
new Vue({el: '#app',data: {message: 'Hello Vue',htmlContent: '<strong>粗体文字</strong>',isVisible: true,isShow: true,list: [{ id: 1, name: '项目1' },{ id: 2, name: '项目2' }],imageSrc: 'https://vuejs.org/images/logo.png',imageAlt: 'Vue Logo',inputValue: ''},methods: {handleClick() {console.log('按钮被点击了!');}}
});
</script>
5. 条件渲染
Vue提供了v-show
和v-if
两种条件渲染方式。
v-show vs v-if
<div id="app"><h2>条件渲染演示</h2><!-- v-show: 通过CSS display属性控制显示隐藏 --><p v-show="showWithVShow">v-show控制的内容(切换display属性)</p><!-- v-if: 条件性地渲染元素 --><p v-if="showWithVIf">v-if控制的内容(条件性渲染)</p><!-- 性能对比 --><div><button @click="toggleShow">切换显示状态</button><button @click="toggleIf">切换渲染状态</button></div><!-- 复杂条件 --><div v-if="user.isVip && user.score > 100">VIP用户且积分大于100</div><!-- 模板包装 --><template v-if="showGroup"><h3>标题</h3><p>内容1</p><p>内容2</p></template>
</div><script>
new Vue({el: '#app',data: {showWithVShow: true,showWithVIf: true,showGroup: true,user: {isVip: true,score: 150}},methods: {toggleShow() {this.showWithVShow = !this.showWithVShow;},toggleIf() {this.showWithVIf = !this.showWithVIf;}}
});
</script>
v-show和v-if的区别
特性 | v-show | v-if |
---|---|---|
渲染方式 | 始终渲染,切换CSS display | 条件性渲染 |
切换开销 | 低 | 高 |
初始开销 | 高 | 低 |
适用场景 | 频繁切换 | 条件很少改变 |
6. v-else和v-else-if
配合v-if
使用的条件渲染指令。
<div id="app"><!-- 基本 v-else --><div v-if="isLoggedIn"><h3>欢迎回来!</h3><p>用户:{{ username }}</p></div><div v-else><h3>请登录</h3><button @click="login">登录</button></div><!-- v-else-if 链式条件 --><div v-if="score >= 90">优秀</div><div v-else-if="score >= 80">良好</div><div v-else-if="score >= 60">及格</div><div v-else>不及格</div><!-- 复杂条件判断 --><div v-if="weather === 'sunny'"><p>今天阳光明媚</p></div><div v-else-if="weather === 'rainy'"><p>今天下雨了</p></div><div v-else-if="weather === 'cloudy'"><p>今天多云</p></div><div v-else><p>天气未知</p></div><!-- 使用template避免额外包装元素 --><template v-if="userType === 'admin'"><h4>管理员面板</h4><p>用户管理</p><p>系统设置</p></template><template v-else-if="userType === 'editor'"><h4>编辑器面板</h4><p>内容管理</p></template><template v-else><h4>普通用户</h4><p>个人中心</p></template><!-- 控制按钮 --><div><button @click="toggleLogin">切换登录状态</button><button @click="changeScore">改变分数</button><button @click="changeWeather">改变天气</button><button @click="changeUserType">切换用户类型</button></div>
</div><script>
new Vue({el: '#app',data: {isLoggedIn: false,username: '张三',score: 85,weather: 'sunny',userType: 'admin'},methods: {login() {this.isLoggedIn = true;},toggleLogin() {this.isLoggedIn = !this.isLoggedIn;},changeScore() {this.score = Math.floor(Math.random() * 100);},changeWeather() {const weathers = ['sunny', 'rainy', 'cloudy', 'unknown'];this.weather = weathers[Math.floor(Math.random() * weathers.length)];},changeUserType() {const types = ['admin', 'editor', 'user'];const currentIndex = types.indexOf(this.userType);this.userType = types[(currentIndex + 1) % types.length];}}
});
</script>
7. 事件处理
使用v-on
指令监听DOM事件。
基本事件处理
<div id="app"><h2>事件处理演示</h2><!-- 基本点击事件 --><button v-on:click="sayHello">点击问候</button><!-- 简写语法 @ --><button @click="count++">计数: {{ count }}</button><!-- 方法调用 --><button @click="increment">增加</button><button @click="decrement">减少</button><!-- 内联处理器 --><button @click="message = '按钮被点击了'">{{ message }}</button><!-- 多个事件 --><button @click="handleClick" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">悬停效果按钮</button><!-- 表单事件 --><input @input="handleInput" @focus="handleFocus" @blur="handleBlur" :value="inputText"><p>输入的内容: {{ inputText }}</p><!-- 键盘事件 --><input @keyup.enter="submitForm" @keyup.esc="clearInput" placeholder="按Enter提交,Esc清空"><!-- 鼠标事件 --><div @click="handleDivClick" style="padding: 20px; background: #f0f0f0; margin: 10px;"><button @click="handleButtonClick">按钮</button><p>点击区域测试事件冒泡</p></div>
</div><script>
new Vue({el: '#app',data: {count: 0,message: '点击按钮改变我',inputText: ''},methods: {sayHello() {alert('Hello Vue.js!');},increment() {this.count++;},decrement() {this.count--;},handleClick() {console.log('按钮被点击');},handleMouseEnter() {console.log('鼠标进入');},handleMouseLeave() {console.log('鼠标离开');},handleInput(event) {this.inputText = event.target.value;},handleFocus() {console.log('输入框获得焦点');},handleBlur() {console.log('输入框失去焦点');},submitForm() {console.log('表单提交:', this.inputText);},clearInput() {this.inputText = '';},handleDivClick() {console.log('DIV被点击');},handleButtonClick() {console.log('按钮被点击');}}
});
</script>
8. v-on调用传参
在事件处理函数中传递参数。
<div id="app"><h2>事件传参演示</h2><!-- 无参数调用 --><button @click="sayHello">无参数</button><!-- 传递静态参数 --><button @click="greet('张三')">问候张三</button><button @click="greet('李四')">问候李四</button><!-- 传递动态参数 --><button @click="greet(currentUser)">问候当前用户</button><!-- 传递多个参数 --><button @click="calculate(5, 3, '+')">5 + 3</button><button @click="calculate(10, 4, '-')">10 - 4</button><!-- 传递事件对象 $event --><button @click="handleWithEvent('参数', $event)">获取事件对象</button><!-- 在列表中传参 --><ul><li v-for="item in items" :key="item.id">{{ item.name }}<button @click="deleteItem(item.id)">删除</button><button @click="updateItem(item.id, '新名称')">更新</button></li></ul><!-- 传递索引 --><div><button v-for="(color, index) in colors" :key="index"@click="selectColor(color, index)":style="{ backgroundColor: color, margin: '5px', padding: '10px' }">{{ color }}</button></div><p>选中的颜色: {{ selectedColor }} (索引: {{ selectedIndex }})</p><p>计算结果: {{ result }}</p>
</div><script>
new Vue({el: '#app',data: {currentUser: '王五',result: '',selectedColor: '',selectedIndex: -1,items: [{ id: 1, name: '项目一' },{ id: 2, name: '项目二' },{ id: 3, name: '项目三' }],colors: ['red', 'green', 'blue', 'yellow', 'purple']},methods: {sayHello() {alert('Hello!');},greet(name) {alert(`你好, ${name}!`);},calculate(a, b, operator) {let result;switch (operator) {case '+':result = a + b;break;case '-':result = a - b;break;case '*':result = a * b;break;case '/':result = a / b;break;}this.result = `${a} ${operator} ${b} = ${result}`;},handleWithEvent(param, event) {console.log('参数:', param);console.log('事件对象:', event);console.log('按钮文本:', event.target.textContent);},deleteItem(id) {this.items = this.items.filter(item => item.id !== id);console.log(`删除了ID为${id}的项目`);},updateItem(id, newName) {const item = this.items.find(item => item.id === id);if (item) {item.name = newName;}},selectColor(color, index) {this.selectedColor = color;this.selectedIndex = index;}}
});
</script>
9. 双向绑定
Vue的双向绑定主要通过v-model
实现。
v-bind单向绑定
<div id="app"><h2>v-bind单向绑定演示</h2><!-- 绑定属性 --><img v-bind:src="imageSrc" v-bind:alt="imageAlt" width="100"><!-- 简写语法 --><a :href="linkUrl" :title="linkTitle">{{ linkText }}</a><!-- 绑定class --><div :class="cssClass">动态CSS类</div><!-- 绑定style --><p :style="textStyle">动态样式文本</p><!-- 绑定布尔属性 --><button :disabled="isDisabled">{{ buttonText }}</button><!-- 表单元素单向绑定 --><input :value="inputValue" @input="updateInput" placeholder="单向绑定"><p>输入值: {{ inputValue }}</p><!-- 控制按钮 --><div><button @click="toggleDisabled">切换按钮状态</button><button @click="changeStyle">改变样式</button><button @click="changeImage">切换图片</button></div>
</div><script>
new Vue({el: '#app',data: {imageSrc: 'https://via.placeholder.com/100x100/ff0000/ffffff?text=Red',imageAlt: '红色占位图',linkUrl: 'https://vuejs.org',linkTitle: 'Vue.js官网',linkText: '访问Vue.js',cssClass: 'highlight',textStyle: {color: 'blue',fontSize: '18px',fontWeight: 'bold'},isDisabled: false,buttonText: '可用按钮',inputValue: ''},methods: {updateInput(event) {this.inputValue = event.target.value;},toggleDisabled() {this.isDisabled = !this.isDisabled;this.buttonText = this.isDisabled ? '禁用按钮' : '可用按钮';},changeStyle() {this.textStyle.color = this.textStyle.color === 'blue' ? 'red' : 'blue';this.textStyle.fontSize = this.textStyle.fontSize === '18px' ? '24px' : '18px';},changeImage() {const colors = ['ff0000', '00ff00', '0000ff', 'ffff00'];const color = colors[Math.floor(Math.random() * colors.length)];this.imageSrc = `https://via.placeholder.com/100x100/${color}/ffffff?text=${color}`;}}
});
</script><style>
.highlight {background-color: yellow;padding: 10px;border: 2px solid orange;border-radius: 5px;
}
</style>
10. 列表渲染
使用v-for
指令进行列表渲染。
基本列表渲染
<div id="app"><h2>v-for列表渲染演示</h2><!-- 渲染数组 --><h3>水果列表</h3><ul><li v-for="fruit in fruits">{{ fruit }}</li></ul><!-- 带索引的数组渲染 --><h3>带索引的列表</h3><ol><li v-for="(fruit, index) in fruits">{{ index + 1 }}. {{ fruit }}</li></ol><!-- 渲染对象数组 --><h3>用户列表</h3><div v-for="user in users" :key="user.id" class="user-card"><h4>{{ user.name }}</h4><p>年龄: {{ user.age }}</p><p>城市: {{ user.city }}</p></div><!-- 渲染对象 --><h3>用户信息</h3><ul><li v-for="(value, key) in userInfo">{{ key }}: {{ value }}</li></ul><!-- 带索引的对象渲染 --><h3>带索引的对象渲染</h3><ul><li v-for="(value, key, index) in userInfo">{{ index }}. {{ key }}: {{ value }}</li></ul><!-- 渲染数字 --><h3>数字列表</h3><span v-for="n in 10" :key="n">{{ n }} </span><!-- 嵌套v-for --><h3>嵌套列表</h3><div v-for="category in categories" :key="category.name"><h4>{{ category.name }}</h4><ul><li v-for="item in category.items" :key="item">{{ item }}</li></ul></div><!-- template包装 --><h3>使用template包装</h3><template v-for="product in products" :key="product.id"><dt>{{ product.name }}</dt><dd>价格: ¥{{ product.price }}</dd></template>
</div><script>
new Vue({el: '#app',data: {fruits: ['苹果', '香蕉', '橙子', '葡萄'],users: [{ id: 1, name: '张三', age: 25, city: '北京' },{ id: 2, name: '李四', age: 30, city: '上海' },{ id: 3, name: '王五', age: 28, city: '广州' }],userInfo: {name: '赵六',age: 32,email: 'zhaoliu@example.com',phone: '13800138000'},categories: [{name: '水果',items: ['苹果', '香蕉', '橙子']},{name: '蔬菜',items: ['白菜', '萝卜', '土豆']}],products: [{ id: 1, name: '笔记本电脑', price: 5999 },{ id: 2, name: '智能手机', price: 2999 },{ id: 3, name: '平板电脑', price: 3999 }]}
});
</script><style>
.user-card {border: 1px solid #ddd;padding: 15px;margin: 10px 0;border-radius: 5px;background-color: #f9f9f9;
}
</style>
11. v-for的key属性
key属性是Vue进行列表更新优化的重要机制。
key属性的作用与原理
在Vue的列表渲染(v - for
)中,key
属性是一个非常关键的存在,主要有以下重要作用:
(1)高效更新虚拟DOM(diff算法优化)
Vue在更新列表时,会使用虚拟DOM的diff算法来比较新旧节点。当列表数据发生变化(比如新增、删除、重新排序等)时,key
能帮助Vue准确识别哪些节点是新增的、哪些是被删除的、哪些是需要移动位置的,从而避免对整个列表进行无意义的重新渲染,大大提升渲染效率。
举个简单例子,假设原来列表是[A, B, C]
,key
分别对应为1、2、3
,后来变成[B, A, C]
。如果没有key
,Vue可能会把这三个节点都重新创建、替换;但有了key
,Vue能快速识别出A
和B
只是位置交换,只需要移动它们的DOM位置,而不用重新销毁和创建,减少了操作开销。
(2)维持组件状态与避免错误
当列表中的项对应着Vue组件时,key
能确保组件的状态(比如组件内部的data
、生命周期状态等)被正确维护。如果没有合适的key
,Vue可能会错误地复用组件实例,导致组件状态混乱,出现一些难以排查的显示或交互问题。
比如列表里的每个项是一个包含输入框的自定义组件,用户在输入框输入了内容后,如果列表数据变化,没有正确key
的话,输入框的内容可能会“乱跑”,因为Vue错误复用了组件,把原本对应其他数据项的组件实例错误匹配,导致显示异常。
key属性的使用规范与示例
(1)使用唯一且稳定的值
key
的值应该是唯一且稳定的,一般推荐使用数据本身的唯一标识,比如从后端获取的数据通常会有id
字段(如商品的productId
、用户的userId
)。尽量避免使用索引(index
)作为key
,虽然在简单场景下用索引好像也能“跑起来”,但在列表有增删操作时,索引会变化,就会让key
失去稳定、唯一的意义,引发前面提到的渲染问题。
不推荐用索引的示例(反例):
<ul><!-- 危险做法!列表增删时索引变化会导致key失效 --><li v-for="(item, index) in list" :key="index"> {{ item.name }}</li>
</ul>
推荐用唯一id
的示例(正例):
假设list
里的每个对象都有唯一id
,像这样:
data() {return {list: [{ id: 1, name: '商品A' },{ id: 2, name: '商品B' },{ id: 3, name: '商品C' }]}
}
<ul><!-- 正确做法,用数据唯一id做key --><li v-for="item in list" :key="item.id"> {{ item.name }}</li>
</ul>
(2)在template上使用key
(处理分组渲染等场景)
当使用<template>
标签配合v-for
进行分组或批量渲染时,key
要绑定在<template>
上,确保这一组节点能被正确识别和更新。
示例:
<template v-for="(group, groupKey) in groupedData" :key="groupKey"><h3>{{ group.title }}</h3><ul><li v-for="item in group.items" :key="item.id">{{ item.name }}</li></ul>
</template>
这里外层<template>
的key
用groupKey
(假设是分组的唯一标识,如'fruits'
、'vegetables'
),内层li
的key
用item.id
,保证不同层级的渲染更新都能正确进行。
(3)与Vue组件结合的示例
如果列表渲染的是自定义Vue组件,key
同样要遵循上述规则,保证组件实例能正确关联数据,维持状态。
比如有一个<ProductItem>
组件,渲染商品列表:
<ProductItem v-for="product in productList" :key="product.id" :product="product"
/>
这样,当productList
数据变化时,Vue能根据product.id
精准判断组件的创建、销毁和更新,避免组件状态错乱。
总之,合理使用key
属性,是保障Vue列表渲染高效、正确运行的关键一环,开发中要养成优先用数据唯一标识作为key
的习惯,避开因key
使用不当带来的各种潜在问题。
错误使用key
的常见问题及表现
- 问题1:输入框内容“串位”
若列表项里有输入框,用索引当key
,删除某一项后,输入框内容会异常“移动”。比如原列表[A, B, C]
对应输入框填了a、b、c
,删除A
后,原本B
的输入框内容会跑到A
的位置显示,因为Vue错误复用了组件,key
(索引)变化导致匹配混乱。 - 问题2:组件生命周期异常
列表项是自定义组件时,错误key
可能让组件的created
、mounted
等生命周期钩子不按预期执行,比如应该重新创建组件实例时,因key
复用导致钩子不触发,组件初始化逻辑失效,出现数据没正确初始化、监听事件丢失等问题。 - 问题3:DOM更新不及时或错误
没有key
或key
不稳定时,diff算法无法准确识别节点变化,可能使DOM更新滞后、重复渲染,页面出现闪烁、内容显示错误(比如新数据没渲染出来,旧数据没及时清除)等现象,影响用户体验和功能正确性。
所以,开发中一定要重视key
的合理设置,遵循“唯一、稳定”原则,规避这些常见问题 。
12. v-model双向绑定
基础用法(输入框示例)
v-model
是 Vue 实现表单元素双向数据绑定的指令,它本质上是 v-bind:value
(或其他属性,依表单元素而定)和 v-on:input
(或对应事件)的语法糖 。
以文本输入框为例:
<div id="app"><!-- v-model 绑定数据 --><input type="text" v-model="message"> <p>你输入的内容是:{{ message }}</p>
</div>
<script>
new Vue({el: '#app',data: {message: ''}
});
</script>
当在输入框输入内容时,message
数据会实时更新;同时,若通过代码修改 message
的值(比如在方法里改变 this.message
),输入框的显示也会同步变化,实现了视图和数据的双向联动。
不同表单元素的v-model
(1)复选框(单个)
单个复选框,v-model
绑定的是布尔值,控制是否选中:
<div id="app"><input type="checkbox" v-model="isChecked"> 是否同意<p>选中状态:{{ isChecked }}</p>
</div>
<script>
new Vue({el: '#app',data: {isChecked: false}
});
</script>
勾选或取消勾选复选框,isChecked
的值会在 true
和 false
间切换。
(2)复选框(多个)
多个复选框,v-model
绑定一个数组,数组元素是选中项的 value
:
<div id="app"><input type="checkbox" value="篮球" v-model="hobbies"> 篮球<input type="checkbox" value="足球" v-model="hobbies"> 足球<input type="checkbox" value="羽毛球" v-model="hobbies"> 羽毛球<p>你的爱好:{{ hobbies }}</p>
</div>
<script>
new Vue({el: '#app',data: {hobbies: []}
});
</script>
选中对应复选框,hobbies
数组会添加对应的 value
;取消选中则移除,实现多选数据的双向绑定。
(3)单选按钮
单选按钮组里,v-model
绑定选中项的 value
,同一组按钮通过 name
关联(也可依靠 v-model
自动管理互斥):
<div id="app"><input type="radio" value="男" v-model="gender"> 男<input type="radio" value="女" v-model="gender"> 女<p>你的性别:{{ gender }}</p>
</div>
<script>
new Vue({el: '#app',data: {gender: ''}
});
</script>
点击不同单选按钮,gender
会被设置为对应 value
,保证同一时间只有一个选中。
(4)下拉选择框(单选)
下拉框单选时,v-model
绑定选中项的 value
(option
标签的 value
属性,若没设置则取文本内容 ):
<div id="app"><select v-model="city"><option value="beijing">北京</option><option value="shanghai">上海</option><option value="guangzhou">广州</option></select><p>你选择的城市:{{ city }}</p>
</div>
<script>
new Vue({el: '#app',data: {city: 'beijing' // 可设置默认选中}
});
</script>
选择下拉项,city
会更新为对应 option
的 value
。
(5)下拉选择框(多选)
给 select
加 multiple
属性开启多选,v-model
绑定数组,存储选中项的 value
:
<div id="app"><select v-model="cities" multiple><option value="beijing">北京</option><option value="shanghai">上海</option><option value="guangzhou">广州</option></select><p>你选择的城市:{{ cities }}</p>
</div>
<script>
new Vue({el: '#app',data: {cities: []}
});
</script>
按住 Ctrl
(Windows)或 Command
(Mac)可多选,选中项的 value
会存入 cities
数组。
v-model
修饰符
Vue 为 v-model
提供了一些修饰符,方便处理特殊需求:
(1).lazy
默认 v-model
是在 input
事件触发时更新数据(输入即同步),加 .lazy
后,会在 change
事件触发时(比如输入框失去焦点)才更新数据:
<input type="text" v-model.lazy="message">
<!-- 输入时数据不实时变,离开输入框(change事件)才更新 -->
(2).number
自动将用户输入的值转为数值类型,若无法转为数字则保持原值。常用于处理需要数字输入的场景,比如年龄、数量:
<input type="text" v-model.number="age">
<!-- 输入“123”,age 是数字123;输入“abc”,age 还是字符串"abc"(若原本类型允许) -->
(3).trim
自动去除用户输入内容首尾的空白字符,适合处理用户名、密码等不想有多余空格的场景:
<input type="text" v-model.trim="username">
<!-- 输入“ 张三 ”,username 会变成“张三” -->
通过 v-model
,Vue 能简洁高效地实现表单数据和视图的双向绑定,搭配不同表单元素和修饰符,满足多样化的交互需求,让表单开发更轻松 。
13. 指令修饰符
Vue 的指令修饰符是对指令功能的扩展,让指令能更灵活地处理场景,以常见指令(如 v-on
、v-bind
等)的修饰符为例说明:
(1)v-on
修饰符
.stop
阻止事件冒泡。在嵌套的 DOM 元素事件中,点击子元素时,父元素的同名事件默认会被触发(事件冒泡机制),用 .stop
可阻断冒泡:
<div id="app"><div @click="parentClick">父元素<button @click.stop="childClick">子按钮</button></div>
</div>
<script>
new Vue({el: '#app',methods: {parentClick() {console.log('父元素点击事件触发');},childClick() {console.log('子按钮点击事件触发');}}
});
</script>
点击子按钮,只会触发 childClick
,父元素的 parentClick
不会因冒泡执行。
.prevent
阻止默认事件,比如阻止表单提交的默认跳转、链接的默认跳转等:
<!-- 阻止表单默认提交(跳转页面等行为) -->
<form @submit.prevent="handleSubmit"><button type="submit">提交</button>
</form>
<!-- 阻止a标签默认跳转 -->
<a href="https://www.example.com" @click.prevent="linkClick">点我</a>
<script>
new Vue({el: '#app',methods: {handleSubmit() {console.log('表单提交逻辑执行,不会跳转');},linkClick() {console.log('链接点击,不会跳转');}}
});
</script>
.capture
让事件采用捕获模式触发。默认事件是冒泡模式(从目标元素向父级传播),捕获模式则是从父级到目标元素方向触发:
<div id="app"><div @click.capture="parentCapture">父元素<div @click="childClick">子元素</div></div>
</div>
<script>
new Vue({el: '#app',methods: {parentCapture() {console.log('父元素捕获阶段触发');},childClick() {console.log('子元素点击触发');}}
});
</script>
点击子元素时,会先触发父元素的 parentCapture
(捕获阶段),再触发子元素的 childClick
。
.self
只有事件目标是当前元素本身时,才触发事件。可避免因子元素冒泡导致父元素事件误触发:
<div id="app"><div @click.self="parentSelfClick">父元素<button @click="childClick">子按钮</button></div>
</div>
<script>
new Vue({el: '#app',methods: {parentSelfClick() {console.log('父元素自身被点击才触发');},childClick() {console.log('子按钮点击触发');}}
});
</script>
点击子按钮,父元素的 parentSelfClick
不会触发;只有点击父元素的非子元素区域,才会触发。
.once
事件只会触发一次,后续点击等操作不再响应:
<button @click.once="onceClick">点击一次</button>
<script>
new Vue({el: '#app',methods: {onceClick() {console.log('只会触发一次');}}
});
</script>
第一次点击按钮执行 onceClick
,之后再点击无效。
按键修饰符(如 .enter
、.esc
等 )
针对键盘事件(keyup
、keydown
等),只在按下特定按键时触发事件:
<input type="text" @keyup.enter="enterHandler">
<input type="text" @keydown.esc="escHandler">
<script>
new Vue({el: '#app',methods: {enterHandler() {console.log('按下回车键触发');},escHandler() {console.log('按下Esc键触发');}}
});
</script>
输入框中,按下 Enter
触发 enterHandler
,按下 Esc
触发 escHandler
,常用的按键修饰符还有 .tab
、.delete
(删除键或退格键 )、.space
等,也可自定义按键别名 。
(2)v-bind
修饰符
.prop
强制将绑定的值作为 DOM 属性 而非 HTML 属性。在有些场景下,DOM 属性和 HTML 属性行为不同,需明确区分时使用。
HTML 属性(attribute)是元素初始渲染时设置的“原始值”,DOM 属性(property)是 JavaScript 中操作 DOM 元素时实际生效的属性,部分属性名、行为有差异(如 value
对应输入框初始值,defaultValue
是 DOM 属性;class
是 HTML 属性,className
是 DOM 属性等 )。
示例:区分 DOM 属性与 HTML 属性
<!-- 场景:设置 input 的默认值(DOM 的 defaultValue 属性) -->
<input v-bind:default-value.prop="defaultInputValue">
<script>
new Vue({el: '#app',data: {defaultInputValue: '初始默认值'}
});
</script>
若不加 .prop
,Vue 会尝试把 default-value
当作 HTML 属性设置(但 HTML 中无 default-value
属性,实际无效 );加 .prop
后,Vue 会明确设置 DOM 的 defaultValue
属性,让输入框初始值正确生效。
.camel
将连字符命名的属性名转为驼峰命名。HTML 属性名不区分大小写,且习惯用连字符(如 stroke-width
),但在 JavaScript 的 DOM 属性中,对应驼峰命名(strokeWidth
)。使用 .camel
可自动转换命名风格。
示例:SVG 图形属性绑定
<svg width="100" height="100"><!-- 绑定 SVG 的 stroke-width 属性(连字符命名) --><line x1="0" y1="0" x2="100" y2="100" v-bind:stroke-width.camel="lineWidth" stroke="black"/>
</svg>
<script>
new Vue({el: '#app',data: {lineWidth: 2 // 对应 DOM 属性 strokeWidth}
});
</script>
不加 .camel
时,stroke-width
会被当作普通 HTML 属性,可能无法正确映射到 DOM 的 strokeWidth
属性;加 .camel
后,Vue 自动转为驼峰命名,确保属性正确设置。
14. v-bind 操作 class
v-bind:class
(简写 :class
)可动态操作元素的 CSS 类,支持对象语法和数组语法,让类名根据数据动态添加/移除。
(1)对象语法
对象的键是类名,值是布尔值,决定是否添加该类。
示例:动态切换样式类
<style>
.active { color: red; font-weight: bold; }
.disabled { opacity: 0.5; cursor: not-allowed; }
</style><div id="app"><button :class="{ active: isActive, disabled: isDisabled }"@click="toggleActive">操作按钮</button>
</div>
<script>
new Vue({el: '#app',data: {isActive: true,isDisabled: false},methods: {toggleActive() {this.isActive = !this.isActive;this.isDisabled = !this.isDisabled;}}
});
</script>
isActive
为true
时,按钮添加active
类;isDisabled
为true
时,按钮添加disabled
类;
点击按钮可动态切换类,实现样式变化。
(2)数组语法
直接通过数组指定要添加的类名,类名可动态拼接(结合三元表达式、变量等 )。
示例:根据条件动态选类
<style>
.success { color: green; }
.error { color: red; }
.warning { color: orange; }
</style><div id="app"><p :class="[status === 'success' ? 'success' : status === 'error' ? 'error' : 'warning']">{{ status }} 提示</p><button @click="changeStatus">切换状态</button>
</div>
<script>
new Vue({el: '#app',data: {status: 'success'},methods: {changeStatus() {const statusList = ['success', 'error', 'warning'];const currentIndex = statusList.indexOf(this.status);this.status = statusList[(currentIndex + 1) % statusList.length];}}
});
</script>
根据 status
变量的值,动态选择对应的类名,改变文本颜色。
(3)对象语法 + 数组语法 混用
复杂场景下,可同时用对象语法和数组语法,灵活组合静态类与动态类。
示例:混合使用语法
<style>
.base { font-size: 16px; }
.highlight { background: yellow; }
.theme-dark { color: #333; }
</style><div id="app"><div :class="['base', // 静态类{ highlight: isHighlight, 'theme-dark': isDarkMode } // 动态类对象]">混合语法示例文本</div><button @click="toggleHighlight">切换高亮</button><button @click="toggleDarkMode">切换深色模式</button>
</div>
<script>
new Vue({el: '#app',data: {isHighlight: false,isDarkMode: false},methods: {toggleHighlight() {this.isHighlight = !this.isHighlight;},toggleDarkMode() {this.isDarkMode = !this.isDarkMode;}}
});
</script>
base
是固定添加的静态类;highlight
和theme-dark
按需动态添加,实现更灵活的样式控制。
15. v-model 应用于其他表单元素
除文本输入框,v-model
还支持 单选框、复选框、下拉框 等表单元素,自动关联数据与视图。
(1)单选框(Radio)
v-model
绑定选中项的 value
,同一组单选框通过 name
隐式分组(或直接靠 v-model
管理互斥 )。
示例:性别选择
<div id="app"><label>男:</label><input type="radio" value="male" v-model="gender"><label>女:</label><input type="radio" value="female" v-model="gender"><p>你选择的性别:{{ gender }}</p>
</div>
<script>
new Vue({el: '#app',data: {gender: 'male' // 默认选中}
});
</script>
点击不同单选框,gender
自动更新为对应 value
,保证同一时间仅一个选中。
(2)复选框(Checkbox)
- 单个复选框:
v-model
绑定布尔值(true
/false
),控制是否选中。 - 多个复选框:
v-model
绑定数组,存储选中项的value
。
示例:单个复选框(协议勾选)
<div id="app"><input type="checkbox" v-model="isAgree"> 同意协议<button :disabled="!isAgree">下一步</button>
</div>
<script>
new Vue({el: '#app',data: {isAgree: false}
});
</script>
勾选后,isAgree
变为 true
,按钮可用;取消勾选则反之。
示例:多个复选框(兴趣选择)
<div id="app"><label>篮球:</label><input type="checkbox" value="basketball" v-model="hobbies"><label>足球:</label><input type="checkbox" value="football" v-model="hobbies"><label>游泳:</label><input type="checkbox" value="swimming" v-model="hobbies"><p>你的爱好:{{ hobbies.join(', ') }}</p>
</div>
<script>
new Vue({el: '#app',data: {hobbies: []}
});
</script>
选中复选框,value
自动加入 hobbies
数组;取消选中则移除。
(3)下拉框(Select)
- 单选下拉:
v-model
绑定选中项的value
(option
的value
,无value
则取文本 )。 - 多选下拉:给
select
加multiple
,v-model
绑定数组,存储选中项value
。
示例:单选下拉(城市选择)
<div id="app"><select v-model="city"><option value="beijing">北京</option><option value="shanghai">上海</option><option value="guangzhou">广州</option></select><p>你选择的城市:{{ city }}</p>
</div>
<script>
new Vue({el: '#app',data: {city: 'beijing' // 默认选中}
});
</script>
选择下拉项,city
自动更新为对应 value
。
示例:多选下拉(城市选择)
<div id="app"><select v-model="cities" multiple><option value="beijing">北京</option><option value="shanghai">上海</option><option value="guangzhou">广州</option></select><p>你选择的城市:{{ cities.join(', ') }}</p>
</div>
<script>
new Vue({el: '#app',data: {cities: []}
});
</script>
按住 Ctrl
(Windows)/Command
(Mac)多选,选中项 value
存入 cities
数组。
16. 计算属性
计算属性(computed
)是基于已有数据动态派生新数据的方式,依赖数据变化时自动更新,且有缓存机制(依赖不变时,直接复用结果,提升性能 )。
(1)基本用法
示例:全名拼接
<div id="app"><input v-model="firstName" placeholder="名"><input v-model="lastName" placeholder="姓"><p>全名:{{ fullName }}</p>
</div>
<script>
new Vue({el: '#app',data: {firstName: '张',lastName: '三'},computed: {fullName() {// 依赖 firstName 和 lastNamereturn this.lastName + this.firstName; }}
});
</script>
fullName
依赖firstName
和lastName
,任一变化时,fullName
自动重新计算;- 若无计算属性,需在模板写表达式(如
{{ lastName + firstName }}
),但模板中逻辑复杂时难维护,计算属性让逻辑更清晰。
(2)缓存机制
计算属性会缓存结果,依赖不变时,多次访问直接用缓存,避免重复计算。
示例:模拟耗时计算
<div id="app"><p>计算结果:{{ expensiveResult }}</p><button @click="noop">无操作(触发重新渲染)</button>
</div>
<script>
new Vue({el: '#app',data: {baseNum: 10},computed: {expensiveResult() {console.log('计算属性执行...'); // 仅依赖变化时打印// 模拟耗时计算(如循环、复杂逻辑)let result = 0;for (let i = 0; i < 1000000; i++) {result += this.baseNum;}return result;}},methods: {noop() {// 空方法,仅触发视图重新渲染}}
});
</script>
- 初始加载或修改
baseNum
时,expensiveResult
重新计算,控制台打印; - 点击“无操作”按钮(仅触发重新渲染),
expensiveResult
依赖未变,直接用缓存,控制台不重复打印,体现缓存优化。
(3)与方法的区别
计算属性 vs 方法(methods
):
- 计算属性:有缓存,依赖不变时复用结果,适合基于已有数据派生新数据的场景。
- 方法:无缓存,每次调用都重新执行,适合需主动触发、不依赖响应式数据的逻辑。
示例:对比两者
<div id="app"><p>计算属性:{{ reversedMsgComputed }}</p><p>方法:{{ reversedMsgMethod() }}</p><button @click="noop">无操作(触发重新渲染)</button>
</div>
<script>
new Vue({el: '#app',data: {message: 'Hello Vue'},computed: {reversedMsgComputed() {console.log('计算属性执行');return this.message.split('').reverse().join('');}},methods: {reversedMsgMethod() {console.log('方法执行');return this.message.split('').reverse().join('');},noop() {}}
});
</script>
- 初始加载:两者都执行,控制台打印;
- 点击“无操作”:计算属性因依赖未变,不执行;方法每次调用都执行,控制台重复打印。
17. 计算属性的完整写法
计算属性默认是“getter”(仅获取值 ),也可显式定义 getter + setter,支持手动修改计算属性的值(触发 setter 逻辑 )。
(1)完整语法(get + set)
computed: {// 计算属性名fullName: { // 获取值(默认触发)get() { return this.lastName + this.firstName;},// 设置值(手动修改时触发)set(newValue) { // 解析 newValue,更新依赖数据const [last, first] = newValue.split(' '); this.lastName = last;this.firstName = first;}}
}
示例:双向操作计算属性
<div id="app"><input v-model="firstName" placeholder="名"><input v-model="lastName" placeholder="姓"><!-- 绑定计算属性 --><input v-model="fullName" placeholder="全名"> <p>firstName: {{ firstName }}</p><p>lastName: {{ lastName }}</p>
</div>
<script>
new Vue({el: '#app',data: {firstName: '张',lastName: '三'},computed: {fullName: {get() {return this.lastName + this.firstName;},set(newValue) {// 假设输入格式为“姓 名”,拆分更新数据const parts = newValue.split(' ');if (parts.length === 2) {this.lastName = parts[0];this.firstName = parts[1];}}}}
});
</script>
- 修改
firstName
或lastName
:fullName
的get
触发,自动更新; - 修改
fullName
输入框:set
触发,按规则拆分值并更新firstName
、lastName
,实现计算属性的“双向绑定”。
(2)使用场景
需手动修改计算属性时(如表单回显、复杂数据同步 ),用完整写法。若仅需“获取”,用简写的 getter 即可。
18. watch 监视器
watch
用于监听响应式数据变化,执行自定义逻辑(如异步请求、复杂操作 )。
(1)基本用法
示例:监听单个数据
<div id="app"><input v-model="username" placeholder="输入用户名"><p>用户名状态:{{ usernameStatus }}</p>
</div>
<script>
new Vue({el: '#app',data: {username: '',usernameStatus: '' // 用于显示用户名校验状态},watch: {// 监听 username 数据变化username(newVal, oldVal) {console.log(`用户名从 "${oldVal}" 变为 "${newVal}"`);// 模拟异步校验(如调用后端接口)if (newVal.length < 3) {this.usernameStatus = '用户名长度不能少于3位';} else {this.usernameStatus = '用户名可用';}}}
});
</script>
- 当
username
变化时,watch
回调自动执行,参数newVal
是变化后的值,oldVal
是变化前的值; - 适合处理数据变化后的异步操作(如接口请求、定时器等),这是计算属性难以直接实现的场景。
(2)监听对象属性
若需监听对象内部属性的变化,需用 字符串路径 或 深度监听(deep: true
)。
① 字符串路径(监听单个属性)
<div id="app"><input v-model="user.name" placeholder="输入姓名"><p>姓名变化日志:{{ nameLog }}</p>
</div>
<script>
new Vue({el: '#app',data: {user: { name: '' },nameLog: ''},watch: {// 用字符串路径监听 user 对象的 name 属性'user.name'(newVal, oldVal) {this.nameLog = `姓名从 "${oldVal}" 改为 "${newVal}"`;}}
});
</script>
② 深度监听(监听对象所有属性)
若对象多个属性可能变化,且需统一处理,可开启 deep: true
:
<div id="app"><input v-model="user.name" placeholder="姓名"><input v-model="user.age" type="number" placeholder="年龄"><p>用户信息变化:{{ userLog }}</p>
</div>
<script>
new Vue({el: '#app',data: {user: { name: '', age: '' },userLog: ''},watch: {user: {handler(newVal, oldVal) {// newVal 和 oldVal 是变化后的完整 user 对象this.userLog = `姓名:${newVal.name},年龄:${newVal.age}`;},deep: true // 开启深度监听,对象任一属性变化都会触发}}
});
</script>
handler
是监听的核心回调函数,deep: true
表示递归监听对象内部所有属性;- 注意:深度监听会消耗更多性能,仅在必要时使用(如对象属性频繁变化且需统一响应)。
(3)监听数组
Vue 能自动监听数组的 变异方法(如 push
、pop
、splice
等),无需额外配置;但直接修改数组索引或长度时,需用 Vue.set
或开启深度监听。
① 监听数组变异方法
<div id="app"><button @click="addHobby">添加爱好</button><p>爱好列表:{{ hobbies.join(', ') }}</p><p>变化日志:{{ hobbyLog }}</p>
</div>
<script>
new Vue({el: '#app',data: {hobbies: [],hobbyLog: ''},methods: {addHobby() {const newHobby = ['篮球', '游泳', '阅读'][Math.floor(Math.random() * 3)];this.hobbies.push(newHobby); // 调用数组变异方法}},watch: {hobbies(newVal) {this.hobbyLog = `新增爱好:${newVal[newVal.length - 1]}`;}}
});
</script>
② 监听数组索引修改(需 Vue.set
或深度监听)
直接修改索引(如 this.hobbies[0] = '足球'
)无法触发 watch
,需用 Vue.set
:
<script>
new Vue({el: '#app',data: {hobbies: ['篮球'],hobbyLog: ''},methods: {updateFirstHobby() {// 用 Vue.set 修改数组索引,确保触发响应式Vue.set(this.hobbies, 0, '足球');}},watch: {hobbies(newVal) {this.hobbyLog = `第一个爱好改为:${newVal[0]}`;}}
});
</script>
19. watch 完整写法
watch
的完整配置包含 handler
(核心回调)、deep
(深度监听)、immediate
(初始触发),适用于复杂场景。
(1)完整配置项说明
配置项 | 类型 | 作用 |
---|---|---|
handler | 函数 | 数据变化时执行的回调,接收 newVal (新值)和 oldVal (旧值)两个参数 |
deep | 布尔值 | 是否深度监听(对象/数组内部属性变化是否触发),默认 false |
immediate | 布尔值 | 是否在初始化时立即执行一次 handler ,默认 false (仅数据变化时触发) |
(2)示例:完整配置的使用
场景:初始化时加载用户信息,且监听用户信息变化时重新请求数据。
<div id="app"><input v-model="user.name" placeholder="姓名"><input v-model="user.age" type="number" placeholder="年龄"><p>用户信息:{{ userInfo }}</p>
</div>
<script>
new Vue({el: '#app',data: {user: { name: '张三', age: 25 },userInfo: '' // 存储格式化后的用户信息},watch: {user: {// 核心回调函数handler(newVal) {this.userInfo = `姓名:${newVal.name},年龄:${newVal.age}岁`;},deep: true, // 深度监听用户对象immediate: true // 初始化时立即执行一次,加载初始信息}}
});
</script>
- 页面加载时,
immediate: true
让handler
立即执行,userInfo
显示初始用户信息; - 修改
user.name
或user.age
时,deep: true
触发handler
,userInfo
实时更新。
(3)注意事项
-
避免滥用深度监听:深度监听会遍历对象所有属性,对象复杂时影响性能,优先用字符串路径监听单个属性;
-
immediate
初始化触发:此时oldVal
为undefined
(初始化无旧值),需在回调中处理边界情况; -
注销监听:组件销毁时,Vue 会自动注销
watch
,无需手动清理;但手动创建的监听(如vm.$watch
)需手动注销:// 手动创建监听,返回注销函数 const unwatch = this.$watch('user.name', (newVal) => {console.log('姓名变化:', newVal); }); // 组件销毁前注销监听 this.$on('hook:beforeDestroy', () => {unwatch(); });
20. 生命周期
Vue 实例从创建到销毁的整个过程称为生命周期,Vue 提供了多个生命周期钩子函数,允许开发者在不同阶段执行自定义逻辑(如初始化数据、操作 DOM、清理资源等)。
(1)生命周期阶段与钩子函数
Vue2 生命周期分为 创建、挂载、更新、销毁 四个阶段,核心钩子函数如下:
阶段 | 钩子函数 | 执行时机 | 常用场景 |
---|---|---|---|
创建阶段 | beforeCreate | 实例创建前,data 、methods 等未初始化 | 几乎不用(无可用数据和方法) |
created | 实例创建完成,data 、methods 已初始化,但 DOM 未挂载($el 不存在) | 初始化数据(如调用接口获取初始数据)、绑定事件监听 | |
挂载阶段 | beforeMount | 挂载前,模板已编译,但未渲染到 DOM($el 已存在但未挂载到页面) | 查看编译后的模板,不操作 DOM |
mounted | 挂载完成,DOM 已渲染到页面($el 存在且挂载) | 操作 DOM(如获取元素、初始化第三方插件)、发起 DOM 相关请求 | |
更新阶段 | beforeUpdate | 数据变化后,DOM 更新前 | 查看数据变化前的状态 |
updated | 数据变化后,DOM 更新完成 | 操作更新后的 DOM(如重新计算元素位置) | |
销毁阶段 | beforeDestroy | 实例销毁前,data 、methods 仍可用 | 清理资源(如清除定时器、注销事件监听、销毁第三方插件实例) |
destroyed | 实例销毁完成,data 、methods 不可用 | 几乎不用(实例已销毁,无可用资源) |
(2)示例:生命周期钩子的使用
<div id="app"><p>计数器:{{ count }}</p><button @click="count++">增加</button><button @click="destroyVm">销毁实例</button>
</div>
<script>
const vm = new Vue({el: '#app',data: {count: 0,timer: null // 存储定时器},// 创建阶段beforeCreate() {console.log('beforeCreate:', this.count, this.increment); // 输出:undefined undefined(data、methods 未初始化)},created() {console.log('created:', this.count, this.increment); // 输出:0 函数(data、methods 已初始化)// 初始化定时器(示例:每2秒增加count)this.timer = setInterval(() => {this.count++;}, 2000);},// 挂载阶段beforeMount() {console.log('beforeMount:', document.querySelector('p').textContent); // 输出:{{ count }}(DOM 未渲染)},mounted() {console.log('mounted:', document.querySelector('p').textContent); // 输出:计数器:0(DOM 已渲染)},// 更新阶段beforeUpdate() {console.log('beforeUpdate(数据):', this.count);console.log('beforeUpdate(DOM):', document.querySelector('p').textContent);// 数据已变,DOM 未更新(如 count=1 时,DOM 仍显示 0)},updated() {console.log('updated(数据):', this.count);console.log('updated(DOM):', document.querySelector('p').textContent);// 数据和 DOM 都已更新(如 count=1 时,DOM 显示 1)},// 销毁阶段beforeDestroy() {console.log('beforeDestroy:', this.count); // 输出当前 count(data 仍可用)// 清理资源:清除定时器clearInterval(this.timer);},destroyed() {console.log('destroyed:', this.count); // 输出当前 count(但实例已销毁,操作无意义)},methods: {increment() {this.count++;},destroyVm() {// 手动销毁 Vue 实例this.$destroy();}}
});
</script>
(3)核心钩子函数使用场景总结
created
:优先处理数据初始化(如接口请求),此时 DOM 未挂载,不可操作 DOM;mounted
:唯一可安全操作 DOM 的钩子(如初始化地图、图表等第三方插件);beforeDestroy
:必须在此清理资源(定时器、事件监听、WebSocket 连接等),避免内存泄漏;- 更新阶段钩子:尽量避免在
updated
中修改数据(可能导致无限循环),仅用于 DOM 操作。
21. 初始化渲染和获取焦点
初始化渲染是 Vue 实例挂载后将模板渲染为 DOM 的过程;获取焦点是常见的 DOM 操作需求(如页面加载后让输入框自动聚焦),需在 DOM 渲染完成后执行。
(1)初始化渲染的核心逻辑
Vue 初始化渲染流程:
- 解析模板(将
{{ }}
、指令等转换为渲染函数); - 执行渲染函数,生成虚拟 DOM;
- 将虚拟 DOM 转换为真实 DOM,挂载到
el
指定的容器中(mounted
钩子触发时完成)。
示例:初始化渲染文本和指令
<div id="app"><h1>{{ title }}</h1><p v-if="showDesc">{{ desc }}</p><input v-model="inputVal" placeholder="输入内容">
</div>
<script>
new Vue({el: '#app',data: {title: 'Vue 初始化渲染',showDesc: true,desc: '页面加载时自动渲染此文本',inputVal: ''},mounted() {console.log('初始化渲染完成,DOM 可用');}
});
</script>
- 页面加载后,
title
、desc
自动渲染到 DOM,v-if="showDesc"
控制p
标签显示,v-model
绑定输入框; mounted
触发时,所有 DOM 已渲染完成。
(2)初始化获取焦点(基础方式)
获取焦点需操作 DOM,必须在 mounted
钩子中执行(DOM 已挂载),通过 document.querySelector
或 this.$el
获取元素。
示例:输入框初始化自动聚焦
<div id="app"><input type="text" id="username" placeholder="自动聚焦">
</div>
<script>
new Vue({el: '#app',mounted() {// 方式1:通过 document 获取元素const input = document.getElementById('username');input.focus(); // 触发聚焦// 方式2:通过 this.$el 获取组件根元素,再查找子元素// const input = this.$el.querySelector('#username');// input.focus();}
});
</script>
mounted
中,document.getElementById('username')
能正确获取输入框元素,调用focus()
实现自动聚焦;- 若输入框是动态渲染的(如
v-if
控制显示),需确保元素已存在(可在updated
中处理,或用nextTick
)。
(3)动态元素的焦点获取(nextTick
)
若元素通过 v-if
、v-for
等动态渲染,mounted
中可能尚未生成该元素,需用 this.$nextTick
确保元素渲染完成后再执行聚焦。
示例:动态显示的输入框获取焦点
<div id="app"><button @click="showInput = true">显示输入框</button><input v-if="showInput" ref="dynamicInput" placeholder="动态聚焦">
</div>
<script>
new Vue({el: '#app',data: {showInput: false},methods: {showAndFocus() {this.showInput = true;// 错误:此时 input 尚未渲染,focus 无效// this.$refs.dynamicInput.focus();// 正确:nextTick 等待 DOM 更新完成this.$nextTick(() => {this.$refs.dynamicInput.focus();});}},// 初始化时自动显示并聚焦(可选)mounted() {this.showAndFocus();}
});
</script>
this.$nextTick
的回调会在 DOM 更新循环完成后执行,确保v-if="showInput"
渲染的输入框已存在;- 用
ref
引用元素(ref="dynamicInput"
),通过this.$refs.dynamicInput
获取元素,比document
更灵活(组件内有效)。
22. mounted 获取焦点
mounted
是 Vue 实例挂载完成的钩子,此时 DOM 已完全渲染,是执行 DOM 操作(如获取焦点)的最佳时机。本节详细说明 mounted
中获取焦点的多种场景和最佳实践。
(1)通过 ref
获取元素(推荐)
ref
是 Vue 提供的引用机制,用于在组件内快速获取 DOM 元素或子组件,比 document
更简洁、更具组件封装性。
示例:ref
实现输入框聚焦
<div id="app"><!-- 用 ref 标记输入框 --><input type="text" ref="usernameInput" placeholder="请输入用户名"><input type="password" ref="passwordInput" placeholder="请输入密码">
</div>
<script>
new Vue({el: '#app',mounted() {// 通过 this.$refs 获取输入框并聚焦this.$refs.usernameInput.focus();// 延迟聚焦到密码框(示例:2秒后切换焦点)setTimeout(() => {this.$refs.passwordInput.focus();}, 2000);}
});
</script>
ref="usernameInput"
给输入框添加引用标识,this.$refs.usernameInput
直接获取该元素;- 优点:不依赖
id
或class
(避免全局冲突),仅在当前组件内有效,适合组件化开发。
(2)多个动态元素的焦点管理
若页面有多个动态生成的输入框(如 v-for
渲染的列表),需通过 ref
数组和索引控制焦点。
示例:列表输入框的焦点切换
<div id="app"><div v-for="(item, index) in inputs" :key="index"><!-- ref 绑定为数组,存储所有输入框 --><input type="text" :ref="`input-${index}`" :placeholder="`输入框 ${index + 1}`"@keyup.enter="focusNext(index)"></div><button @click="addInput">添加输入框</button>
</div>
<script>
new Vue({el: '#app',data: {inputs: [{}, {}, {}] // 初始3个输入框},mounted() {// 初始化聚焦第一个输入框this.$refs['input-0'].focus();},methods: {// 按 Enter 键聚焦下一个输入框focusNext(index) {const nextIndex = index + 1;// 检查下一个输入框是否存在if (this.$refs[`input-${nextIndex}`]) {this.$refs[`input-${nextIndex}`].focus();} else {// 最后一个输入框按 Enter 时添加新输入框并聚焦this.addInput(nextIndex);}},// 添加新输入框并聚焦addInput(focusIndex) {this.inputs.push({});// 等待 DOM 更新后聚焦新输入框this.$nextTick(() => {this.$refs[`input-${focusIndex}`].focus();});}}
});
</script>
:ref="
input-index‘"‘动态生成引用名(如‘input−0‘、‘input−1‘),‘this.{index}`"` 动态生成引用名(如 `input-0`、`input-1` ),`this.index‘"‘动态生成引用名(如‘input−0‘、‘input−1‘),‘this.refs` 存储为对象;- 按
Enter
键时,focusNext
方法切换到下一个输入框,最后一个输入框按Enter
时自动添加新输入框并聚焦。
(3)第三方组件的焦点控制
若使用第三方 UI 组件(如 Vue 插件的输入框),需通过组件暴露的方法或 $el
获取底层 DOM 元素。
示例:第三方输入框组件聚焦
<!-- 假设使用第三方输入框组件 <custom-input> -->
<div id="app"><custom-input ref="customInput" placeholder="第三方组件输入框"></custom-input>
</div>
<script>
// 模拟第三方组件
Vue.component('custom-input', {template: `<div class="custom-input"><input type="text" :value="value" @input="$emit('input', $event.target.value)"></div>`,props: ['value']
});new Vue({el: '#app',mounted() {// 方式1:通过组件的 $el 获取底层 DOM 元素const input = this.$refs.customInput.$el.querySelector('input');input.focus();// 方式2:若组件暴露 focus 方法,直接调用(推荐)// this.$refs.customInput.focus();}
});
</script>
- 第三方组件通常会封装 DOM 结构,需通过
this.$refs.customInput.$el
获取组件根元素,再查找内部输入框; - 若组件提供了
focus
等方法(如 Element UI 的el-input
),直接调用this.$refs.customInput.focus()
更优雅,避免依赖内部 DOM 结构。
(4)注意事项
mounted
中确保元素存在:若元素通过v-if="false"
初始隐藏,mounted
中无法获取,需在v-if
变为true
后(如updated
或nextTick
)执行聚焦;- 避免频繁操作 DOM:焦点切换等操作尽量通过用户交互触发,避免在
mounted
中执行大量 DOM 操作; - 组件销毁时清理:若聚焦逻辑依赖定时器等,需在
beforeDestroy
中清理,避免内存泄漏。