从 0-1 再学一次vue
注意:本文大部分内容和实例选自 vue官网
1. 什么是 Vue?
Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
2.使用vite创建一个vue项目
如果初学者开篇去学习一个框架的理论,一定是会被劝退,so,我希望可以从实例去学习一个框架,熟练掌握使用之后再去了解框架原理。vite官网
命令行输入cnpm create vite@latest vue-demo1 -- --template vue (你可以使用npm,yarn等)
cnpm create vite@latest vue-demo1 -- --template vue
Need to install the following packages:
create-vite@5.5.3
Ok to proceed? (y) y
√ Select a framework: » Vue
√ Select a variant: » JavaScript
Scaffolding project in C:\Users\GA\Desktop\vue3-project\vue-demo1...
Done. Now run:
cd vue-demo1
npm install
npm run dev
npm notice
npm notice New major version of npm available! 9.9.2 -> 10.9.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.9.0
npm notice Run npm install -g npm@10.9.0 to update!
npm notice
到这一步项目创建完成
2.1 项目目录
2.2 项目的入口文件是main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
App是vue的一个组件,引入App这个组件,并且挂着在id='#app'的div上,就可以在浏览器输出对应的页面。
2.2.1 App组件展示
<script setup>
</script>
<template>
hello vue3
</template>
<style scoped>
</style>
2.2.2 index.html
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue3</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
main.js 中vue挂载的就是 createApp(App).mount('#app') 这个 <div id="app"></div> dom元素。
2.2.3 浏览器展示
首先使用cnpm install 安装项目依赖
使用npm run dev 本地启动项目
成功完成了最基本的项目搭建运行。
3.Vue的响应式基础
vue的两大核心之一——响应式
3.1 ref
在组合式 API 中,推荐使用 ref() 函数来声明响应式状态:
<script setup>
import { ref } from 'vue';
const count = ref(0); // 使用ref声明一个响应式的数据
console.log(count.value); //0 此处需要使用.value的操作获取到数据
</script>
<template>
<h1>Hello Vue3</h1>
<button>{{ count }}</button> // 在模版中我们可以直接使用这个数据
</template>
<style scoped></style>
我们可以添加方法修改count的值
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = (type) =>{
if(type == 1){ // type等于1时数据增加
count.value++;
}else{
count.value--;
}
console.log(count.value);
}
</script>
<template>
<h1>Hello Vue3</h1>
<button @click="increment(0)">-</button>
<button>{{ count }}</button>
<button @click="increment(1)">+</button>
</template>
<style scoped></style>
响应式数据就是这样方便。
3.1.1 问题:为什么要使用 ref ?
你可能会好奇:为什么我们需要使用带有 .value 的 ref,而不是普通的变量?为了解释这一点,我们需要简单地讨论一下 Vue 的响应式系统是如何工作的。
当你在模板中使用了一个 ref,然后改变了这个 ref 的值时,Vue 会自动检测到这个变化,并且相应地更新 DOM。这是通过一个基于依赖追踪的响应式系统实现的。当一个组件首次渲染时,Vue 会追踪在渲染过程中使用的每一个 ref。然后,当一个 ref 被修改时,它会触发追踪它的组件的一次重新渲染。
在标准的 JavaScript 中,检测普通变量的访问或修改是行不通的。然而,我们可以通过 getter 和 setter 方法来拦截对象属性的 get 和 set 操作。
该 .value 属性给予了 Vue 一个机会来检测 ref 何时被访问或修改。在其内部,Vue 在它的 getter 中执行追踪,在它的 setter 中执行触发。从概念上讲,你可以将 ref 看作是一个像这样的对象:
// 伪代码,不是真正的实现
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
} }
如果你对 JavaScript对象 了解不够深入,可能不会理解其中的原理,但是这并不要紧,你现在已经学会了vue3的相应式数据的使用。
3.1.2 JavaScript对象的 setter和getter方法的使用;
JavaScript对象有两种类型的属性
- 第一种是 数据属性,这个比较常用,之前我们使用对象的方式大多都是这样。
- 是 访问器属性(accessor property) 。它们本质上是用于获取和设置值的函数,但从外部代码来看就像常规属性。
getter 和 setter
访问器属性由 “getter” 和 “setter” 方法表示。在对象字面量中,它们用 get 和 set 表示:
let obj = {
name:'小美',
age:16,
get value(){
return `我的名字是${this.name}, 今年${this.age}岁`; // 外部使用obj.value 时调用getter
},
set value(val){
this.age = val; // 外部使用obj.value = "xxx" 时调用setter
}
}
console.log(obj.value) // 我的名字是小美, 今年16岁
obj.value = 100;
console.log(obj.value) // 我的名字是小美, 今年100岁
我们可以通过绑定存取器来对对象属性读取或者修改操作进行拦截,在其中进行一些操作。
3.1.3 深层响应性
Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:
<script setup>
import { ref } from 'vue';
const teacher = ref({
info:{
age:18
},
hobby:['唱歌','跳舞']
});
teacher.value.info.age = 20;
teacher.value.hobby.push('看剧');
console.log(teacher.value) // {hobby:['唱歌', '跳舞', '看剧'],info:{age:20}}
</script>
3.2 DOM更新的时期(nextTick的作用)
当你修改了响应式数据的时候,DOM会自动更新,但是这个更新并不是立即更新,而是异步的过程。 Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。
要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:
3.2.1 nextTick使用场景
- 获取更新后的 DOM 元素
- 更新后的样式计算
- 触发一些特定事件
实例一:获取当前dom的值
<script setup>
import { nextTick, ref } from 'vue';
const count = ref(0);
let countRef = ref(null);
const increment = (type) => {
if (type == 1) { // type等于1时数据增加
count.value++;
} else {
count.value--;
}
console.log('before', countRef.value.innerText);
nextTick(() => {
console.log('after', countRef.value.innerText);
})
}
</script>
<template>
<h1>nextTick</h1>
<button @click="increment(0)">-</button>
<button ref="countRef">{{ count }}</button>
<button @click="increment(1)">+</button>
</template>
<style scoped></style>
结果:
场景二:异步数据获取dom相关的高度 父组件parent.vue中
<template>
<div>
<h1>nextTick</h1>
<list :listData="listData" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import list from './list.vue';
const listData = ref<Array<number>>([]) ;
setTimeout(()=>{
// 模仿网络请求
listData.value = Array.from({length:10}, (_, i) => i + 1);
},1000)
</script>
<style scoped></style>
子组件list.vue中接受listData,不使用nextTick
<template>
<div>
<ul ref="listRef">
<li v-for="item in listData" :key="item"
style="height: 50px; border-bottom: 1px solid lightcoral;color: black;">
{{ item }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue';
const listRef = ref(null);
const props = defineProps({
listData: {
type: Array<number>,
default: []
}
})
// 我们需要获取list组件的高度
watch(()=>props.listData,(val1, val2)=>{
//注意这里使用了函数 () => props.listData 来确保 watch 能够正确地追踪 props.listData 的变化。这是因为直接监听 props.listData 可能不会触发更新,因为 props 对象本身不是响应式的,而是它的属性(如 listData)是响应式的
handleGetHeight();
});
const handleGetHeight = () =>{
console.log(listRef.value.clientHeight) // list组件的高度
}
</script>
<style scoped></style>
结果
使用nextTick
<template>
<div>
<ul ref="listRef">
<li v-for="item in listData" :key="item"
style="height: 50px; border-bottom: 1px solid lightcoral;color: black;">
{{ item }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue';
const listRef = ref(null);
const props = defineProps({
listData: {
type: Array<number>,
default: []
}
})
// 我们需要获取list组件的高度
watch(()=>props.listData,(val1, val2)=>{
//注意这里使用了函数 () => props.listData 来确保 watch 能够正确地追踪 props.listData 的变化。这是因为直接监听 props.listData 可能不会触发更新,因为 props 对象本身不是响应式的,而是它的属性(如 listData)是响应式的
nextTick(()=>{
handleGetHeight();
})
});
const handleGetHeight = () =>{
console.log(listRef.value.clientHeight) // list组件的高度
}
</script>
<style scoped></style>
结果:可以拿到dom的高度
3.3 reactive()
还有另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:
reactive可以将复杂数据类型(对象,数组,map,set等)转换为响应式数据
<template>
<div>
<h1>ref 和 reactive的区别</h1>
<ul>
<li>{{ user.name }}</li>
<li>{{ user.age }}</li>
<li>{{ user.hobby }}</li>
</ul>
<button @click="handleChange">点击修改</button>
<button @click="handleReplace">点击替代</button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
let user = reactive({
name: '小刚',
age: 18,
hobby: [
'吃饭', '睡觉'
]
});
console.log('user', user)
const handleChange = () => {
user.age = 20;
user.hobby.push('打豆豆');
console.log('user', user)
}
const handleReplace = () => {
user = {
name: '小美',
age: 14,
hobby: [
'看电影', '睡觉'
]
}
console.log('user', user)
}
</script>
<style scoped></style>
当我点击修改,数据成功修改
当我点击替代,无反应。
3.3.1 reactive() 的局限性
根据以上的例子也可以得出
- 有限的值类型:它只能用于对象类型 (对象、数组和如
Map、Set这样的集合类型)。它不能持有如string、number或boolean这样的原始类型。 - 不能替换整个对象:由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失:
- 对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。
3.3.2 ref 和 reactive 的区别
| 特性 | ref | reactive |
|---|---|---|
| 适用场景 | 基本数据类型、DOM引用、复杂类型封装 | 对象、数组、嵌套数据 |
| 访问方式 | 必须通过 .value 来访问和修改值 | 直接通过对象属性访问和修改 |
| 响应式行为 | 对于对象或数组,只有包裹的整体是响应式的,内部属性需要手动用 ref | 深度响应式,对所有嵌套属性进行监听 |
| 能否解构 | ref 可以解构(解构后仍然需 .value) | 解构后失去响应式能力 |
4.计算属性
什么是计算属性?其实你也可以把他理解为一个响应式数据,不过是经过一系列操作变形的响应式数据。 模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象:
js
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
我们想根据 author 是否已有一些书籍来展示不同的信息:
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于 author.books。更重要的是,如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍。
因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑。这是重构后的示例:
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
我们在这里定义了一个计算属性 publishedBooksMessage。computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value。
Vue 的计算属性会自动追踪响应式依赖。它会检测到 publishedBooksMessage 依赖于 author.books,所以当 author.books 改变时,任何依赖于 publishedBooksMessage 的绑定都会同时更新。
4.1 计算属性缓存 vs 方法
你可能注意到我们在表达式中像这样调用一个函数也会获得和计算属性相同的结果:
<p>{{ calculateBooksMessage() }}</p>
// 组件中
function calculateBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No'
}
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。
这也解释了为什么下面的计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖:
const now = computed(() => Date.now())
相比之下,方法调用总是会在重渲染发生时再次执行函数。
为什么需要缓存呢?想象一下我们有一个非常耗性能的计算属性 list,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list。没有缓存的话,我们会重复执行非常多次 list 的 getter,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。
5.Class 与 Style 绑定
数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 class 和 style 都是 attribute,我们可以和其他 attribute 一样使用 v-bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 class 和 style 的 v-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
绑定 HTML class
绑定对象
我们可以给 :class (v-bind:class 的缩写) 传递一个对象来动态切换 class:
例子如下:(无法上传视频,没有转换gif,将就看把....)
<script setup lang="ts">
import {ref} from "vue";
const isActive = ref(true);
const handleChangeColor = () =>{
isActive.value = !isActive.value
}
</script>
<template>
<div :class="{red: isActive}" class="bold">
这里有一个苹果
</div>
<div :class="{red: !isActive}">
这里有一个西瓜
</div>
<div>
<button @click="handleChangeColor">修改颜色</button>
<button>修改样式</button>
</div>
</template>
<style scoped>
.red{
color: lightcoral;
}
.blue{
color: lightblue;
}
.bold{
font-weight: 600;
}
</style>
绑定的对象并不一定需要写成内联字面量的形式,也可以直接绑定一个对象:
<script setup lang="ts">
import {reactive, ref} from "vue";
const isActive = ref(true);
const styles = reactive({
blue:true,
fz:true
})
const handleChangeColor = () =>{
isActive.value = !isActive.value
}
</script>
<template>
<div :class="{red: isActive}" class="bold">
这里有一个苹果
</div>
<div :class="{red: !isActive}">
这里有一个橘子
</div>
<div :class="styles">
这里有一个大西瓜
</div>
<div>
<button @click="handleChangeColor">修改颜色</button>
<button>修改样式</button>
</div>
</template>
<style scoped>
.red{
color: lightcoral;
}
.blue{
color: lightblue;
}
.fz{
font-size: 40px;
}
.bold{
font-weight: 600;
}
</style>
我们也可以绑定一个返回对象的计算属性。这是一个常见且很有用的技巧(当样式需要经过许多逻辑处理):
例子如下:
<script setup lang="ts">
import {computed, reactive, ref} from "vue";
const isActive = ref(true);
const isShow = ref(false);
const styles = reactive({
blue:true,
fz:true
})
const handleChangeColor = () =>{
isActive.value = !isActive.value
}
const handleChangeStyle = () =>{
isShow.value = true;
}
// 计算属性
const styleComputed = computed(()=>{
console.log('isShow && isActive', isShow && isActive)
return {
display:isShow.value && isActive.value,
fz:isActive.value,
blue:isActive.value
}
})
</script>
<template>
<div :class="{red: isActive}" class="bold">
这里有一个苹果
</div>
<div :class="{red: !isActive}">
这里有一个橘子
</div>
<div :class="styleComputed" style="display: none">
这里有一个大西瓜
</div>
<div>
<button @click="handleChangeColor">修改颜色</button>
<button @click="handleChangeStyle">修改样式</button>
</div>
</template>
<style scoped>
.red{
color: lightcoral;
}
.blue{
color: lightblue;
}
.fz{
font-size: 40px;
}
.bold{
font-weight: 600;
}
.display {
display: block !important;
}
</style>
绑定数组
我们可以给 :class 绑定一个数组来渲染多个 CSS class:
js
const activeClass = ref('active')
const errorClass = ref('text-danger')
template
<div :class="[activeClass, errorClass]"></div>
渲染的结果是:
template
<div class="active text-danger"></div>
如果你也想在数组中有条件地渲染某个 class,你可以使用三元表达式:
template
<div :class="[isActive ? activeClass : '', errorClass]"></div>
errorClass 会一直存在,但 activeClass 只会在 isActive 为真时才存在。
然而,这可能在有多个依赖条件的 class 时会有些冗长。因此也可以在数组中嵌套对象:
template
<div :class="[{ [activeClass]: isActive }, errorClass]"></div>
在组件上使用
本节假设你已经有 Vue 组件的知识基础。如果没有,你也可以暂时跳过,以后再阅读。
对于只有一个根元素的组件,当你使用了 class attribute 时,这些 class 会被添加到根元素上并与该元素上已有的 class 合并。
举例来说,如果你声明了一个组件名叫 MyComponent,模板如下:
template
<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>
在使用时添加一些 class:
template
<!-- 在使用组件时 -->
<MyComponent class="baz boo" />
渲染出的 HTML 为:
template
<p class="foo bar baz boo">Hi!</p>
Class 的绑定也是同样的:
template
<MyComponent :class="{ active: isActive }" />
当 isActive 为真时,被渲染的 HTML 会是:
template
<p class="foo bar active">Hi!</p>
如果你的组件有多个根元素,你将需要指定哪个根元素来接收这个 class。你可以通过组件的 $attrs 属性来指定接收的元素:
template
<!-- MyComponent 模板使用 $attrs 时 -->
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
template
<MyComponent class="baz" />
这将被渲染为:
html
<p class="baz">Hi!</p>
<span>This is a child component</span>
你可以在透传 Attribute 一章中了解更多组件的 attribute 继承的细节。
绑定内联样式
绑定对象
:style 支持绑定 JavaScript 对象值,对应的是 HTML 元素的 style 属性:
js
const activeColor = ref('red')
const fontSize = ref(30)
template
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
尽管推荐使用 camelCase,但 :style 也支持 kebab-cased 形式的 CSS 属性 key (对应其 CSS 中的实际名称),例如:
template
<div :style="{ 'font-size': fontSize + 'px' }"></div>
直接绑定一个样式对象通常是一个好主意,这样可以使模板更加简洁:
js
const styleObject = reactive({
color: 'red',
fontSize: '30px'
})
template
<div :style="styleObject"></div>
同样的,如果样式对象需要更复杂的逻辑,也可以使用返回样式对象的计算属性。
绑定数组
我们还可以给 :style 绑定一个包含多个样式对象的数组。这些对象会被合并后渲染到同一元素上:
template
<div :style="[baseStyles, overridingStyles]"></div>
自动前缀
当你在 :style 中使用了需要浏览器特殊前缀的 CSS 属性时,Vue 会自动为他们加上相应的前缀。Vue 是在运行时检查该属性是否支持在当前浏览器中使用。如果浏览器不支持某个属性,那么将尝试加上各个浏览器特殊前缀,以找到哪一个是被支持的。
样式多值
你可以对一个样式属性提供多个 (不同前缀的) 值,举例来说:
template
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex。
6.条件渲染
v-if
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
template
<h1 v-if="awesome">Vue is awesome!</h1>
v-else
你也可以使用 v-else 为 v-if 添加一个“else 区块”。
template
<button @click="awesome = !awesome">Toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
Toggle
Vue is awesome!
一个 v-else 元素必须跟在一个 v-if 或者 v-else-if 元素后面,否则它将不会被识别。
v-else-if
顾名思义,v-else-if 提供的是相应于 v-if 的“else if 区块”。它可以连续多次重复使用:
template
<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-else 类似,一个使用 v-else-if 的元素必须紧跟在一个 v-if 或一个 v-else-if 元素后面。
<template> 上的 v-if
因为 v-if 是一个指令,他必须依附于某个元素。但如果我们想要切换不止一个元素呢?在这种情况下我们可以在一个 <template> 元素上使用 v-if,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素。
template
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
v-else 和 v-else-if 也可以在 <template> 上使用。
v-show
另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样:
template
<h1 v-show="ok">Hello!</h1>
不同之处在于 v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性。
v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。
v-if vs. v-show
v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
v-if 和 v-for
警告
同时使用 v-if 和 v-for 是不推荐的,因为这样二者的优先级不明显。请查看风格指南获得更多信息。
当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行。请查看列表渲染指南获取更多细节。
侦听器
基本示例
计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。
在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数:
<script setup lang="ts">
import {ref, watch} from "vue";
// 列一
const question = ref('');
const answer = ref('Questions usually contain a question mark. ;-)')
const loading = ref(false);
// 监听源必须是响应式数据--> 一个ref
watch(question,async (newVal, oldVal)=>{
//newVal 是这个最新数据改变的值,oldVal是最近一次改变的值
if(newVal.includes('?')){
loading.value = true;
answer.value = 'Thinking.....';
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
} finally {
loading.value = false
}
}
})
</script>
<template>
<h1>vue3 侦听器</h1>
<hr>
<div>
<h2>例一:</h2>
<div>
<p>Ask a yes/no question:</p>
<input type="text" v-model="question" :disabled="loading">
<p>{{answer}}</p>
</div>
</div>
<hr>
<div>
<h2>例二:</h2>
</div>
</template>
<style scoped>
</style>
侦听数据源类型
watch 的第一个参数可以是不同形式的“数据源”:它可以是
1.一个 ref (包括计算属性)
2.一个响应式对象
3.一个 getter 函数
4.或多个数据源组成的数组:
<template>
<div>
<h1>例二:数据源的类型</h1>
<div>
total:{{val1 + val2}}
</div>
<button @click="val1++">修改数据</button>
</div>
</template>
<script setup lang="ts">
import {ref, watch} from "vue";
const val1 = ref(6);
const val2 = ref(8);
// watch( val1.value + val2.value,(newVal1,newVal2)=>{ // 这样不生效
// console.log(newVal1)
// })
// 请使用监听getter函数
watch(() => val1.value + val2.value,(newVal1,newVal2)=>{
console.log(newVal1)
})
// 监听多个值
watch([val1,val2],([new1, new2],[old1,old2])=>{
console.log(new1, new2) // 7 8
console.log(old1,old2) // 6 8
})
</script>
<style lang="scss" scoped>
</style>
例三:
<template>
<div>
数量 :{{total.count}}
</div>
<hr>
<button @click="total.count++">增加</button>
</template>
<script setup lang="ts">
import {reactive, watch} from "vue";
const total = reactive({
count: 0
});
// 这样监听不生效,因为 watch() 得到的参数是一个 number
// watch(total.count,(newVal , oldVal)=>{
// console.log(newVal , oldVal)
// })
// 需要使用gatter函数
watch(()=>total.count,(newVal , oldVal)=>{
console.log(newVal , oldVal)
})
</script>
<style scoped>
</style>
深度监听器
<template>
<div>
<h1>深度监听器</h1>
<div>
姓名: {{info.obj.name}}
年纪: {{info.obj.age}}
</div>
<hr>
<button @click="handleChange">修改</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch} from "vue";
const info = ref({
obj:{
name:'xiexie',
age:123
}
});
const handleChange = () =>{
info.value.obj.name = '嘻嘻嘻';
info.value.obj.age++;
}
watch(info,(val1, val2)=>{
console.log(val1)
},{deep:true}) // 如果不设置deep,那么这个ref数据就监听不到,因为它的层级比较深层
</script>
<style scoped>
</style>
在 Vue 3.5+ 中,deep 选项还可以是一个数字,表示最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数
即时回调的侦听器
watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。举例来说,我们想请求一些初始数据,然后在相关状态更改时重新请求数据。
我们可以通过传入 immediate: true 选项来强制侦听器的回调立即执行:
js
watch(
source,
(newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
},
{ immediate: true }
)
一次性侦听器
- 仅支持 3.4 及以上版本
每当被侦听源发生变化时,侦听器的回调就会执行。如果希望回调只在源变化时触发一次,请使用 once: true 选项。
js
watch(
source,
(newValue, oldValue) => {
// 当 `source` 变化时,仅触发一次
},
{ once: true }
)