Vue3笔记

创建一个 Vue 应用

在你想创建项目的目录打开终端运行以下命令 (不要带上 $ 符号):

1
2
3
4
5
npm create vue@latest

cd <your-project-name> 请取项目名称
npm install
npm run dev

当你准备将应用发布到生产环境时,请运行:

1
npm run build

此命令会在 ./dist 文件夹中为你的应用创建一个生产环境的构建版本。关于将应用上线生产环境的更多内容,请阅读生产环境部署指南

Vue目录结构

2024-01-12_14-28-43.webp

2024-01-12_11-20-16.webp

模板语法

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。

在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。

如果你对虚拟 DOM 的概念比较熟悉,并且偏好直接使用 JavaScript,你也可以结合可选的 JSX 支持直接手写渲染函数而不采用模板。但请注意,这将不会享受到和模板同等级别的编译时优化。

文本插值

最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号):

1
<span>Message: {{ msg }}</span>

双大括号标签会被替换为相应组件实例中 msg 属性的值。同时每次 msg 属性更改时它也会同步更新。

原始 HTML

双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令

1
2
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

属性绑定

双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute,应该使用 v-bind 指令

1
<div v-bind:id="dynamicId"></div>

v-bind 指令指示 Vue 将元素的 id attribute 与组件的 dynamicId 属性保持一致。如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。

简写

因为 v-bind 非常常用,我们提供了特定的简写语法:

1
<div :id="dynamicId"></div>

开头为 : 的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。此外,他们不会出现在最终渲染的 DOM 中。简写语法是可选的,但相信在你了解了它更多的用处后,你应该会更喜欢它。

接下来的指引中,我们都将在示例中使用简写语法,因为这是在实际开发中更常见的用法。

同名简写

如果 attribute 的名称与绑定的 JavaScript 值的名称相同,那么可以进一步简化语法,省略 attribute 值:

1
2
3
4
5
<!-- 与 :id="id" 相同 -->
<div :id></div>

<!-- 这也同样有效 -->
<div v-bind:id></div>

这与在 JavaScript 中声明对象时使用的属性简写语法类似。请注意,这是一个只在 Vue 3.4 及以上版本中可用的特性。

布尔型 Attribute

布尔型 attribute 依据 true / false 值来决定 attribute 是否应该存在于该元素上。disabled 就是最常见的例子之一。

v-bind 在这种场景下的行为略有不同:

1
<button :disabled="isButtonDisabled">Button</button>

isButtonDisabled真值或一个空字符串 (即 <button disabled="">) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。

动态绑定多个值

如果你有像这样的一个包含多个 attribute 的 JavaScript 对象:

1
2
3
4
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}

通过不带参数的 v-bind,你可以将它们绑定到单个元素上:

1
<div v-bind="objectOfAttrs"></div>

使用 JavaScript 表达式

至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:

1
2
3
4
5
6
7
{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div :id="`list-${id}`"></div>

这些表达式都会被作为 JavaScript ,以当前组件实例为作用域解析执行。

在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:

  • 在文本插值中 (双大括号)
  • 在任何 Vue 指令 (以 v- 开头的特殊 attribute) attribute 的值中

仅支持表达式

每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return 后面。

因此,下面的例子都是无效的:

1
2
3
4
5
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}

<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}

调用函数

可以在绑定的表达式中使用一个组件暴露的方法:

1
2
3
<time :title="toTitleDate(date)" :datetime="date">
{{ formatDate(date) }}
</time>

指令 Directives

指令是带有 v- 前缀的特殊 attribute。Vue 提供了许多内置指令,包括上面我们所介绍的 v-bindv-html

指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-forv-onv-slot)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 v-if 为例:

1
<p v-if="seen">Now you see me</p>

这里,v-if 指令会基于表达式 seen 的值的真假来移除/插入该 <p> 元素。

参数 Arguments

某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind 指令来响应式地更新一个 HTML attribute:

1
2
3
4
<a v-bind:href="url"> ... </a>

<!-- 简写 -->
<a :href="url"> ... </a>

这里 href 就是一个参数,它告诉 v-bind 指令将表达式 url 的值绑定到元素的 href attribute 上。在简写中,参数前的一切 (例如 v-bind:) 都会被缩略为一个 : 字符。

另一个例子是 v-on 指令,它将监听 DOM 事件:

1
2
3
4
<a v-on:click="doSomething"> ... </a>

<!-- 简写 -->
<a @click="doSomething"> ... </a>

这里的参数是要监听的事件名称:clickv-on 有一个相应的缩写,即 @ 字符。我们之后也会讨论关于事件处理的更多细节。

动态参数

同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:

1
2
3
4
5
6
7
8
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>

<!-- 简写 -->
<a :[attributeName]="url"> ... </a>

这里的 attributeName 会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href

相似地,你还可以将一个函数绑定到动态的事件名称上:

1
2
3
4
<a v-on:[eventName]="doSomething"> ... </a>

<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>

在此示例中,当 eventName 的值是 "focus" 时,v-on:[eventName] 就等价于 v-on:focus

动态参数值的限制

动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。

动态参数语法的限制

动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:

1
2
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>

如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,也是 Vue 最基础的概念之一,我们很快就会讲到。

当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:

1
<a :[someAttr]="value"> ... </a>

上面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]。如果你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。单文件组件内的模板受此限制。

修饰符 Modifiers

修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault():

1
<form @submit.prevent="onSubmit">...</form>

之后在讲到 v-onv-model 的功能时,你将会看到其他修饰符的例子。

directive7WSr6AKH.webp

条件渲染

v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。

1
<h1 v-if="awesome">Vue is awesome!</h1>

v-else

你也可以使用 v-elsev-if 添加一个“else 区块”。

1
2
3
4
<button @click="awesome = !awesome">Toggle</button>

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

一个 v-else 元素必须跟在一个 v-if 或者 v-else-if 元素后面,否则它将不会被识别。

v-else-if

顾名思义,v-else-if 提供的是相应于 v-if 的“else if 区块”。它可以连续多次重复使用:

1
2
3
4
5
6
7
8
9
10
11
12
<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> 元素。

1
2
3
4
5
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>

v-elsev-else-if 也可以在 <template> 上使用。

v-show

另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样:

1
<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-for

我们可以使用 v-for 指令基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名

1
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
1
2
3
<li v-for="item in items">
{{ item.message }}
</li>

v-for 块中可以完整地访问父作用域内的属性和变量。v-for 也支持使用可选的第二个参数表示当前项的位置索引。

1
2
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
1
2
3
4
5
6
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

Parent - 0 - Foo
Parent - 1 - Bar

v-for 变量的作用域和下面的 JavaScript 代码很类似:

1
2
3
4
5
6
7
8
9
10
const parentMessage = 'Parent'
const items = [
/* ... */
]

items.forEach((item, index) => {
// 可以访问外层的 `parentMessage`
// 而 `item` 和 `index` 只在这个作用域可用
console.log(parentMessage, item.message, index)
})

v-for 是如何对应 forEach 回调的函数签名的。实际上,你也可以在定义 v-for 的变量别名时使用解构,和解构函数参数类似:

1
2
3
4
5
6
7
8
<li v-for="{ message } in items">
{{ message }}
</li>

<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>

对于多层嵌套的 v-for,作用域的工作方式和函数的作用域很类似。每个 v-for 作用域都可以访问到父级作用域:

1
2
3
4
5
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>

你也可以使用 of 作为分隔符来替代 in,这更接近 JavaScript 的迭代器语法:

1
<div v-for="item of items"></div>

v-for 与对象

你也可以使用 v-for 来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.keys() 的返回值来决定。

1
2
3
4
5
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
1
2
3
4
5
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>

可以通过提供第二个参数表示属性名 (例如 key):

1
2
3
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>

第三个参数表示位置索引:

1
2
3
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>

v-for 里使用范围值

v-for 可以直接接受一个整数值。在这种用例中,会将该模板基于 1...n 的取值范围重复多次。

1
<span v-for="n in 10">{{ n }}</span>

注意此处 n 的初值是从 1 开始而非 0

<template> 上的 v-for

与模板上的 v-if 类似,你也可以在 <template> 标签上使用 v-for 来渲染一个包含多个元素的块。例如:

1
2
3
4
5
6
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

通过 key 管理状态

Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。

默认模式是高效的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时 DOM 状态 (例如表单输入值) 的情况

为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key attribute:

1
2
3
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>

当你使用 <template v-for> 时,key 应该被放置在这个 <template> 容器上:

1
2
3
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>

数组变化侦测

变更方法

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:

  • push() // 在数组的末尾添加一个或者多个元素,返回数组的新长度
  • pop() // 删除数组末尾最后一个,并返回改变原数组的值
  • shift() // 在数组的头部添加一个或者多个元素,返回数组的新长度
  • unshift() // 在数组的头部添加一个或者多个元素,返回数组的新长度
  • splice() // 删除数组头部最后一个,并返回改变原数组的值
  • sort() // 返回排序的数组,如果数组包含undefined,会排序到末尾。如果不带参数的调用sort,数组元素以字母顺序排序,反之以大小顺序排序
  • reverse() // 将数组倒叙,返回数组的新长度

替换一个数组

变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter()concat()slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 例如下方
<template>
<p v-for="(item,index) of names" :key="index">{{ item }}</p>
<button @click="getClick">点击</button>
</template>
<script>
export default {
data() {
return {
names:["1","2","3","4"]
}
},
methods:{
getClick(){
this.names=this.names.concat("5","6"); //想要响应式数组变更,则必须在前面替换旧数组
}
}
}
</script>

事件处理

监听事件

我们可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="handler"@click="handler"

事件处理器 (handler) 的值可以是:

  1. 内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与 onclick 类似)。
  2. 方法事件处理器:一个指向组件上定义的方法的属性名或是路径。

内联事件处理器

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<button @click="cont++">{{ contents }}</button>
</template>
<script>
export default {
data() {
return {
contents:0,
}
}
}
</script>

方法事件处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<button @click="s1">{{ contents }}</button>
</template>
<script>
export default {
data() {
return {
contents:0,
}
},
methods:{
s1(){
this.contents++
}
}
}
</script>

事件传参

当有一个变量的时还要传event则就要在前面加$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<p @click="getClick(item,$event)" v-for="(item,index) of names" :key="index">{{ item }}</p>
</template>
<script>
export default {
data() {
return {
names:["a","b","c"]
}
},
methods:{
getClick(item,e){
console.log(item);
console.log(e.target);
}
}
}
</script>

事件修饰符

在处理事件时调用 event.preventDefault()event.stopPropagation() 是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。

为解决这一问题,Vue 为 v-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:

  • .stop
  • .prevent
  • .self
  • .capture
  • .once
  • .passive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>

<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>

<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>

<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>

<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>

.capture.once.passive 修饰符与原生 addEventListener 事件相对应:

1
2
3
4
5
6
7
8
9
10
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>

<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>

<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>

.passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能

按键修饰符

在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在 v-on@ 监听按键事件时添加按键修饰符。

1
2
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />

你可以直接使用 KeyboardEvent.key 暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。

1
<input @keyup.page-down="onPageDown" />

在上面的例子中,仅会在 $event.key'PageDown' 时调用事件处理。

按键别名

Vue 为一些常用的按键提供了别名:

  • .enter
  • .tab
  • .delete (捕获“Delete”和“Backspace”两个按键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

系统按键修饰符

你可以使用以下系统按键修饰符来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。

  • .ctrl
  • .alt
  • .shift
  • .meta

举例来说:

1
2
3
4
5
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>

.exact 修饰符

.exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。

1
2
3
4
5
6
7
8
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

鼠标按键修饰符

  • .left
  • .right
  • .middle

这些修饰符将处理程序限定为由特定鼠标按键触发的事件。

计算属性

计算属性缓存 VS 方法

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以
维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<p>{{ prints }}</p>
<p>{{ println() }}</p>
</template>
<script>
export default {
data() {
return {
p: "你好,Vue3!"
}
},
// 计算属性
computed: {
prints() {
return this.p.length > 0 ? "yes" : "no"
}
},
// 方法或函数
methods: {
println() {
return this.p.length > 0 ? "yes" : "no"
}
}
}
</script>
  • 计算属性:计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算
  • 方法:方法调用总是会在重渣染发生时再次执行函数

类与样式绑定

Class绑定

数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 classstyle 都是 attribute,我们可以和其他 attribute 一样使用 v-bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 classstylev-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。

绑定对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<p :class="{'active':option,'red':color}">Class绑定1</p> // 第一种绑定对象
<p :class="getActive">Class绑定2</p> // 多个绑定对象
<p :class="[arrActive,arrColor]">Class绑定3</p> // 数组绑定对象
<p :class="[option ? 'active red' : ' ']">Class绑定4</p> // 三目运算符绑定对象
<p :class="[{'active':option},arrColor]">Class绑定5</p> // 数组和对象
</template>
<script>
export default{
data(){
return{
option:true,
color:true,
getActive:{
'active':true,
'red':true
},
arrActive:'active',
arrColor:'red'
}
}
}
</script>
<style>
.active{
font-size: 1.5rem;
}
.red{
color: red;
}
</style>

数组和对象嵌套过程中,只能是数组嵌套对象,不能反其道而行

Style绑定

绑定对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<p :style="{color:'red',fontSize:activeSize+'px'}">Style绑定1</p> // 可直接写样式,或者用变量来控制
<p :style="StyleObject">Style绑定2</p> // 用对象方式来绑定
<p :style="[StyleObject,StyleDemo]">Style绑定3</p> // 可以用数组嵌套多个对象
</template>
<script>
export default{
data(){
return{
activeSize:30,
StyleObject:{
color:'red',
fontSize:'30px',
},
StyleDemo:{
textIndent:'1em',
marginBottom:'20px'
}
}
}
}
</script>

样式多值

你可以对一个样式属性提供多个 (不同前缀的) 值,举例来说:

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex

scoped

Style当中加了scoped则表示样式只在当前页面使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<p>{{ text }}</p>
</template>
<script>
export default{
data(){
return{
text:'组件'
}
}
}
</script>
<style scoped>
p{
color: red;
}
</style>

侦听器

计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。

在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<p>{{ message }}</p>
<button @click="revise">修改数据</button>
</template>
<script>
export default{
data(){
return{
message:'hello',
}
},
methods:{
revise(){
this.message='world'
}
},
watch:{ // “watch” 只能侦听 “return” 里
message(newValue,oldValue){ // 函数名必须为要侦听的变量或对象,括号里面取名可随便
console.log(newValue,oldValue);
}
}
}
</script>

表单输入绑定

在前端处理表单时,我们常常需要将表单输入框的内容同步给JavaScript中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model指令帮我们简化了这一步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<input type="text" v-model="text">
<p>{{ text }}</p>
</template>
<script>
export default{
data(){
return{
text:''
}
}
}
</script>

复选框

单一的复选框,绑定布尔类型值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
</template>
<input type="checkbox" id="checkbox" v- model="checkbox">
<label for="checkbox">{{ checkbox }}</label>
</template>
<script>
export default{
data(){
return{
text:'',
checkbox:true
}
}
}
</script>

修饰符

v-model也提供了修饰符:**.lazy.number.trim**

.lazy

默认情况下,v-model会在每次input事件后更新数据。你可以添加**.lazy修饰符来改为每次change**事件后更新数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<input type="text" v-model.lazy="text">
<p>{{ text }}</p>
</template>
<script>
export default{
data(){
return{
text:'',
checkbox:true
}
}
}
</script>

.number只能输入数字、**.trim**去除两端空格。

模板引用

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref attribute:

挂载结束后引用都会被暴露在this.$refs之上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<p class="p1" ref="pname">{{ text }}</p>
<button @click="getPname">获取</button>
</template>
<script>
export default{
data(){
return{
text:"内容"
}
},
methods:{
getPname(){
console.log(this.$refs.pname);
}
}
}
</script>

组件基础

定义组件

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<import1 /> // 可重复调用组件
<import1 />
</template>
<script>
import import1 from './components/import1.vue' // 局部'注册'
export default {
components: {
import1
}
}
</script>

组件嵌套关系

组件注册方式

一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。

全局注册

1
2
3
4
5
6
7
import { createApp } from 'vue'
import App from './App.vue'
import Article from './pages/Article.vue'

const app=createApp(App)
app.component("Article",Article)
app.mount('#app')

局部注册

全局注册虽然很方便,但有以下几个问题:

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。

相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。

在使用 <script setup> 的单文件组件中,导入的组件可以直接在模板中使用,无需注册:

1
2
3
4
5
6
7
8
9
10
<template>
<Header />
<Main />
<Aside />
</template>
<script setup>
import Header from './pages/Header.vue';
import Main from './pages/Main.vue';
import Aside from './pages/Aside.vue';
</script>

如果没有使用 <script setup>,则需要使用 components 选项来显式注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<Header />
<Main />
<Aside />
</template>
<script>
import Header from './pages/Header.vue';
import Main from './pages/Main.vue';
import Aside from './pages/Aside.vue';
export default {
components: {
Header,
Main,
Aside,
}
}
</script>

局部注册的组件在后代组件中不可用。在这个例子中,ComponentA 注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。

组件传递数据

Props

组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的传递数据的解决方案就是props

pass.vue

可静态传递数据,**:**可动态传递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<h3>pass</h3>
<acceptance title="静态数据" :text="content" />
</template>
<script>
import acceptance from './acceptance.vue';
export default{
components:{
acceptance
},
data(){
return{
content:'动态数据',
}
}
}
</script>

acceptance.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<h3>acceptance</h3>
<p>{{ title }}</p>
<p>{{ text }}</p>
</template>
<script>
export default{
data(){
return{

}
},
props:["title","text"]
}
</script>

props 传递数据,只能从父级传递子级,不能反其道而行

Number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<h3>pass</h3>
<acceptance :Number="Number"/>
</template>
<script>
import acceptance from './acceptance.vue';
export default{
components:{
acceptance
},
data(){
return{
Number:1,
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<h3>acceptance</h3>
<p>{{ Number }}</p>
</template>
<script>
export default{
data(){
return{

}
},
props:["Number"]
}
</script>
Array
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<h3>pass</h3>
<acceptance :Array="Array"/>
</template>
<script>
import acceptance from './acceptance.vue';
export default{
components:{
acceptance
},
data(){
return{
Array:['a','b','c']
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<h3>acceptance</h3>
<ul v-for="(item,index) in Array">
<li>{{ item }}</li>
</ul>
</template>
<script>
export default{
data(){
return{

}
},
props:["Array"]
}
</script>
Object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<h3>pass</h3>
<acceptance :object="object"/>
</template>
<script>
import acceptance from './acceptance.vue';
export default{
components:{
acceptance
},
data(){
return{
object:{
names:"liming",
age:18,
}
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<h3>acceptance</h3>
<p>{{ object.names }}</p>
<p>{{ object.age }}</p>
</template>
<script>
export default{
data(){
return{

}
},
props:["object"]
}
</script>

Props效验

Vue组件可以更细致地声明对传入的props的校验要求

可添加type对传递的数据进行校验,提供的类型:StringNumberArrayObjectDateFunctionSymbolError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<h3>pass</h3>
<acceptance :title="title"/>
</template>
<script>
import acceptance from './acceptance.vue';
export default{
components:{
acceptance
},
data(){
return{
title:"20"
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h3>acceptance</h3>
<p>{{ title }}</p>
</template>
<script>
export default{
data(){
return{

}
},
props:{
title:{
type:[String,…,…] //可添加多个类型
}
}
}
</script>

default给默认值

type给出 nullundefined 值则会跳过任何类型检查。required必须得传数据

如果父级没有传数据,则显示默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<h3>acceptance</h3>
<p>{{ title }}</p>
<p>{{ numbers }}</p>
<p>{{ arrays }}</p>
<p>{{ objects }}</p>
</template>
<script>
export default{
data(){
return{

}
},
props:{
title:{
type:[String],
default:0,
},
numbers:{
type:[Number],
required:true,

},
arrays:{
type:[Array],
default(){
return ["空"];
}
},
objects:{
type:[Object],
default(){
return {message:'hello'};
}
}
}
}
</script>

字符串和数字可直接default,但如果是数组和对象,必须通过工厂函数来返回默认值

组件事件

在组件的模版表达式中,可以直接使用$emit方法触发自定义事件

触发自定义事件的目的是组件之间传递数据

  • 组件之间传递数的方案:
  • 父传子:props
  • 子传父:自定义事件(this.$emit)