v-bind
想要响应式地绑定一个 attribute,应该使用 v-bind
指令:
如果绑定的值是 null
或者 undefined
,那么该 attribute 将会从渲染的元素上移除
然而,对于一个自定义组件。这不意味着拿不到props,只不过他的值是undefined或null
同名简写
只在 Vue 3.4 及以上版本中可用
<!-- 与 :id="id" 相同 -->
<div :id></div>
<!-- 这也同样有效 -->
<div v-bind:id></div>
布尔型 Attribute
当 isButtonDisabled
为真值或一个空字符串 (即 <button disabled="">
) 时,元素会包含这个 disabled
attribute。而当其为其他假值时 attribute 将被忽略。
动态绑定多个值
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
<div v-bind="objectOfAttrs"></div>
使用 JavaScript 表达式
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
- 在文本插值中 (双大括号)
- 在任何 Vue 指令 (以
v-
开头的特殊 attribute) attribute 的值中
v-model
在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦:
<input
:value="text"
@input="event => text = event.target.value">
v-model
指令帮我们简化了这一步骤:
<input v-model="text">
另外,v-model
还可以用于各种不同类型的输入,<textarea>
、<select>
元素。它会根据所使用的元素自动使用对应的 DOM 属性和事件组合
修饰符
.lazy
默认情况下,v-model
会在每次 input
事件后更新数据。你可以添加 lazy
修饰符来改为在每次 change
事件后更新数据:
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
.number
如果你想让用户输入自动转换为数字,你可以在 v-model
后添加 .number
修饰符来管理输入:
<input v-model.number="age" />
如果该值无法被 parseFloat()
处理,那么将返回原始值。
number
修饰符会在输入框有 type="number"
时自动启用。
.trim
如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model
后添加 .trim
修饰符
v-for
v-for遍历数组
基本使用不再赘述
v-for里可以解构
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
你也可以使用 of
作为分隔符来替代 in
<div v-for="item of items"></div>
v-for也可以遍历对象
遍历的顺序会基于对该对象调用 Object.keys()
的返回值来决定
不同的是第二个参数表示属性名,第三个参数表示位置索引
在 v-for
里使用范围值
v-for
可以直接接受一个整数值。在这种用例中,会将该模板基于 1...n
的取值范围重复多次。初值是从1开始。
<span v-for="n in 10">{{ n }}</span>
v-for
与v-if
当它们同时存在于一个节点上时,
v-if
比v-for
的优先级更高。这意味着v-if
的条件将无法访问到v-for
作用域内定义的变量别名:
<!-- 这会抛出一个错误,因为属性 todo 此时 没有在该实例上定义 -->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
Vue官网给出了两种解决方案
- 使用计算属性先过滤原数组,再迭代这个计算属性
- 在外先包装一层
<template>
再在其上使用v-for
可以解决这个问题,官网更推荐这种,简单明了。
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
通过 key 管理状态
没有key的情况下,更新策略是
公共长度部分节点打补丁
根据长度移除多余的节点或者挂载新节点
所以不会调整元素的顺序,在公共部分会依次比较元素的类型,如果类型一致会重用,不会销毁。
这样重用可能会有问题
<script setup>
import { reactive, ref } from 'vue'
const arr=reactive([1,2,3,4,5,6])
function add(){
arr.splice(2,1)
}
</script>
<template>
<div v-for="item in arr" >
<input />
</div>
<div @click="add">点击</div>
</template>
但是点击之后,不是我们想删的元素
如果带上了key,会使用diff算法
将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。
同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。
推荐在任何可行的时候为
v-for
提供一个key
attribute,除非所迭代的 DOM 内容非常简单 (例如:不包含组件或有状态的 DOM 元素),或者你想有意采用默认行为来提高性能。
key
绑定的值期望是一个基础类型的值,例如字符串或 number 类型。不要用对象作为v-for
的 key。
指令 Directives
参数 Arguments
某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind
指令来响应式地更新一个 HTML attribute:
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>
动态参数
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
修饰符 Modifiers
修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent
修饰符会告知 v-on
指令对触发的事件调用 event.preventDefault()
:
ref 解包细节
主要是ref作为对象/数组内的元素,以及在模板上的解包不同
作为响应式对象的属性
一个 ref 会在作为响应式对象的属性被访问或修改时自动解包。换句话说,它的行为就像一个普通的属性:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
const data= ref({
msg
})
console.log(data.value.msg)//相当于普通属性访问
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</template>
<script setup>
import { reactive, ref } from 'vue'
const msg = ref('Hello World!')
const data= reactive({
msg
})
console.log(data.msg)//相当于普通属性访问
</script>
<template>
<h1>{{ msg }}</h1>
<input v-model="msg" />
</template>
只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。
数组和集合的注意事项
ref 作为响应式数组或原生集合类型 (如 Map
) 中的元素被访问时,它不会被解包:
<script setup>
import { ref } from 'vue'
const books = (ref/reactive)([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books.value[0])
</script>
在模板中解包的注意事项
在模板渲染上下文中,只有顶级的 ref 属性才会被解包。
在下面的例子中,count
和 object
是顶级属性,但 object.id
不是:
const count = ref(0)
const object = { id: ref(1) }
{{ count + 1 }}//可以拿到
{{ object.id + 1 }}//拿不到值,因为不是顶级属性
另一个需要注意的点是,如果 ref 是文本插值的最终计算值 (即 {{ }}
标签),那么它将被解包,因此以下内容将渲染为 1
:
{{ object.id }} //等价于{{ object.id.value }}
{{ object.id + 1 }}//不能
模板引用
ref 在v-for上的使用
此时它将是一个数组
ref在自定义组件上的使用
通过defineExpose使用该组件暴露出的数据和方法
生命周期钩子
onMounted
其所有同步子组件都已经被挂载 (不包含异步组件或 <Suspense> 树内的组件)。
onUpdated
注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。
父组件的更新钩子将在其子组件的更新钩子之后调用。
这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行 (考虑到性能因素)。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
此时可以拿到最新的dom
onUnmounted
需要所有子组件先被卸载
onBeforeMount
已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。
侦听器
侦听数据源类型
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
深层侦听器
watch传入一个reactive类型的数据,会自动创建深层监听器。相当于deep:true.监听其他类型都不会深层监听
watchEffect
如果你需要侦听一个嵌套数据结构中的几个属性,watchEffect()
可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性。
watchEffect
仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个await
正常工作前访问到的属性才会被追踪。也就是await之后的响应式数据不会被跟踪
回调的触发时机
修改了响应式数据,可能会同时触发 Vue 组件更新和侦听器回调
类似于组件更新,用户创建的侦听器回调函数也会被批处理以避免重复调用
默认情况下,侦听器回调会在父组件更新 (如有) 之后、所属组件的 DOM 更新之前被调用。这意味着如果你尝试在侦听器回调中访问所属组件的 DOM,那么 DOM 将处于更新前的状态。
Post Watchers
如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM,你需要指明 flush: 'post'
选项
后置刷新的 watchEffect()
有个更方便的别名 watchPostEffect()
同步侦听器
监听的响应式数据修改后立即执行回调,更加拿不到最新的dom
同步侦听器不会进行批处理,每当检测到响应式数据发生变化时就会触发(开销大)。可以使用它来监视简单的布尔值,但应避免在可能多次同步修改的数据源 (如数组) 上使用。
举个例子,在定时器中对监听的响应式数据进行5次修改
setup() {
const data =reactive({a:1,b:2})
watch(()=>data.a+data.b,()=>{
console.log(data,'watch')
},{
flush:'sync'
})
setTimeout(()=>{
data.a+=1
data.a+=1
data.a+=1
data.a+=1
data.b-=1
},2000)
const message = ref('Hello vue!')
return {
message
}
}
打印结果,触发五次cb
使用默认属性,之后打印一次,因为批处理
异步创建监听器
如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏
<script setup>
import { watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {})
// ...这个则不会!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
要手动停止一个侦听器,请调用 watch
或 watchEffect
返回的函数:
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑:
// 需要异步请求得到的数据
const data = ref(null)
watchEffect(() => {
if (data.value) {
// 数据加载后执行某些操作...
}
})
特殊情况
setup() {
const data =reactive({a:1,b:2})
watch(()=>data.a+data.b,()=>{
console.log(data,'watch')
})
setTimeout(()=>{
data.a+=1
data.b-=1
},2000)
const message = ref('Hello vue!')
return {
message
}
}
这样不会触发cb,因为是比较getter函数返回值是否改变才执行cb
可以看到如果设置了deep为true或者是浅响应会触发cb的