在 Vue 开发中,掌握响应式数据处理、模板语法、生命周期等核心知识点是构建高效应用的基础。这篇笔记将围绕 Vue 中的关键概念展开,通过问题引导、代码示例和总结帮助你梳理知识点。
一、ref和reactive创建基本类型的响应式数据
在以前的学习中,我们并没有接触过响应式数据这个概念,那么响应式数据到底是什么呢?接下来我会用一个例子来带大家了解什么在vue中什么是响应式数据:
<script setup lang="ts">
let num1 = 0 // 创建变量num1
</script>
<template>
<div>我的数字:{{num1}}</div> // 显示变量num1
<button @click="num1++">点击数字+1</button> // 设置点击事件,点击后num1+1
</template>
但是我们实际点击按钮的时候,num1始终是原来初始的值,始终不会改变,本质上的原因是因为在vue中分为响应式数据和非响应式数据,直接这么定义的数据是非响应数据,当然也不会被点击事件出发,所以当我们要设置响应式数据的时候就需要显性的告诉编译器。
在 Vue 开发中,掌握响应式数据处理、模板语法、生命周期等核心知识点是构建高效应用的基础。这篇笔记将围绕 Vue 中的关键概念展开,通过问题引导、代码示例和总结帮助你梳理知识点。
1.1 ref创建基本类型的响应式数据
ref 可以用来创建基本类型(如数字、字符串、布尔值)的响应式数据,也可以创建对象类型的响应式数据。它的内部会将基本类型数据包装成一个包含 value 属性的对象。
问题:如何用 ref 创建一个响应式的计数器,并在点击按钮时更新它?
<template>
<div>
<p>计数器:{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 创建基本类型的响应式数据
const count = ref(0)
// 点击事件:更新数据(注意在脚本中需要通过 .value 访问)
const increment = () => {
count.value++
}
</script>
运行结果:
说明:
- 在模板中使用 ref 创建的数据时,不需要加 .value,Vue 会自动解析。
- 在脚本中修改数据时,必须通过 .value 访问,因为 ref 包装后的数据是一个响应式对象。
1.2 reactive创建基本类型的响应式数据
当我们遇到对象类型的数据,数据的层级很深的时候,最后还需要.value来访问,这时候使用我吗的reactive定义响应式数据的话就会简单很多,reactive 主要用于创建对象类型的响应式数据,它返回一个响应式的代理对象。注意:reactive 不能直接用于基本类型数据,否则无法实现响应式。例如:
<template>
<div>
<!-- 直接用 reactive 包裹基本类型,数据更新后视图不会响应 -->
<p>错误示例(非响应式):{{ wrongNum }}</p>
<button @click="updateWrong">更新错误示例</button>
<!-- 正确:将基本类型放在对象中 -->
<p>正确示例(响应式):{{ rightData.num }}</p>
<button @click="updateRight">更新正确示例</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// 错误用法:reactive 直接包裹基本类型,不具备响应式
const wrongNum = reactive(10)
// 正确用法:将基本类型放在对象中
const rightData = reactive({
num: 10
})
const updateWrong = () => {
wrongNum = 20 // 视图不会更新
}
const updateRight = () => {
rightData.num = 20 // 视图会响应更新
}
</script>
1.3 ref和reactive的对比和总结
特性 |
ref |
reactive |
适用类型 |
基本类型、对象类型 |
仅对象类型(对象、数组等) |
访问方式 |
脚本中需用 .value,模板中不用 |
直接访问属性(无需 .value) |
本质 |
包装成含 value 的对象 |
返回对象的响应式代理 |
推荐场景 |
基本类型、简单对象或数组 |
复杂对象(如嵌套对象) |
二、toRefs和toRef响应式转换
当我们从 reactive 创建的响应式对象中解构属性时,解构后的属性会失去响应式。如何解决这个问题?toRefs 和 toRef 就是用来处理这种场景的工具。
错误示例:
2.1 toRefs
toRefs 可以将一个响应式对象(reactive 创建的)转换为一个普通对象,其中每个属性都是一个 ref 类型的响应式数据。
问题:如果直接解构 reactive 创建的对象,属性会失去响应式,如何用 toRefs 解决这个问题?
<template>
<div>
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
<button @click="updateInfo">更新信息</button>
</div>
</template>
<script setup>
import { reactive, toRefs } from 'vue'
// 创建响应式对象
const user = reactive({
name: '张三',
age: 20
})
// 用 toRefs 转换后解构,属性仍保持响应式
const { name, age } = toRefs(user)
// 更新数据
const updateInfo = () => {
name.value = '李四'
age.value = 21
console.log(user.name, user.age) // 更新解构后的数据,会改变原来对象的值
}
</script>
说明:
- toRefs 会为对象的每个属性创建一个 ref,因此解构后仍能保持响应式,并且解构后的的数据还和原来的对象形数据保持关联。
- 适用于需要批量解构响应式对象属性的场景。
2.2 toRef
toRef 用于为响应式对象的单个属性创建一个 ref,与原对象保持关联。
问题:如果只需要响应式对象中的某一个属性,如何用 toRef 单独提取?
基本语法:
toref(对象名,单独提取的属性名)
<template>
<div>
<p>年龄:{{ ageRef }}</p>
<button @click="increaseAge">年龄+1</button>
</div>
</template>
<script setup>
import { reactive, toRef } from 'vue'
const user = reactive({
name: '张三',
age: 20
})
// 单独提取 age 属性为 ref
const ageRef = toRef(user, 'age')
// 更新属性
const increaseAge = () => {
ageRef.value++ // 原对象的 age 也会同步更新
console.log(user.age) // 输出原来对象的 age
}
</script>
说明:
- toRef 的第一个参数是响应式对象,第二个参数是属性名。
- 提取的 ref 与原对象属性联动:修改 ref 的值,原对象属性会同步更新,反之亦然。
2.3.toRefs和toRef的对比和总结
特性 |
toRefs |
toRef |
作用 |
批量转换响应式对象的所有属性 |
单独转换响应式对象的单个属性 |
输入 |
响应式对象 |
响应式对象 + 属性名 |
输出 |
包含多个 ref 的普通对象 |
单个 ref 对象 |
适用场景 |
需要解构多个属性时 |
只需要单个属性时 |
- toRefs 和 toRef 都用于reactive定义的响应式对象解包的时候来保留响应式对象属性的响应式,避免解构后失去联动。
- 两者创建的 ref 都与原对象属性关联,修改时会相互影响。
三、模板语法
Vue 的模板语法是声明式的,用于将数据渲染到 DOM 中。常用的模板渲染方式有文本插值、原始 HTML 插入和纯文本插入。
3.1 文本插值
文本插值是最基础的模板语法,使用双大括号 {{ }} 将数据插入到模板(HTML标签)中。
问题:如何在模板中显示响应式数据,并在数据更新时自动刷新?
<template>
<div>
<p>用户名:{{ username }}</p>
<p>当前时间:{{ new Date().toLocaleString() }}</p> <!-- 支持表达式 -->
</div>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('小宁爱Vue!')
</script>
3.2 原始HTML
如果需要插入 HTML 片段(而非纯文本),可以使用 v-html 指令。
问题:如何在模板中渲染一段包含标签的 HTML 字符串?
<template>
<div>
<!-- 直接插值会显示纯文本 -->
<p>{{htmlStr}}</p>
<!-- v-html 插入HTML片段,会解析标签 -->
<p v-html="htmlStr"></p>
</div>
</template>
<script setup>
const htmlStr = '<strong style="color:red">这是红色的文本</strong>'
</script>
注意:
- 动态渲染 HTML 存在 XSS 风险,仅在信任的内容中使用 v-html。
- v-html 会覆盖元素的子节点。
3.3 插入纯文本
除了双大括号,v-text 指令也可以用于插入纯文本,功能与 {{ }} 类似,但优先级更高。
问题:v-text 和 {{ }} 有什么区别?
<template>
<div>
<p v-text="message+fg"></p> <!-- 等价于 {{ message }} -->
<p>{{ message }} 后面的内容</p> <!-- 插值可以与其他内容混合 -->
</div>
</template>
<script setup>
import { ref } from 'vue'
const fg = ref('fg')
const message = ref('Hello Vue')
</script>
说明:
- v-text 会替换整个元素的内容,而 {{ }} 可以嵌入到元素的其他内容中。
- 两者都会将内容作为纯文本处理,不会解析 HTML。
四、computed计算属性
在模板中,我们有时需要对数据进行处理后再显示(如格式化、计算)。如果直接在模板中写复杂逻辑,会导致模板臃肿。computed 计算属性就是用来解决这个问题的。
基础语法1:(只读)
const 变量名 = conpeted(回调函数)
基础语法2:(改)
const 变量名 = competed(
{
get:()=>{return 要修改的值}
ste:(变量名(也就是接受要修改的值))=>{函数体}
}
)
问题:如何根据两个响应式数据(商品单价和数量)计算总价,并在数据变化时自动更新?
<template>
<div>
<p>单价:{{ price }} 元</p>
<p>数量:{{ quantity }} 个</p>
<p>总价:{{ totalPrice }} 元</p> <!-- 计算属性 -->
<button @click="price++">提高单价</button>
<button @click="quantity++">增加数量</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const price = ref(10)
const quantity = ref(2)
// 计算属性:依赖 price 和 quantity,自动追踪依赖并缓存结果
const totalPrice = computed(() => {
return price.value * quantity.value
})
</script>
特性:
- 缓存性:计算属性会缓存结果,只有当依赖的响应式数据(其实就是回调函数的返回值发生变化的时候)变化时,才会重新计算。
- 只读性:默认情况下,计算属性是只读的。如果需要修改,可通过 get 和 set 定义:
<template>
<p>{{firstName}}</p>
<p>{{lastName}}</p>
<button @click="fullName = '小妹'">修改名字为小妹</button>
<p>{{fullName}}</p>
</template>
<script setup>
import {ref, reactive, computed} from "vue";
let firstName = ref("李")
let lastName = ref("小妹")
let fullName = computed({
get:()=> { // 获取fullName的时候调用
return firstName.value + lastName.value
},
set:(value)=>{ // 修改fullName的时候调用
let names = value.split(" ")
firstName.value = '小'
lastName.value = '宁'
}
}
)
</script>
注意:
这种写法competed的参数实际上是一个对象(键值对)类型,这个对象类型包含了两个回调函数,一个是get回调函数,另外一个是set回调函数,get函数在访问计算属性的时候调用,set(参数)在修改就算属性的时候调用,set中的参数实际上指的是get函数中的返回内容,这个名字可以自己随意取。
总结:
- 计算属性适用于依赖其他数据派生的场景,避免模板中的复杂逻辑。
- 与方法相比,计算属性的缓存特性能提升性能(方法每次渲染都会重新执行)。
五、v-bind:属性绑定
在 Vue 中,我们需要动态绑定 HTML 属性(如 src、class、style 等)时,使用 v-bind 指令。
5.1 v-bind:属性名="变量名"
v-bind:属性名 用于将 HTML 属性与响应式数据绑定,属性值会随数据变化而更新。
问题:如何动态绑定图片的 src 属性,实现点击切换图片(图片为网络地址可以直接使用)?
<template>
<div>
<img v-bind:src="imgUrl" alt="图片" width="200px">
<button @click="toggleImg">切换图片</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const imgList = [
'https://picsum.photos/id/1/200/200',
'https://picsum.photos/id/2/200/200'
]
const imgUrl = ref(imgList[0])
const toggleImg = () => {
// 使用三元运算符:对图片的切换
imgUrl.value = imgUrl.value === imgList[0] ? imgList[1] : imgList[0]
}
</script>
5.2 简写 :属性名="变量名"
v-bind 有一个简写形式:省略 v-bind,直接用 :属性名。
问题:如何用简写形式绑定 a 标签的 href 属性?
<template>
<a :href="linkUrl" target="_blank">访问 Vue 官网</a>
</template>
<script setup>
import { ref } from 'vue'
const linkUrl = ref('https://vuejs.org/')
</script>
总结:
- v-bind 用于动态绑定属性,简写为 :。
- 绑定的值可以是变量、表达式或对象(如 :class="{ active: isActive }")。
六、v-on:事件绑定
在 Vue 中,处理用户交互(如点击、输入)需要绑定事件,v-on 指令用于事件绑定。
6.1 v-on:事件类型="函数名称"
v-on:事件类型 用于绑定事件,值为事件处理函数。
问题:如何为按钮绑定点击事件,实现弹窗提示?
<template>
<button v-on:click="showMessage">点击弹窗</button>
</template>
<script setup>
const showMessage = () => {
alert('Hello, v-on!')
}
</script>
6.2 @事件类型="函数名称"
v-on 的简写形式是 @,即 @事件类型="函数名称",更常用。
问题:如何用简写形式绑定输入框的 input 事件,实时获取输入内容?
<template>
<div>
<input type="text" @input="handleInput" placeholder="输入内容">
<p>实时监视你输入的内容:{{ inputValue }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const inputValue = ref('')
// 事件处理函数接收事件对象
const handleInput = (e) => {
inputValue.value = e.target.value
}
</script>
6.3 常见的事件类型
1. 鼠标事件
@click
:单击元素时触发,适用于按钮点击、菜单选择等场景。@dblclick
:双击元素时触发,可用于文件打开、特殊操作等场景。@mouseenter
:鼠标进入元素时触发,常用于悬停效果、下拉菜单显示等。@mouseleave
:鼠标离开元素时触发,适用于悬停效果消失、菜单隐藏等。@mouseover
:鼠标悬停在元素上时触发,类似mouseenter
但会冒泡。@mouseout
:鼠标移出元素时触发,类似mouseleave
但会冒泡。@mousedown
:鼠标按钮按下时触发,可用于拖拽操作开始等场景。@mouseup
:鼠标按钮释放时触发,适用于拖拽操作结束等场景。2. 键盘事件
@keydown
:按下任意键时触发,常用于键盘快捷键、游戏控制等。@keyup
:释放按键时触发,适用于表单提交、字符输入确认等。@keypress
:按下字符键时触发,用于字符输入处理(已不推荐使用)。@keyup.enter
:按下回车键时触发,可用于表单提交、换行等场景。@keyup.tab
:按下 Tab 键时触发,适用于焦点切换等场景。@keyup.delete
:按下删除键时触发,用于删除操作等场景。@keyup.esc
:按下 ESC 键时触发,适用于取消操作、关闭弹窗等场景。3. 表单事件
@input
:输入框内容改变时触发,可实时获取输入值(如实时显示用户输入内容)。@change
:表单元素值改变并失去焦点时触发,适用于下拉选择、复选框状态改变等场景。@focus
:元素获得焦点时触发,常用于输入框激活状态处理等。@blur
:元素失去焦点时触发,可用于表单验证、自动保存等场景。@submit
:表单提交时触发,适用于表单数据提交等场景。4. 窗口事件
@resize
:窗口大小改变时触发,常用于响应式布局调整等场景。@scroll
:滚动条滚动时触发,适用于滚动监听、懒加载等场景。@load
:页面或资源加载完成时触发,可用于图片加载完成、初始化操作等。@unload
:页面卸载时触发,适用于数据保存、清理工作等场景。5. 触摸事件
@touchstart
:触摸开始时触发,适用于移动端触摸操作的起始阶段。@touchmove
:触摸移动时触发,常用于手势操作、滑动等场景。@touchend
:触摸结束时触发,适用于触摸操作完成的场景。
超级适用的输入框提交事件(必学):
<template>
<input type="text" @keyup.enter="handleEnter" placeholder="按Enter提交">
</template>
<script setup>
const handleEnter = (e)=>{
console.log(e.target.value)
alert('提交成功')
}
</script>
总结:
- 事件绑定用 v-on:事件类型 或简写 @事件类型。
- 事件处理函数可以接收事件对象
七、v-model:表单输入双向绑定
在处理表单时,我们常常需要将表单元素的值与响应式数据双向绑定,即数据变化时表单更新,表单输入时数据也同步更新。v-model 就是用于实现这种双向绑定的指令。
7.1 v-model 双向数据绑定
v-model 可以在表单元素(如输入框、复选框、下拉框等)上创建双向数据绑定,它会根据表单元素的类型自动选择合适的绑定方式。
问题:如何用 v-model 实现输入框与数据的双向绑定,使得输入内容实时同步到数据,且数据修改时输入框也随之更新?
<template>
<div>
<input type="text" v-model="message">
<p>你输入的是:{{message}}</p>
<button @click="clear">清空</button>
</div>
</template>
<script setup>
import {ref} from "vue";
let message = ref('')
function clear() {
message.value = ''
}
</script>
说明:
- v-model 本质上是语法糖,它会根据表单元素类型绑定 value 属性和 input 事件(或其他对应事件)。
- 对于不同的表单元素,v-model 的绑定逻辑不同:
- 文本框 / 文本域:绑定 value 和 input 事件
- 复选框:绑定 checked 和 change 事件
- 单选按钮:绑定 checked 和 change 事件
- 下拉框:绑定 value 和 change 事件
7.2 三种方法阻止form表单的默认提交
当表单使用 submit 按钮提交时,默认会触发页面刷新,这在单页应用中通常是不需要的。以下是三种阻止表单默认提交行为的方法:
方法一:使用 @submit.prevent 修饰符(推荐)
<template>
<form @submit.prevent="handleSubmit">
<input type="text" v-model="username">
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const handleSubmit = () => {
console.log('提交的用户名:', username.value)
// 自定义提交逻辑(如接口请求)
}
</script>
方法二:在事件处理函数中调用 e.preventDefault()
<template>
<form @submit="handleSubmit">
<input type="text" v-model="username">
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const handleSubmit = (e) => {
e.preventDefault() // 阻止默认行为
console.log('提交的用户名:', username.value)
}
</script>
方法三:将按钮类型改为 button,手动触发提交
<template>
<form>
<input type="text" v-model="username">
<button type="button" @click="handleSubmit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const handleSubmit = () => {
console.log('提交的用户名:', username.value)
}
</script>
总结:
- @submit.prevent 是最简洁的方法,直接在模板中通过修饰符阻止默认行为。
- 方法二适合需要在阻止默认行为前执行额外逻辑的场景。
- 方法三通过改变按钮类型避免触发表单提交,适合完全自定义提交时机的场景。
八、Class 与 Style绑定
在 Vue 中,我们可以通过 v-bind 动态绑定元素的 class 和 style,实现样式的动态变化。
8.1 Class绑定
动态绑定 class 时,可以使用对象、数组或字符串形式,根据条件切换类名。
问题:如何根据不同的条件为元素动态添加或移除类名(如激活状态、错误状态)?
方式一:对象语法(根据条件添加类名)
基本语法:
:class = "{变量名2:布尔值变量1,变量名1:布尔值变量2.......}"
<template>
<h1 :class="{active:isActive,error:hasError}">我是对象型语法的class绑定</h1>
<button @click ="changeActive">点我激活</button>
<button @click = changeError>点我失活</button>
</template>
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
function changeActive(){
isActive.value = !isActive.value
}
function changeError(){
hasError.value = !hasError.value
}
</script>
<style>
.active{
color:red;
border: #3a5fc6 1px solid;
}
.error{
background-color: burlywood;
}
</style>
方式二:数组语法(列表形式添加类名)
基本语法:
:class = "[变量1,变量2.......]"(注意:变量绑定是属性的类名,可以通过改变类名来改变样式)
<template>
<div :class="[baseClass, activeClass]">
数组形式Class绑定
</div>
<button @click="activeClass = activeClass === 'active' ? '' : 'active'">改变动态的样式</button>
</template>
<script setup>
import { ref } from 'vue'
const baseClass = ref('box')
const activeClass = ref('active')
</script>
<style>
.box {
color: red;
}
.active {
background-color: blue;
}
</style>
点击按钮后:
方式三:数组 + 对象结合(条件性添加数组中的类名)
<template>
<!-- 数组类型 对象类型的class绑定 -->
<div :class="[baseClass, isActive ? activeClass : '']">
混合形式Class绑定
</div>
<button @click="changeActive">点击切换成动态样式</button>
</template>
<script setup>
import { ref } from 'vue'
const baseClass = ref('box')
const activeClass = ref('active')
const isActive = ref(true) // 设置默认值为true
function changeActive(){
isActive.value = !isActive.value
}
</script>
<style>
.box{
background-color: burlywood;
}
.active{
color:red;
border: #3a5fc6 1px solid;
}
</style>
点击切换动态的样式:
8.2 Style 绑定
动态绑定 style 时,同样可以使用对象或数组形式,直接设置内联样式。
问题:如何根据响应式数据动态修改元素的内联样式(如颜色、字体大小)?
方式一:对象语法(键为 CSS 属性,值为样式值)
基本语法:
:style = "{属性1:变量1,属性2:变量2.......}
<template>
<div :style="{ color: textColor, fontSize: fontSize + 'px' }">
动态Style示例
</div>
<button @click="changeStyle">改变样式</button>
</template>
<script setup>
import { ref } from 'vue'
const textColor = ref('black')
const fontSize = ref(16)
const changeStyle = () => {
textColor.value = 'red'
fontSize.value = 20
}
</script>
方式二:数组语法(多个样式对象合并)
<template>
<div :style="[baseStyle, activeStyle]">
数组形式Style绑定
</div>
</template>
<script setup>
import { ref } from 'vue'
const baseStyle = ref({
padding: '10px',
border: '1px solid #ccc'
})
const activeStyle = ref({
backgroundColor: 'yellow'
})
</script>
总结:
- class 绑定适合通过预定义的类名切换样式,维护性更好。
- style 绑定适合直接动态设置具体样式值,灵活性更高。
- 两者都支持对象和数组语法,可根据场景选择使用。
九、条件渲染
在开发中,我们经常需要根据条件显示或隐藏元素,Vue 提供了 v-if 和 v-show 两种指令实现条件渲染。
9.1 v-if / v-else-if / v-else
v-if 用于根据条件渲染元素,条件为 false 时,元素会被从 DOM 中移除;v-else-if 和 v-else 用于搭配 v-if 实现多条件判断。
问题:如何根据用户角色(管理员、普通用户、游客)显示不同的内容?
<template>
<div>
<div v-if="role === 'admin'">
<h3>管理员面板</h3>
<p>拥有所有权限</p>
</div>
<div v-else-if="role === 'user'">
<h3>用户面板</h3>
<p>拥有部分权限</p>
</div>
<div v-else>
<h3>访客面板</h3>
<p>请先登录查看更多权限</p>
</div>
<button @click="changeRole">切换角色</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const role = ref("admin")
function changeRole() {
if(role.value === "admin"){
role.value = "user"
}
else if(role.value === "user"){
role.value = "guest"
}
else{
role.value = "admin"
}
}
</script>
说明:
- v-else-if 和 v-else 必须紧跟在 v-if 或 v-else-if 之后,否则无法识别。
- 当条件切换时,v-if 会销毁和重建元素,因此其内部的组件也会经历销毁和创建的生命周期。
9.2 v-show
v-show 也用于根据条件显示元素,但它不会移除元素,而是通过设置 display: none 来隐藏元素。
问题:如何实现一个可以显示 / 隐藏的提示框,且切换时性能更好?
<template>
<div>
<div v-show="isVisible" class="alert">
这是一个提示框
</div>
<button @click="toggleVisible">显示/隐藏</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
const toggleVisible = () => {
isVisible.value = !isVisible.value
}
</script>
说明:
- v-show 不支持 v-else,也不能用于 <template> 标签。
- 元素始终存在于 DOM 中,只是通过 CSS 控制显示 / 隐藏。
9.3 v-if和v-show的区别
特性 | v-if | v-show |
---|---|---|
渲染方式 | 条件为 false 时移除 DOM 元素 | 条件为 false 时设置 display: none |
初始渲染成本 | 条件为 false 时,无初始渲染成本 | 无论条件如何,都有初始渲染成本 |
切换成本 | 较高(涉及 DOM 操作) | 较低(仅修改 CSS) |
适用场景 | 条件很少切换的场景 | 条件频繁切换的场景 |
其他 | 支持 v-else |
不支持 v-else |
十、v-for列表渲染
v-for 用于基于数组或对象渲染列表,它可以遍历数组、对象、字符串或数字。
问题:如何遍历一个商品列表,并显示每个商品的名称和价格?
<template>
<div>
<h3>商品列表</h3>
<ul>
<li v-for="(product, index) in products" :key="product.id">
{{ index + 1 }}. {{ product.name }} - ¥{{ product.price }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const products = ref([
{ id: 1, name: '手机', price: 3999 },
{ id: 2, name: '电脑', price: 5999 },
{ id: 3, name: '平板', price: 2999 }
])
</script>
说明:
- v-for 的语法为 (item, index) in 数组,index 为可选的索引值。
- 必须为 v-for 渲染的元素添加 :key 属性,且 key 的值需唯一(通常使用数据的唯一标识,如 id),目的是帮助 Vue 识别元素的身份,提高列表渲染性能。
遍历对象:
<template>
<div>
<h3>用户信息</h3>
<ul>
<!--三个参数: 值 键名 下标(0开始) -->
<li v-for="(value, key, index) in user" :key="key">
{{index + 1}}.{{ key }}: {{ value }}
</li>
<!--两个参数: 值 键名 -->
<li v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</li>
<!--一个参数: 值 -->
<li v-for="(value) in user" :key="key">
{{ value }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const user = ref({
name: '张三',
age: 20,
gender: '男'
})
</script>
注意事项:
- 不要在同一元素上同时使用 v-if 和 v-for(v-for 的优先级更高,可能导致逻辑混乱)。
- 当数组发生变化时,Vue 会通过 “就地更新” 的策略更新列表,只重新渲染变化的元素,提高性能。
十一、watch监听器
在 Vue 中,当我们需要在数据变化时执行一些副作用操作(如日志记录、数据请求等),可以使用 watch 监听器。它能监听指定的响应式数据,并在数据变化时触发回调函数。
11.1 情况一:监视ref定义的基本类型数据
基础语法:
watch(监听的数据, (新值, 老值) => { 函数体 })
<template>
<div>
<p>计数器:{{ count }}</p>
<button @click="count++">+1</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
// 监视ref定义的基本类型数据
watch(count, (newValue, oldValue) => {
console.log(`count变化了:旧值${oldValue},新值${newValue}`)
})
</script>
- watch 的第一个参数是要监听的目标(这里是 count)。
- 第二个参数是回调函数,接收两个参数:newValue(变化后的值)和 oldValue(变化前的值)。
11.2 情况二:监视ref定义的对象类型数据
基础语法:
watch(监听的对象, (新值, 老值) => { 函数体 }, { deep: true })
当 ref 包裹的是对象类型数据时,watch 默认不会深度监听对象内部属性的变化,需要通过 deep: true 开启深度监听。
问题:如何监听 ref 定义的对象类型数据中某个属性的变化?
<template>
<div>
<p>用户信息:{{ user }}</p>
<button @click="user.age++">年龄+1</button>
<button @click="user.name = '李四'">修改姓名</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const user = ref({
name: '张三',
age: 20
})
// 监视ref定义的对象类型数据(默认浅监听,对象内部属性变化不会触发)
// 需要添加deep: true开启深度监听
watch(user, (newValue, oldValue) => {
console.log('user变化了', newValue, oldValue)
}, { deep: true })
</script>
注意:
- 对于 ref 定义的对象类型数据,watch 监听的是对象的引用变化。若要监听对象内部属性变化,必须设置 deep: true。
- 开启深度监听后,对象任何深层属性变化都会触发回调,但 oldValue 可能不准确(与 newValue 指向同一对象)。
11.3 情况三:监视reactive定义的对象类型数据
基础语法:
watch((监听的对象, (新值, 老值) =>
{ 函数体
})
reactive 定义的对象类型数据,watch 默认会进行深度监听,无需手动设置 deep: true。
问题:如何监听 reactive 定义的对象类型数据的变化?
<template>
<div>
<p>用户信息:{{ user }}</p>
<button @click="user.age++">年龄+1</button>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: '张三',
age: 20
})
// 监视reactive定义的对象类型数据(默认深度监听)
watch(user, (newValue, oldValue) => {
console.log('user变化了', newValue, oldValue)
})
</script>
说明:
- reactive 定义的对象,watch 会自动深度监听,任何属性变化都会触发回调。
- 此时 oldValue 同样可能不准确,因为对象是引用类型。
11.4 情况四:监视对象中的某个属性
基础语法:
watch(对象,属性, (新值, 老值) =>
{ 函数体
})
当只需要监听对象中的某个特定属性时,可以通过函数形式指定要监听的属性。
问题:如何只监听对象中 age 属性的变化,忽略其他属性?
<template>
<div>
<p>用户信息:{{ user }}</p>
<button @click="user.age++">年龄+1</button>
<button @click="user.name = '李四'">修改姓名</button>
</div>
</template>
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: '张三',
age: 20
})
// 监视对象中的某个属性(用函数返回要监听的属性)
watch(() => user.age, (newValue, oldValue) => {
console.log(`age变化了:旧值${oldValue},新值${newValue}`)
})
</script>
说明:
- 第一个参数是一个函数,返回要监听的具体属性(user.age)。
- 只有该属性变化时,才会触发回调,其他属性变化不会影响。
11.5 情况五:监视多个数据
基础语法:
watch([监听数据1,监听数据2....] ,([监听数据1的新值,监听数据2的新值],[监听数据1的旧值,监听数据2的旧值]) =>
{
函数体
})
watch 可以同时监听多个数据,当其中任何一个数据变化时,都会触发回调。
问题:如何同时监听 count 和 user.age 的变化?
<template>
<div>
<p>计数器:{{ count }}</p>
<p>年龄:{{ user.age }}</p>
<button @click="count++">count+1</button>
<button @click="user.age++">age+1</button>
</div>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
const count = ref(0)
const user = reactive({
age: 20,
name: '张三',
})
// 监视多个数据(数组形式)
watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => {
console.log(`count变化:${oldCount}→${newCount}`)
console.log(`age变化:${oldAge}→${newAge}`)
})
</script>
总结:
- watch 适用于需要知道数据变化前后值的场景,或需要执行异步操作的场景。
- 根据监听目标的类型(基本类型 / 对象类型、ref/reactive),需选择不同的监听方式,必要时开启深度监听。
十二、watchEffect
watchEffect 是另一种监听响应式数据变化的方式,它不需要明确指定监听的目标,会自动追踪回调函数中使用的响应式数据,当这些数据变化时,回调函数会重新执行。
问题:如何实现当 count 或 user.age 变化时,自动打印它们的当前值,而无需手动指定监听目标?
<template>
<div>
<p>计数器:{{ count }}</p>
<p>年龄:{{ user.age }}</p>
<button @click="count++">count+1</button>
<button @click="user.age++">age+1</button>
</div>
</template>
<script setup>
import { ref, reactive, watchEffect } from 'vue'
const count = ref(0)
const user = reactive({
age: 20
})
// watchEffect自动追踪回调中使用的响应式数据
watchEffect(() => {
console.log(`当前count: ${count.value}, 当前age: ${user.age}`)
})
</script>
与 watch 的区别:
特性 |
watch |
watchEffect |
监听目标 |
需要明确指定 |
自动追踪回调中使用的数据 |
新旧值 |
可以获取新旧值 |
只能获取当前值 |
执行时机 |
数据变化时执行 |
初始化时会执行一次(收集依赖),之后数据变化时执行 |
适用场景 |
需要知道数据变化前后值的场景 |
只需根据数据变化执行副作用,无需关心旧值的场景 |
十三、生命周期
Vue 组件从创建到销毁的过程中,会经历一系列特定的阶段,这些阶段被称为生命周期。Vue 提供了生命周期钩子函数,允许我们在这些阶段执行自定义逻辑。
常见的生命周期钩子:
钩子函数 |
说明 |
onMounted |
组件挂载到 DOM 后执行 |
onUpdated |
组件更新后执行(DOM 重新渲染后) |
onUnmounted |
组件从 DOM 中卸载后执行 |
官网晚上的生命周期函数(地址):
问题:如何在组件挂载后请求数据,更新时打印日志,卸载前清理定时器?
<template>
<div>
<p>{{ data }}</p>
<button @click="count++">更新组件</button>
</div>
</template>
<script setup>
// onMounted 组件挂载到 DOM 后执行
// onUpdated 组件更新后执行(DOM 重新渲染后)
// onUnmounted 组件从 DOM 中卸载后执行
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
const data = ref('加载中...')
const count = ref(0)
let timer = null
// 组件挂载后:请求数据、开启定时器
onMounted(() => {
console.log('组件挂载完成')
// 模拟数据请求
setTimeout(() => {
data.value = '请求到的数据'
}, 1000)
// 开启定时器
timer = setInterval(() => {
console.log('定时器运行中...')
}, 1000)
})
// 组件更新后:打印日志
onUpdated(() => {
console.log('组件更新完成')
})
// 组件卸载前:清理定时器
onUnmounted(() => {
console.log('组件即将卸载')
clearInterval(timer)
})
</script>
没有挂载:
挂载之后:
没有更新和卸载(不会执行onUpdated和onUnmounted):
说明:
- onMounted 常用于初始化操作(如数据请求、事件监听)。
- onUpdated 可用于在组件更新后执行依赖 DOM 的操作,但应避免在此修改数据(可能导致无限循环)。
- onUnmounted 用于清理资源(如定时器、事件监听),防止内存泄漏。
十四、自定义 hooks
在 Vue 开发中,我们经常会遇到一些重复的逻辑(比如表单处理、数据请求、定时器管理等)。如果每个组件都单独实现这些逻辑,不仅代码冗余,还会增加维护成本。这时候,自定义 hooks 就派上用场了。
以下就通过结合一个防抖来示例:
useDebounce.ts
import {ref} from 'vue'
// 导入Vue的reactive函数,用于创建响应式数据
import { reactive } from "vue";
// 导入axios库,用于发送HTTP请求
import axios from "axios";
// 创建一个响应式的字符串数组,用于存储狗狗图片的URL
let dogList = reactive<string[]>([])
async function getDog() {
// 发送GET请求获取随机狗狗图片的URL
let res = await axios.get("https://dog.ceo/api/breed/pembroke/images/random")
// 将获取到的图片URL添加到dogList数组中
dogList.push(res.data.message)
}
function useDebounce<T>(callback: () => void, delay: number) {
// 使用 Vue 的 ref 函数创建一个响应式的计时器变量
const timer = ref<number | null>(null)
// 返回一个函数,当其被调用时,将执行防抖动逻辑
const fd= () => {
// 如果计时器存在,则清除之前的计时器,以确保在延迟时间内只执行一次回调
if (timer.value) {
console.log("clear当前的计时器",timer.value)
// 根据id清除计时器
clearTimeout(timer.value)
}
// 设置一个新的计时器,如果在延迟时间后没有再次调用此函数,则执行回调
timer.value = setTimeout(() => {
callback() // 执行getDog()
}, delay)
}
return fd
}
// 导出dogList和getDog函数,供其他模块使用
export {
dogList,
getDog,
useDebounce
}
useDebounce.vue
<script setup lang="ts">
// 导入自定义的防抖钩子函数,用于在指定时间内部分更新函数的执行
import { useDebounce,dogList, getDog } from "@/hooks/useDebounce2"
// 定义防抖动的延迟时间,单位为毫秒,此处设置为2秒
const debounceDelay = 2000
// 使用防抖钩子函数封装获取狗狗图片的函数
// 这样可以避免在用户频繁点击按钮时发送过多的请求
// 函数将在最后一次调用后的 debounceDelay 毫秒后执行
const debouncedGetDog = useDebounce(() => {
getDog()
}, debounceDelay)
</script>
<template>
<div class="hooksdemo">
<h2>useDebounce 示例2</h2>
<!-- 使用 v-for 指令循环渲染狗狗图片 -->
<img v-for="(u, index) in dogList" :key="index" :src="u" alt="Dog Image">
<!-- 当用户点击按钮时,将调用 debouncedGetDog 函数 -->
<!-- 防抖技术确保在频繁点击时不会发送过多的请求 -->
<button @click="debouncedGetDog">再来一只狗(防抖)</button>
</div>
</template>
App.vue
<script setup lang="ts">
// import one from "./components/one.vue";
// import two from "./components/two.vue";
import useDebounce from "@/components/useDebounce.vue";
</script>
<template>
<!-- <two></two>-->
<!-- <hr>-->
<!-- <br>-->
<!--<!– <one></one>–>-->
<useDebounce></useDebounce>
</template>