Vue3笔记
Vue3笔记
LvemiX⁹创建一个 Vue 应用
在你想创建项目的目录打开终端运行以下命令 (不要带上 $
符号):
1 | npm create vue@latest |
当你准备将应用发布到生产环境时,请运行:
1 | npm run build |
此命令会在 ./dist
文件夹中为你的应用创建一个生产环境的构建版本。关于将应用上线生产环境的更多内容,请阅读生产环境部署指南。
Vue目录结构
模板语法
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 | <p>Using text interpolation: {{ rawHtml }}</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 | <!-- 与 :id="id" 相同 --> |
这与在 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 | const objectOfAttrs = { |
通过不带参数的 v-bind
,你可以将它们绑定到单个元素上:
1 | <div v-bind="objectOfAttrs"></div> |
使用 JavaScript 表达式
至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:
1 | {{ number + 1 }} |
这些表达式都会被作为 JavaScript ,以当前组件实例为作用域解析执行。
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
- 在文本插值中 (双大括号)
- 在任何 Vue 指令 (以
v-
开头的特殊 attribute) attribute 的值中
仅支持表达式
每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return
后面。
因此,下面的例子都是无效的:
1 | <!-- 这是一个语句,而非表达式 --> |
调用函数
可以在绑定的表达式中使用一个组件暴露的方法:
1 | <time :title="toTitleDate(date)" :datetime="date"> |
指令 Directives
指令是带有 v-
前缀的特殊 attribute。Vue 提供了许多内置指令,包括上面我们所介绍的 v-bind
和 v-html
。
指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-for
、v-on
和 v-slot
)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 v-if
为例:
1 | <p v-if="seen">Now you see me</p> |
这里,v-if
指令会基于表达式 seen
的值的真假来移除/插入该 <p>
元素。
参数 Arguments
某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind
指令来响应式地更新一个 HTML attribute:
1 | <a v-bind:href="url"> ... </a> |
这里 href
就是一个参数,它告诉 v-bind
指令将表达式 url
的值绑定到元素的 href
attribute 上。在简写中,参数前的一切 (例如 v-bind:
) 都会被缩略为一个 :
字符。
另一个例子是 v-on
指令,它将监听 DOM 事件:
1 | <a v-on:click="doSomething"> ... </a> |
这里的参数是要监听的事件名称:click
。v-on
有一个相应的缩写,即 @
字符。我们之后也会讨论关于事件处理的更多细节。
动态参数
同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:
1 | <!-- |
这里的 attributeName
会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName
,其值为 "href"
,那么这个绑定就等价于 v-bind:href
。
相似地,你还可以将一个函数绑定到动态的事件名称上:
1 | <a v-on:[eventName]="doSomething"> ... </a> |
在此示例中,当 eventName
的值是 "focus"
时,v-on:[eventName]
就等价于 v-on:focus
。
动态参数值的限制
动态参数中表达式的值应当是一个字符串,或者是 null
。特殊值 null
意为显式移除该绑定。其他非字符串的值会触发警告。
动态参数语法的限制
动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:
1 | <!-- 这会触发一个编译器警告 --> |
如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,也是 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-on
和 v-model
的功能时,你将会看到其他修饰符的例子。
条件渲染
v-if
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
1 | <h1 v-if="awesome">Vue is awesome!</h1> |
v-else
你也可以使用 v-else
为 v-if
添加一个“else 区块”。
1 | <button @click="awesome = !awesome">Toggle</button> |
一个 v-else
元素必须跟在一个 v-if
或者 v-else-if
元素后面,否则它将不会被识别。
v-else-if
顾名思义,v-else-if
提供的是相应于 v-if
的“else if 区块”。它可以连续多次重复使用:
1 | <div v-if="type === 'A'"> |
和 v-else
类似,一个使用 v-else-if
的元素必须紧跟在一个 v-if
或一个 v-else-if
元素后面。
<template>
上的 v-if
因为 v-if
是一个指令,他必须依附于某个元素。但如果我们想要切换不止一个元素呢?在这种情况下我们可以在一个 <template>
元素上使用 v-if
,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template>
元素。
1 | <template v-if="ok"> |
v-else
和 v-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 | <li v-for="item in items"> |
在 v-for
块中可以完整地访问父作用域内的属性和变量。v-for
也支持使用可选的第二个参数表示当前项的位置索引。
1 | const parentMessage = ref('Parent') |
1 | <li v-for="(item, index) in items"> |
v-for
变量的作用域和下面的 JavaScript 代码很类似:
1 | const parentMessage = 'Parent' |
v-for
是如何对应 forEach
回调的函数签名的。实际上,你也可以在定义 v-for
的变量别名时使用解构,和解构函数参数类似:
1 | <li v-for="{ message } in items"> |
对于多层嵌套的 v-for
,作用域的工作方式和函数的作用域很类似。每个 v-for
作用域都可以访问到父级作用域:
1 | <li v-for="item in items"> |
你也可以使用 of
作为分隔符来替代 in
,这更接近 JavaScript 的迭代器语法:
1 | <div v-for="item of items"></div> |
v-for
与对象
你也可以使用 v-for
来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.keys()
的返回值来决定。
1 | const myObject = reactive({ |
1 | <ul> |
可以通过提供第二个参数表示属性名 (例如 key):
1 | <li v-for="(value, key) in myObject"> |
第三个参数表示位置索引:
1 | <li v-for="(value, key, index) in myObject"> |
在 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 | <ul> |
通过 key 管理状态
Vue 默认按照“就地更新”的策略来更新通过 v-for
渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
默认模式是高效的,但只适用于列表渲染输出的结果不依赖子组件状态或者临时 DOM 状态 (例如表单输入值) 的情况。
为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key
attribute:
1 | <div v-for="item in items" :key="item.id"> |
当你使用 <template v-for>
时,key
应该被放置在这个 <template>
容器上:
1 | <template v-for="todo in todos" :key="todo.name"> |
数组变化侦测
变更方法
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。这些变更方法包括:
push()
// 在数组的末尾添加一个或者多个元素,返回数组的新长度pop()
// 删除数组末尾最后一个,并返回改变原数组的值shift()
// 在数组的头部添加一个或者多个元素,返回数组的新长度unshift()
// 在数组的头部添加一个或者多个元素,返回数组的新长度splice()
// 删除数组头部最后一个,并返回改变原数组的值sort()
// 返回排序的数组,如果数组包含undefined,会排序到末尾。如果不带参数的调用sort,数组元素以字母顺序排序,反之以大小顺序排序reverse()
// 将数组倒叙,返回数组的新长度
替换一个数组
变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一些不可变 (immutable) 方法,例如 filter()
,concat()
和 slice()
,这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的:
1 | // 例如下方 |
事件处理
监听事件
我们可以使用 v-on
指令 (简写为 @
) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="handler"
或 @click="handler"
。
事件处理器 (handler) 的值可以是:
- 内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与
onclick
类似)。 - 方法事件处理器:一个指向组件上定义的方法的属性名或是路径。
内联事件处理器
1 | <template> |
方法事件处理器
1 | <template> |
事件传参
当有一个变量的时还要传event
则就要在前面加$
1 | <template> |
事件修饰符
在处理事件时调用 event.preventDefault()
或 event.stopPropagation()
是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。
为解决这一问题,Vue 为 v-on
提供了事件修饰符。修饰符是用 .
表示的指令后缀,包含以下这些:
.stop
.prevent
.self
.capture
.once
.passive
1 | <!-- 单击事件将停止传递 --> |
.capture
、.once
和 .passive
修饰符与原生 addEventListener
事件相对应:
1 | <!-- 添加事件监听器时,使用 `capture` 捕获模式 --> |
.passive
修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
按键修饰符
在监听键盘事件时,我们经常需要检查特定的按键。Vue 允许在 v-on
或 @
监听按键事件时添加按键修饰符。
1 | <!-- 仅在 `key` 为 `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 | <!-- Alt + Enter --> |
.exact
修饰符
.exact
修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。
1 | <!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 --> |
鼠标按键修饰符
.left
.right
.middle
这些修饰符将处理程序限定为由特定鼠标按键触发的事件。
计算属性
计算属性缓存 VS 方法
模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以
维护。因此我们推荐使用计算属性来描述依赖响应式状态的复杂逻辑
1 | <template> |
- 计算属性:计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算
- 方法:方法调用总是会在重渣染发生时再次执行函数
类与样式绑定
Class绑定
数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 class
和 style
都是 attribute,我们可以和其他 attribute 一样使用 v-bind
将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 class
和 style
的 v-bind
用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
绑定对象
1 | <template> |
数组和对象嵌套过程中,只能是数组嵌套对象,不能反其道而行
Style绑定
绑定对象
1 | <template> |
样式多值
你可以对一个样式属性提供多个 (不同前缀的) 值,举例来说:
1 | <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div> |
数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex
。
scoped
在Style当中加了scoped则表示样式只在当前页面使用
1 | <template> |
侦听器
计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。
在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数
1 | <template> |
表单输入绑定
在前端处理表单时,我们常常需要将表单输入框的内容同步给JavaScript中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦,v-model指令帮我们简化了这一步骤
1 | <template> |
复选框
单一的复选框,绑定布尔类型值
1 | </template> |
修饰符
v-model也提供了修饰符:**.lazy、.number、.trim**
.lazy
默认情况下,v-model会在每次input事件后更新数据。你可以添加**.lazy修饰符来改为每次change**事件后更新数据。
1 | <template> |
.number只能输入数字、**.trim**去除两端空格。
模板引用
虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的 ref
attribute:
挂载结束后引用都会被暴露在this.$refs之上。
1 | <template> |
组件基础
定义组件
1 | <template> |
组件嵌套关系
组件注册方式
一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。
全局注册
1 | import { createApp } from 'vue' |
局部注册
全局注册虽然很方便,但有以下几个问题:
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
- 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。
在使用 <script setup>
的单文件组件中,导入的组件可以直接在模板中使用,无需注册:
1 | <template> |
如果没有使用 <script setup>
,则需要使用 components
选项来显式注册:
1 | <template> |
局部注册的组件在后代组件中不可用。在这个例子中,ComponentA
注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。
组件传递数据
Props
组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的传递数据的解决方案就是props
pass.vue
可静态传递数据,**:**可动态传递数据
1 | <template> |
acceptance.vue
1 | <template> |
props 传递数据,只能从父级传递子级,不能反其道而行
Number
1 | <template> |
1 | <template> |
Array
1 | <template> |
1 | <template> |
Object
1 | <template> |
1 | <template> |
Props效验
Vue组件可以更细致地声明对传入的props的校验要求
可添加type对传递的数据进行校验,提供的类型:String、Number、Array、Object、Date、Function、Symbol、Error
1 | <template> |
1 | <template> |
用default给默认值
type
给出 null
和 undefined
值则会跳过任何类型检查。required
必须得传数据
如果父级没有传数据,则显示默认值
1 | <template> |
字符串和数字可直接default
,但如果是数组和对象,必须通过工厂函数来返回默认值
组件事件
在组件的模版表达式中,可以直接使用$emit
方法触发自定义事件
触发自定义事件的目的是组件之间传递数据
- 组件之间传递数的方案:
- 父传子:
props
- 子传父:
自定义事件(this.$emit)