@[TOC](自学前端开发 - VUE 框架 (三) 常用的标签指令 、选项式API、组合式API)
vue 常用的标签指令
指令其实就是标签的一个属性,它由 v-
作为前缀,表明是一个由 vue 提供的特殊属性。它们将为渲染的 DOM 应用特殊的响应式行为。
v-html
v-html
指令的意思是,在当前组件实例上,将此元素的 innerHTML 与 rawHtml 属性保持同步。
<p>Using text interpolation: {
{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
span
的内容将会被替换为 rawHtml
属性的值,插值为纯 HTML——数据绑定将会被忽略。注意,不能使用 v-html
来拼接组合模板,因为 Vue 不是一个基于字符串的模板引擎。在使用 Vue 时,应当使用组件作为 UI 重用和组合的基本单元。
v-text
v-text
通过设置元素的 textContent 属性来工作,因此它将覆盖元素中所有现有的内容。相当于文本插值。
<span v-text="msg"></span>
<!-- 等同于 -->
<span>{
{msg}}</span>
v-bind
v-bind
指令会绑定一个或若干属性到元素对象上:
<div v-bind:id="dynamicId"></div>
v-bind
指令指示 Vue 将元素的 id
属性与组件的 dynamicId
属性保持一致。如果绑定的值是 null
或者 undefined
,那么该属性将会从渲染的元素上移除。
也可以简写为
<div :id="dynamicId"></div>
对于 v-bind
指令绑定的属性,如果是布尔型属性,例如 disabled、hidden 等,可以这样使用:
<button :disabled="isButtonDisabled">Button</button>
当 isButtonDisabled
为真值或一个空字符串 (即 <button disabled="">
) 时,元素会包含这个 disabled 属性。而当其为其他假值时属性将被忽略。
如果 v-bind
指令不带参数,则会绑定其包含的多个属性到元素对象上:
data() {
return {
objectOfAttrs: {
id: 'container',
class: 'wrapper'
}
}
}
<div v-bind="objectOfAttrs"></div>
可以使用的修饰符有:
- .camel ——将短横线命名的属性转变为驼峰式命名。
- .prop ——强制绑定为 DOM 成员。
- .attr ——强制绑定为 DOM 属性。
参考示例:
<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />
<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>
<!-- 缩写 -->
<img :src="imageSrc" />
<!-- 缩写形式的动态 attribute 名 -->
<button :[key]="value"></button>
<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />
<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]"></div>
<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>
<!-- 绑定对象形式的 attribute -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
<!-- prop 绑定。“prop” 必须在子组件中已声明。 -->
<MyComponent :prop="someThing" />
<!-- 传递子父组件共有的 prop -->
<MyComponent v-bind="$props" />
<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>
v-on
v-on
指令会监听一个 DOM 事件,参数就是需要监听的事件。当用于普通元素,只监听原生 DOM 事件。当用于自定义元素组件,则监听子组件触发的自定义事件。
当监听原生 DOM 事件时,方法接收原生事件作为唯一参数。如果使用内联声明,声明可以访问一个特殊的 $event
变量:v-on:click="handle('ok', $event)"
。
<!-- 方法处理函数 -->
<button v-on:click="doThis"></button>
<!-- 动态事件 -->
<button v-on:[event]="doThis"></button>
<!-- 内联声明 -->
<button v-on:click="doThat('hello', $event)"></button>
<!-- 缩写 -->
<button @click="doThis"></button>
<!-- 使用缩写的动态事件 -->
<button @[event]="doThis"></button>
<!-- 停止传播 -->
<button @click.stop="doThis"></button>
<!-- 阻止默认事件 -->
<button @click.prevent="doThis"></button>
<!-- 不带表达式地阻止默认事件 -->
<form @submit.prevent></form>
<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>
<!-- 按键用于 keyAlias 修饰符-->
<input @keyup.enter="onEnter" />
<!-- 点击事件将最多触发一次 -->
<button v-on:click.once="doThis"></button>
<!-- 对象语法 -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
可以使用的修饰符:
- .stop ——调用
event.stopPropagation()
。 - .prevent ——调用
event.preventDefault()
。 - .capture ——在捕获模式添加事件监听器。
- .self ——只有事件从元素本身发出才触发处理函数。
- .{keyAlias} ——只在某些按键下触发处理函数。
- .once ——最多触发一次处理函数。
- .left ——只在鼠标左键事件触发处理函数。
- .right ——只在鼠标右键事件触发处理函数。
- .middle ——只在鼠标中键事件触发处理函数。
- .passive ——通过 { passive: true } 附加一个 DOM 事件。
v-model
v-model
指令会将表单元素的 value 属性双向绑定到数据模型中,例如当用户修改了元素对象的值,数据模型的值也同时被修改
<div id="app">
<p>{
{ num }}</p>
<input type="text" v-model="num">
</div>
<script>
const {
createApp} = Vue
let vm = Vue.createApp({
data() {
return {
num: 0
}
},
mounted() {
setInterval(() => {
this.num++;
}, 1000)
}
}).mount('#app')
</script>
可以使用修饰符
- .lazy ——监听 change 事件而不是 input
- .number ——将输入的合法符串转为数字
- .trim ——移除输入内容两端空格
v-pre
跳过该元素及其所有子元素的编译,会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。
<span v-pre>{
{ this will not be compiled }}</span>
v-once
仅渲染元素和组件一次,并跳过之后的更新。在随后的重新渲染,元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。
<!-- 单个元素 -->
<span v-once>This will never change: {
{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
<h1>comment</h1>
<p>{
{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
<li v-for="i in list" v-once>{
{i}}</li>
</ul>
v-memo
缓存一个模板的子树。在元素和组件上都可以使用。为了实现缓存,该指令需要传入一个固定长度的依赖值数组进行比较。如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过。举例来说:
<div v-memo="[valueA, valueB]">
...
</div>
当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 <div>
及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。
正确指定缓存数组很重要,否则应该生效的更新可能被跳过。v-memo 传入空依赖数组 (v-memo="[]"
) 将与 v-once
效果相同。
v-memo 与 v-for 一起使用
v-memo
仅用于性能至上场景中的微小优化,应该很少需要。最常见的情况可能是有助于渲染海量 v-for
列表 (长度超过 1000 的情况):
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {
{ item.id }} - selected: {
{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
当组件的 selected 状态改变,默认会重新创建大量的 vnode,尽管绝大部分都跟之前是一模一样的。v-memo
用在这里本质上是在说“只有当该项的被选中状态改变时才需要更新”。这使得每个选中状态没有变的项能完全重用之前的 vnode 并跳过差异比较。注意这里 memo 依赖数组中并不需要包含 item.id,因为 Vue 也会根据 item 的 :key
进行判断。需注意的是,当搭配 v-for
使用 v-memo
,确保两者都绑定在同一个元素上。v-memo
不能用在 v-for
内部。
vue 常用的选项式API选项
状态选项
data
data 选项是 vue 默认的数据模型(model),它会接收一个函数返回的数据对象,并将其在模板中渲染出来。
<div id="app">
<p>{
{ message }}</p>
</div>
<script>
let vm = Vue.createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
</script>
可以通过修改其数据来更新页面渲染。
vm.$data.message = 'Hello World!'
data 中声明的所有数据会被 vm 对象进行遍历,并赋值给 vm 对象作为其属性。所以也可以这样来修改数据:
vm.message = 'Hello World!'
computed
computed 选项(计算属性)是一个对象,对象中的属性则是一个 getter 方法,用来描述依赖响应式状态的复杂逻辑。computed 选项生命的属性(getter 方法)在应用实例化时,也会像 data 声明的属性一样成为应用的属性。
export default {
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
computed: {
// 一个计算属性的 getter
publishedBooksMessage() {
// `this` 指向当前组件实例
return this.author.books.length > 0 ? 'Yes' : 'No'
}
}
}
<p>Has published books:</p>
<!-- 因为 computed 的成员是 getter 方法,所以所有成员可以像变量一样直接使用 -->
<span>{
{ publishedBooksMessage }}</span>
计算属性是只读的,如果尝试写则会收到一个运行时警告。如果确实需要进行写入操作,则需要同时提供 getter 和 setter 来创建
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
// 注意:这里使用的是解构赋值语法
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
}
methods
methods 是方法选项,和 computed 组件类似的是,它也是一个对象,对象中的每个属性都是一个方法,可以在模板中被调用。
两者不同之处在于计算属性值会基于其响应式依赖被缓存,即计算属性仅会在其响应式依赖更新时才重新计算。而方法组件在每次调用时都会重新进行计算。
// 计算属性永远不会更新,因为 Date.now() 不是响应式依赖。所以应该使用方法组件。
computed: {
now() {
return Date.now()
}
}
Vue 自动为 methods 中的方法绑定了永远指向组件实例的 this。这确保了方法在作为事件监听器或回调函数时始终保持正确的 this。你不应该在定义 methods 时使用箭头函数,因为箭头函数没有自己的 this 上下文。
watch
watch 选项用于声明在数据更改时调用的侦听回调。watch 选项期望接受一个方法或对象:如果是方法,则方法名为组件的示例属性名,方法参数包含旧值和新值,方法内容为回调函数。如果是对象,则其中的键是需要侦听的响应式组件实例属性 (例如,通过 data 或 computed 声明的属性),而值是相应的回调函数。该回调函数接受被侦听源的新值和旧值。
除了一个根级属性,键名也可以是一个简单的由点分隔的路径,例如 a.b.c
。注意,这种用法不支持复杂表达式——仅支持由点分隔的路径。如果你需要侦听复杂的数据源,可以使用命令式的 $watch()
API。
值也可以是一个方法名称的字符串 (通过 methods 声明),或包含额外选项的对象。当使用对象语法时,回调函数应被声明在 handler 中。额外的选项包含:
- immediate:在侦听器创建时立即触发回调。第一次调用时,旧值将为 undefined。
- deep:如果源是对象或数组,则强制深度遍历源,以便在深度变更时触发回调。
- flush:调整回调的刷新时机,例如 ‘post’ 会在 DOM 更新后进行回调(默认在 DOM 更新前进行回调)
- onTrack / onTrigger:调试侦听器的依赖关系。
声明侦听器回调时避免使用箭头函数,因为它们将无法通过 this 访问组件实例。
export default {
data() {
return {
a: 1,
b: 2,
c: {
d: 4
},
e: 5,
f: 6
}
},
watch: {
// 侦听根级属性
a(val, oldVal) {
console.log(`new: ${
val}, old: ${
oldVal}`)
},
// 字符串方法名称
b: 'someMethod',
// 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
c: {
handler(val, oldVal) {
console.log('c changed')
},
deep: true
},
// 侦听单个嵌套属性:
'c.d': function (val, oldVal) {
// do something
},
// 该回调将会在侦听开始之后立即调用
e: {
handler(val, oldVal) {
console.log('e changed')
},
immediate: true
},
// 你可以传入回调数组,它们将会被逐一调用
f: [
'handle1',
function handle2(val, oldVal) {
console.log('handle2 triggered')
},
{
handler: function handle3(val, oldVal) {
console.log('handle3 triggered')
}
/* ... */
}
]
},
methods: {
someMethod() {
console.log('b changed')
},
handle1() {
console.log('handle 1 triggered')
}
},
created() {
this.a = 3 // => new: 3, old: 1
}
}
生命周期选项(钩子)
created
created 选项在组件实例处理完所有与状态相关的选项后调用。
当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el
属性仍不可用。
mounted
mounted 选项接收一个函数,会在组件被挂载之后调用。
<div id='app'>
<p>count = {
{ count }}</p>
<div>
<script>
let vm = Vue.createApp({
data() {
return{
count: 0
}},
mounted() {
setInterval(()=>{
this.count++;
// 在 vm 外部,可以使用 vm.$data.count 改变绑定的数据
// 也可以使用 vm.count 来改变绑定的数据
},1000)
}
}).mount('#app')
</script>
updated
updated 选项接收一个函数,在组件因为一个响应式状态变更而更新其 DOM 树之后调用。父组件的更新钩子将在其子组件的更新钩子之后调用。这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。如果需要在某个特定的状态更改后访问更新后的 DOM,可以使用 nextTick()
作为替代。注:这个钩子在服务端渲染时不会被调用。
unmounted
unmounted 选项接收一个函数,在组件实例被卸载之后调用。一个组件在以下情况下被视为已卸载:
- 其所有子组件都已经被卸载。
- 所有相关的响应式作用 (渲染作用以及
setup()
时创建的计算属性和侦听器) 都已经停止。
可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。这个钩子在服务端渲染时不会被调用。
vue 常用的组合式API函数
setup
setup()
钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
- 需要在非单文件组件中使用组合式 API 时。
- 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
其他情况下,都应优先使用<script setup>
语法。
组合式API使用时也是基于应用实例,因此需要先创建一个应用实例。和选项式API不同的是,选项式API在创建应用实例时,传入的是若干选项组成的对象,而组合式API传入的,主要是 setup 函数对象。所有选项式API中各选项功能都写在 setup 函数中。
<div id="app">
<p @click="increment">{
{ num }}</p>
</div>
<script>
const {
createApp, ref} = Vue;
let app = createApp({
setup() {
// 响应式状态
let num = ref(0);
// 用来修改状态、触发更新的函数
function increment() {
num.value++
}
// 返回值会暴露给模板和其他的选项式 API 钩子
return {
num, increment}
}
}).mount('#app')
</script>
请注意在模板中访问从 setup 返回的 ref 时,它会自动浅层解包,因此你无须再在模板中为它写 .value。当通过 this 访问时也会同样如此解包。
ref
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value
。ref 对象是可更改的,也就是说可以为 .value
赋予新的值。它也是响应式的,即所有对 .value
的操作都将被追踪,并且写操作会触发与之相关的副作用。
如果将一个对象赋值给 ref,那么这个对象将通过 reactive()
转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。若要避免这种深层次的转换,可以使用 shallowRef()
来替代。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
computed
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value
暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。
创建一个只读的计算属性 ref:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
创建一个可写的计算属性 ref:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
reactive
返回一个对象的响应式代理。响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。
值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。若要避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,可以使用 shallowReactive()
作替代。返回的对象以及其中嵌套的对象都会通过 ES Proxy 包裹,因此不等于源对象,建议只使用响应式代理,避免使用原始对象。
和 ref()
比起来, reactive 创建的响应式代理对象不用使用 .value
就能正确的使用其成员。所以对于基础数据使用 ref()
,对于数组或对象使用 reactive()
来转化响应式状态。
watch
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。watch(source,callback,options)
默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
第一个参数是侦听器的源。这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象
- …或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
第三个可选的参数是一个对象,支持以下这些选项:
- immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
- deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。
- flush:调整回调函数的刷新时机。参考回调的刷新时机及 watchEffect()。
- onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器。
与 watchEffect()
相比,watch() 使我们可以:
- 懒执行副作用;
- 更加明确是应该由哪个状态触发侦听器重新执行;
- 可以访问所侦听状态的前一个值和当前值。
侦听一个 getter 函数:
const state = reactive({
count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
侦听一个 ref:
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true }
强制侦听器进入深层级模式。在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。
const state = reactive({
count: 0 })
watch(
() => state,
(newValue, oldValue) => {
// newValue === oldValue
},
{
deep: true }
)
当直接侦听一个响应式对象时,侦听器会自动启用深层模式:
const state = reactive({
count: 0 })
watch(state, () => {
/* 深层级变更状态所触发的回调 */
})