本篇文章的目的是快速的入门 Vue.js,力求能够通过本篇文章了解 Vue.js 的基本用法,但是并不会覆盖 Vue.js 的方方面面,在读完本篇文章之后,便可阅读 Vue.js 的官方教程,进一步的了解 Vue 的进阶内容。
Vue 带来的开发思维的改变
假设有这么一个要求,要我们根据一个数组渲染出一个列表,对于下面的数组
const courses = ['语文', '数学', '英语'];  | 
要求渲染出下面的 HTML 结构
<ul>  | 
所以我们会写出这样的代码
const courses = ['语文', '数学', '英语'];  | 
如果数组的内容发生改变,我们还需要手动更新上述的 HTML 结构。所以传统的开发方式为更新数据,根据数据操作 DOM 树。
那么使用 Vue 会有什么不同,来看一个使用 Vue 的方式来实现上述的功能
<div id="app">  | 
上面的程序可能看不懂,毕竟还没有开始学,所以不必担心。从代码量上看,二者似乎相差不大,可能还有点多,但是当我们更新数组时,我们不必操作 DOM 树来更新页面了,当数据发生变化时,Vue 自动地帮我们更新页面,这种模式我们称为 MVVM,其中的 V 表示 View,而 M 表示 Model,数据与视图进行了绑定,当数据发生变化时,视图也会相应的更新,如下图
所以 Vue 给我们带来开发思维上的改变就是,我们只需要操作数据即可,而更新页面的工作不需要我们来做了。
Hello World
安装 Vue
安装 Vue 当然是选择官网了,当然我们也可以选择通过 CDN 引入文件,例如
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  | 
Hello World
下面就是经典的 Hello World 程序,新建一个 HTML 文件,内容如下
  | 
页面上显示的内容如下:
发生了什么
我们先简单的捋一捋发生了什么,一切先从下面的代码说起
const vm = new Vue({  | 
上面我们创建了一个 Vue 的实例 new Vue(),并且向其中传入一个对象,包括这么两个属性:
- el:绑定的 DOM 元素
 - data:返回一个对象的函数,返回对象中的数据与视图进行了绑定,当修改数据时,相应的视图也会发生更新
 
当我们创建一个 Vue 实例时,首先它会根据 el 属性指定的选择器找到相应的 DOM 元素,我们称这个 DOM 结果为模板(template),例如上例根据 #app 选择器我们找到
<div id="app">  | 
接着便会解析该模板,例如上面将 {{message}} 替换为了 data 中定义的数据 message 的对应的值,即 Hello World!,解析完毕后便会渲染页面,我们便在页面上看到了 Hello World!。
因为 Vue 对 data 属性中定义的数据进行了拦截,一旦我们改变对象属性的值,Vue 便会对用到该属性的模板进行解析、渲染。
拦截数据是通过
Object.definePropety()来做到的,可以通过 MDN 来了解该方法的使用。
Vue实例
上面我们使用变量 vm 接收了 new Vue() 返回的 Vue 实例,现在我们看看里面有什么。
$el
模板经过 Vue 解析、渲染以后,然后根据该模板生成一个 DOM 元素挂载在页面中,而这个 DOM 元素我们可以通过 vm.$el 进行访问得到
$data
通过 vm.$data 可以得到 data 属性返回的对象
我们可以通过 vm.$data 来修改数据
上面我们修改数据 message 为 Hello Vue,页面便发生了变化,进一步证实了数据与视图的绑定。
为了方便通过 vm 操作数据,所有的数据都被挂载到了 vm 上,即我们可以直接通过 vm 访问以及修改数据,而不必通过 vm.$data
上面我们通过 vm.message 直接修改了数据,页面也立即发生了变化。
无论我们是通过
vm.message还是vm.$data.message修改数据,它们之间是互相影响的。即当我们通过vm.xxx修改数据,那么vm.$data.xxx的值也会发生改变,反之亦然。
模板语法
本小节主要讲如何在模板中引用在 data 属性中定义的数据。
插值
在模板中通过 {{}} 插值语法便可引用在 data 中定义的数据,正如上例中的 {{message}},除此以外,{{}} 内部可以是任何的 JavaScript 表达式,如
{{1 + 2}}  | 
{{}} 中的内容会被解析,然后被替换为相应的内容。
指令
v-bind
如果我们想让模板中的属性与数据进行动态绑定,我们便需要借助于 v-bind 属性,有下面的模板以及数据
<div id="app">  | 
const vm = new Vue({  | 
当我们将鼠标放置在 p 标签上时,显示的 title 是 message,而不是数据 Hello World!,说明 title 属性并没有与 message 进行绑定,因为图比较难截,所以自己试验一下。不过从渲染后的 DOM 元素可以证明这一点
我们希望 p 标签的 title 属性与 data 中定义的 massage 进行绑定,我们只需要在属性 title 签名加上 v-bind: 即可
<div id="app">  | 
这时将鼠标放在 p 标签,这时显示的便是 Hello World!,从渲染后的 DOM 元素可以证明这一点
并且这时我们对数据进行更改,相应的数据也会发生变化
因为 v-bind: 比较常用,所以它有一个缩写 :,上述模板可以改为
<div id="app">  | 
class 属性与 style 属性也可以使用 v-bind 绑定属性,不过类与样式实在太过特殊,所以 Vue 对其有做一些特殊的扩展,可以参见官网。
v-on
指令 v-on 可以绑定一个事件
<div id="app">  | 
在上面我们为 p 标签添加了一个点击事件 v-on:click="clear",当我们点击 p 标签时会触发一个叫 clear 的方法,该方法需要在 methods 选项中进行定义,如下
const vm = new Vue({  | 
clear 的方法会将 message 数据设置为空字符串
当我们点击 p 标签时,message 数据变为空字符串,相应的页面也会发生改变。
绑定事件也是一个很常见的操作,所以也有缩写,上面的 v-on: 可以替换为 @
<div id="app">  | 
除了使用 v-on 指令绑定 methods 中的事件,除此之外,我们也可以内联操作数据
<div id="app">  | 
在内联的写法中,我们可以通过
$event访问到event事件对象。
在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的操作,Vue 为 v-on 提供了事件修饰符
.stop:阻止事件继续传播.prevent:阻止默认行为.capture:使用捕获模式.once:事件只执行一次
在监听键盘事件时,我们一般需要检测按下了哪个键,Vue 可以在监听事件时添加按键修饰符
<input @keyup.enter="submit">  | 
处理函数只会在 event.key 等于 enter 被调用,Vue 提供如下按键修饰符
.enter.tab.delete.esc.space.up.down.left.right
在监听鼠标点击事件时,有时也会判断按下了鼠标的哪个按钮,Vue 也提供了相应的鼠标修饰符
.left.right.middle
在日常的使用,我们经常使用快捷键进行快捷操作,设置快捷键一般需要系统按键符配合,如 ctrl,因此 Vue 也为我们提供了系统按键符,包括
.ctrl.alt.shift
<!-- Alt + C -->  | 
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->  | 
v-if、v-show
v-if 和 v-show 指令可以控制元素是否渲染
<div id="app">  | 
上面我们为 p 标签使用了 v-if 指令,指定了它的值为 show,当数据 show 为 true 才会显示标签 p,为 false 时则不会显示。在页面中还有一个按钮,当点击按钮时,会触发 toggle 方法,而该方法是将 show 进行取反,也就是说当我们点击按钮时,如果 p 标签显示,那么点击按钮时它会从页面消失,反之亦然
使用 v-show 也会达到相同的效果,我们将模板中的 v-if 修改为 v-show
<div id="app">  | 
那么二者有何不同? v-show 是控制元素的 display 属性来控制元素是否显示与否的,而 v-if 则是从 DOM 树上移除元素或者添加元素,并且 v-if 是惰性的,所谓惰性指的是如果一开始渲染条件为 false,那么什么也不做,只有当条件第一次为真时,才会开始渲染。
所以当元素频繁切换时,v-if 每次都需要创建元素,相对于 v-show 来说有较大的开销,所以对于这种场景,我们选择 v-show,如果在运行时条件很少改变,因此 v-if 相当于 v-show 有更低的初始渲染开销,所以这时我们选择 v-if。
另外 v-if 可以与 v-else-if 以及 v-else 配合使用,参考下面的代码
<div id="app">  | 
在 data 数据中,我们定义了一个 count 变量,并且定义了一个方法 inc,该方法对数据 count 进行递增。在模板中
<p v-if="count % 3 === 0">{{count}}: 3n</p>  | 
我们对三个标签使用了 v-if v-else-if v-else 三个指令,根据 count 对 3 余数的值来决定显示哪一个
v-for
假设有一个数组数据,我们需要将它渲染为一个列表,尝试如下写法
<div id="app">  | 
虽然上面的代码可以达到我们的功能,但是实在不够灵活:
- 数据发生改变时,页面不能发生改变,例如像 
courses数组中添加元素时,页面的内容不会发生改变 - 代码重复,上面我们是一项一项的写出要显示的内容,如果数组很大,有一万个元素,难道我们要写一万行吗,不仅难以维护,而且很丑
 
使用 v-for 可以轻易的解决上述的问题,我们修改模板代码如下
<div id="app">  | 
我们在 li 标签上面添加了 v-for 指令,Vue 会帮我们从数组中取出元素,然后对于每一个元素都渲染出一个 li 标签,这样一来,当数组发生改变时,页面也会相应的改变,另外,无论数组中有多少项,代码都不需要更改。
仔细观察,我们还为
li标签绑定了一个key属性,它是作为li属性的唯一标识,那么它有什么用呢?假设我们向更改了数组(增加、删除、改变元素的值),我们需要重新渲染所所有的
li标签吗? 当然不需要,我们只要渲染那些更改了li标签就可以了,而识别哪些标签更改了,正是需要通过key属性办到,所以当我们使用v-for指令时,最好同时设置key属性。
官网推荐不要同时使用 v-for 与 v-if 指令,如果同时使用了 v-for 与 v-if,那么 v-for 的优先级更高。
v-model
v-model 指令一般用于表单,它可以将 data 中定义的数据与表单的 value 值进行双向绑定,所谓的双向绑定指的是:
- 数据改变,表单的值也会发生变化
 - 表单接收用户的输入,表单的值发生改变,使得数据也会发生改变
 
<div id="app">  | 
上面我们在 input 中使用了 v-model 指定,它与数据 message 进行了绑定,这就意味着当我们输入字符时,数据 message 会发生改变,当我们改变数据 message 时,输入框中的内容也会发生改变
v-model 其实就是一个语法糖
<input type="text" v-model="message">  | 
相当于
<input type="text" :value="message" @input="message = $event.target.value">  | 
计算属性与监听器
计算属性
我们可以在模板的插值中使用任何的 JavaScript 表达式,这可以使得我们的代码更加的灵活
<div id="app">  | 
在上面的代码中,我们在模板的插值中使用了一个较为复杂的表达式,可能需要一定的时间才能明白我们做的事情:翻转字符串。在模板中使用比较复杂的插值表达式,会使得模板过重,难以维护,我们可以使用计算属性来完成上面的目的
<div id="app">  | 
在创建 Vue 实例时,我们新增了一个 computed 属性,该属性包含多个方法,这些方法我们称为计算属性,我们可以直接在模板中直接引用这些属性,例如我们在 p 标签中直接引用了该计算属性
<p>reversedMessage: {{reversedMessage}}</p>  | 
当 Vue 解析模板时,会使用 reversedMessage 方法的返回值来替代模板中引用的计算属性。在计算属性中我们依赖了在 data 中定义的数据,当 data 中的数据发生改变时,计算属性也会相应的发生改变,所以计算属性也是与视图进行绑定的。
另外值得一提的是,计算属性是有缓存的,如果在多处访问计算属性,只会调用一次 computed 中定义的方法,然后将其结果缓存起来 ,如果在模板中有多处使用计算属性,除了第一次需要计算,后面直接使用缓存即可,所以对于一些计算复杂,耗时的任务我们便可以使用计算属性
<div id="app">  | 
在上面的代码中,我们在模板中引用了三次计算属性
<p>reversedMessage: {{reversedMessage}}</p>  | 
并且我们新增了一个数据项 count,当我们每次调用 computed 属性中的 reversedMessage 便会对 count 进行递增,虽然我们在模板中引用了三次计算属性,但是因为计算属性有缓存,所以 count 的计数应该为 1,页面显示如下
计算属性也会被挂载到 Vue 实例 vm 上,所以我们可以直接通过 vm.xxx 的形式访问计算属性
监听器
handler
在创建 Vue 实例时,我们还可以添加一个选项 watch,它可以监听 data 中定义的数据,当监听的数据发生变化时,便会执行相应的操作
<div id="app">  | 
上面的代码我们监听了数据 question
watch: {  | 
当我们向输入框输入数据时,question 就会发生改变,就会执行上述 question 中的 handler 方法,在这个方法中我们将数据 message 设置为 question 的翻转
监听器还可以接收两个参数,分别为更新前的值与更新后的值
watch: {  | 
如果监听的数据只有 handler 方法,则可以简写为如下
watch: {  | 
immediate
继续看一个很简单的代码
<div id="app">  | 
上面的代码很简单,我们设置了两个属性 message 与 reversedMessage,并且我们监听了 message,当 message 发生变化时,便会将 reversedMessage 设置为 message 的翻转。此时的页面显示为
reversedMessage 的内容为空,并不是 message 的翻转,这是因为只有当 message 发生变化时,watch 中的方法才会执行。如果我们希望立即执行,那么我们便要设置 immediate 参数为 true
watch: {  | 
此时页面显示为
deep
当我们监听一个对象或数组时,可能需要用到 deep 属性。考虑下面的场景
<div id="app">  | 
在 data 中我们定义了一个数据 user,它是一个对象,包含两个字段 username 和 gender;在模板中,我们使用插值语法使用了这两个数据,并且当我们点击 username 时,会修改 username 的值;在 watch 中我们监听了 user,当 user 发生改变时,会设置 gender 的值
上面我们点击 username 时,username 的值的确发生了改变,但是 gender 并没有发生改变,这是为什么? 这个也很容易理解,我们使用 watch 监听 user,但是当我们修改 user.username 的时候,user 根本没有发生改变,如果我们希望修改对象的属性也能被监听到,那么我们应该设置 deep 属性为 true
watch: {  | 
计算属性与监听器:
- 监听器的作用是监听数据的变化,然后触发一个行为,例如异步操作、请求数据
 - 计算属性是根据当前数据计算得到一个值
 由于计算属性具有缓存功能,所以我们一般考虑使用计算属性。
生命周期
对于一个 Vue 实例,从创建到被销毁,会经历一系列的阶段,就如同人一样,从出生到死亡,会经历幼年、青年、中年、老年等一系列的阶段。Vue 为我们提供了一些钩子函数,例如 beforeCreate 函数,当初始化数据之前会调用这个方法,又如 created 函数,当初始化数据之后会调用这个函数。
Vue 实例的完整生命周期如下图所示,现在你不需要弄懂所有的东西,但随着对 Vue 使用的深入,它的参考价值会越来越高
上图牵涉到如下钩子函数:
- beforeCreate:初始化数据之前调用该函数,在该函数中不能通过 
vm.xxx访问数据 - created:初始数据之后调用该函数,这时可以访问到数据
 - beforeMount: 渲染 
$el之前调用该函数 - mounted:渲染 
$el之后调用该函数 - beforeUpdate:数据更新之前执行该函数
 - updated:数据更新之后执行该函数
 - beforeDestroy:Vue 实例销毁之前执行该函数
 - destroyed:Vue 实例销毁之后执行该函数
 
<div id="app">  | 
上述代码中的钩子函数会按照
- beforeCreate
 - created
 - beforeMount
 - mounted
 
的顺序执行,控制台打印结果如下
当我们更新数据的时候,会重新渲染页面,在渲染页面之前会调用 beforeUpdate 函数,渲染之后会调用 updated 函数(这两个钩子函数的用处不大,很少使用)。
经过实验我发现无法在
updated钩子函数中访问$el。
比较常用的钩子函数有两个:
- created:这里可以向后端请求数据
 - mounted:在这里可以操作 DOM 元素
 
组件
一个页面一般由很多个部分组成,例如 header main footer 等部分
我们把这些部分称之为组件,通过使用组件搭建页面,就像搭积木一样搭建页面,组件可以在不同的页面直接复用(包括样式以及逻辑),复用性得到了增强,并且易于维护。
定义组件、使用组件
组件分为两种:
- 全局组件
 - 局部组件
 
通过 Vue.component 可以构建一个全局组件,下面给出一个示例
Vue.component("my-list", {  | 
第一个参数 my-list 是组件的名称,我们可以在其他组件中通过
<my-list></my-list>  | 
使用该组件。第二个参数是一个对象,包含如下选项:
- template:该组件对应的模板
 - data:组件包含的数据
 - methods:组件包含的方法
 - …
 
该对象需要设置的字段与我们创建  Vue 实例设置的字段差不多。下面给出一个使用该组件的例子
<div id="app">  | 
我们还可以通过如下声明一个局部组件
const myList = {  | 
要使用局部组件,需要事先在 components 属性进行声明
const vm = new Vue({  | 
这样才可以在模板中使用组件 my-list
<div id="app">  | 
props
在上面我们创建一个 my-list 组件,my-list 组件展示的数据是在其内部的 data 中定义的,但是它作为一个通用组件,它展示的内容应该由使用该组件的组件传入,本节讲述的便是如何向组件传入数据。
父子组件
如果一个组件
A在其内部使用了组件B,那么我们就称A是B的父组件,B是A的子组件。
传递数据
如果我们想要给组件 my-list 传入数据,我们需要通过属性 (props) 进行传入
<div id="app">  | 
在上面的代码中,我们通过为 my-list 组件的 courses 属性绑定数据来传递数据,而在 my-list 组件中要是用传入的数据,则需要在它的 props 属性中进行声明,这样才可以使用传入的数据。
传递一个对象的所有属性
如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的
v-bind(取代v-bind:prop-name)。例如,对于一个给定的对象post:
 post: {
id: 1,
title: 'My Journey with Vue'
}下面的模板:
 <component v-bind="post"></component>等价于
 <component
:id="post.id"
:title="post.title"
></component>
类型检查
props 除了可以是一个数组以外,还可以是一个对象
props: {  | 
上面的意思是,接收到的 courses 属性必须为数组。除此之外还可以对传入参数做更多的验证
props: {  | 
- type:规定传入属性的类型,可以有如下取值
- String
 - Number
 - Boolean
 - Array
 - Object
 - Date
 - Function
 - Symbol
 - Promise
 
 - required:是否是必须的
 - default:如果不传入数据,则使用该默认值
 - validator:函数,对传入的数据进行校验
 
如果传入的数据没有通过验证的话,就会在控制台打印错误,例如我们不向组件 my-list 传入数据
<my-list ></my-list>  | 
因为没有传入数据,所以使用默认值,并且因为我们要求必须传入数据 required: true,所以在控制台给出了一个警告。
注意:
props参数的验证是在实例化组件之前,因此无法在default和validator中访问组件中定义的数据(如datacomputed等)- 注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的
 prop来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态,所以不要在子组件中修改prop
$attrs
如果我们向组件传入了一个属性,但是在组件中并没有在 props 中声明这个属性,那么这个属性会被默认添加到组件的根元素上
Vue.component('my-list', {  | 
如上我们定义了一个全局组件,它没有在 props 中声明需要接收的数据,如果我们向其传递数据,那么传递的数据会被放在根元素上,也就是 div 元素上
<div id="app">  | 
const vm = new Vue({  | 
如果不希望根元素直接继承属性,那么可以在组件的选项中设置 inheritAttrs: false
Vue.component('my-list', {  | 
这时我们发现根元素上没有继承传入的属性了。但是我们可以通过 $attrs 获得父组件传入的属性,它是一个对象,我们可以自己决定将属性赋予哪个元素
Vue.component('my-list', {  | 
上面我们决定将所有接收到的且未在 props 中声明的属性全部放在 p 标签上
注意:在
props中已经声明的属性不会在$attrs中出现。
自定义事件
上面讲述了父组件向子组件传递数据,那么子组件如何向父组件传递数据呢? 假设有一个父组件,它维护着一个 courses 的数组,它有两个子组件
- course-add:用以向 
courses中添加一个数据 - course-display:用以展示 
courses中的数据 
<div id="app">  | 
我们希望当我们点击添加课程的按钮时,向父组件的 courses 添加一门课程,也就是如何将子组件的数据传递到父组件。要做到这件事情,只能通过回调函数的方式将数据传递给父组件,首先我们为组件 course-add 绑定一个自定义的事件
<course-add @add-course="addCourse"></course-add>  | 
我们为组件 course-add 绑定了 add-course 这个自定义事件,要触发该自定义事件,可通过 this.$emit('add-course') 触发,点我们点击按钮时我们便触发该事件,即
// course-add  | 
因为我们为 add-course 这个自定义事件绑定了父组件的 addCourse 方法,所以当触发 add-course 这个自定义事件时,便会调用父组件的 addCourse 方法
// 父组件  | 
我们再次捋一捋流程:
- 点击按钮,触发子组件的 
add方法 - 在 
add方法中,触发自定义事件add-course,并传递数据 - 自定义事件 
add-course绑定的处理函数为父组件的addCourse方法 - 在父组件的 
addCourse方法中我们拿到子组件传递的数据,并添加到courses中 
踩坑:HTML 的属性大小写是不敏感的,会一律转化为小写。如果你为组件添加如下自定义事件
addCourse,那么它会被转化为小写的形式,即addcourse,所以如果你通过this.$emit('addCourse')触发事件的话是不会成功的,所以一律推荐使用kebab-case命名,即add-course。
双向绑定
如果我们为组件绑定 v-model 时,它实际上自动绑定了 value 属性,以及添加了一个名为 input 的自定义事件
Vue.component('base-input', {  | 
我们下面可以向使用正常表单一样使用该组件
<div id="app">  | 
const vm = new Vue({  | 
因为向单选框、复选框这样的输入控件可能会将 value 用于不同的目的,我们可以通过 model 属性避免这样的冲突
Vue.component('base-checkbox', {  | 
上面我们在 model 属性中声明绑定的属性为  checked 以及绑定的自定义事件为 change。
动态组件
我们可以通过 <component> 以及 :is 属性来动态的切换组件
<div id="app">  | 
当我们点击单选框时,会修改 currentComponent 的值,而 component 会根据 currentComponent 选择展示什么组件,例如当我们点击第一个单选框时,currentComponent 的值会被设置为 component-a,因此 <component> 会展示 component-a 这个组件。在下面我们定义用到的组件
Vue.component('component-a', {  | 
插槽
插槽内容
除了可以通过 prop 向组件传递数据,我们还可以通过插槽向组件传递数据,例如
<layout>  | 
其中 content 便是传递的数据,它会被分发到 layout 组件中特定的位置中。假设 layout 组件的模板如下
<div>  | 
那么该模板会被渲染为
<div>  | 
content 会替换 layout 中的 <slot></slot> 标签,slot 便是插槽的意思,它就是一个占位置的。
后备插槽
如果没有组件传入分发的内容,我们希望使用默认值,这时便可以使用后背插槽
<div>  | 
slot 标签中的内容就是默认值,当我们没有为 layout 组件传入内容时便会使用默认值,如
<layout></layout>  | 
会被渲染为
<div>  | 
具名插槽
有时候我们需要多个插槽,有如下 layout 组件
<div>  | 
这个时候我们就要为 slot 标签指定 name 属性,以区别不同的插槽
<div>  | 
上述我们没有为 main 中的 slot 指定 name 属性,它会有一个默认的名称 default。接下来我们如下使用 layout 组件进行组件分发
<layout>  | 
layout 组件最终会被渲染为
<div>  | 
同 v-on 与 v-bind 一样,v-slot 也有缩写,我们可以把上述的 v-slot: 替换为 #
<layout>  | 
作用域插槽
如果能在插槽中访问子组件才有的数据是很有用的,假设有如下的 <current-user> 组件
<span>  | 
我们希望能够访问 current-user 组件中的 user 数据
<current-user>  | 
但是上述访问的是 current-user 父组件的数据,所以上面的代码不会正常工作。为了访问到子组件的数据,我们可以为 slot 元素绑定一个属性
<span>  | 
接着我们便可以以下面的方式访问 current-user 的数据了
<current-user>  | 
如果 current-user 中只有默认插槽的话,上面的代码可以简写为
<current-user v-slot="slotProps">  | 
另外我们可以通过结构语法,从 slotProps 中解构出 user,如
<current-user v-slot="{user}">  | 
动画
Vue 在插入、更新、移除 DOM 元素时,提供不同的方式应用过渡效果。
transition
Vue 提供了 transition 组件,可以为任何组件以及元素提供进入、离开过渡,下面给出一个例子
<div id="app">  | 
现在点击按钮没有任何的过渡效果,现在我们为 p 标签外面添加 transition 组件
<div id="app">  | 
为元素包裹 transition 组件以后,Vue 会做如下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名
 - 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用
 - 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行
 
CSS 动画
如果我们使用了 CSS 动画,在元素插入之前, trasnition 组件会为包裹元素添加 v-enter 类,其中 v 就是在 transition 指定的 name 属性值,如上面的 name 属性值为 fade,所以会添加 fade-enter 类,该类用以定义动画的初始状态,在元素插入之后会被移除;在插入的过程中,会添加一个 v-enter-active 以及 v-enter-to类,v-enter-active 为元素定义过渡状态,在动画完成之后被移除, v-enter-to 用以定义过渡结束时的状态,也是在动画完成之后被移除。
对于离场动画也是同理,在离场之前会添加一个 v-leave 类,用以定义动画的初始状态,动画开始后被移除,在离场的过程中会添加 v-leave-active 和 v-leave-to 类,分别用以动画的过渡状态以及动画最终状态。
.fade-enter, .fade-leave-to {  | 
效果如下:
除了可以使用 transition 属性定义过渡状态,还可以使用 animation 动画,例如
.bounce-in-enter-active{  | 
使用 CSS 动画库
我们可以通过以下属性自定义过渡类名:
- enter-class
 - enter-active-class
 - enter-to-class
 - leave-class
 - leave-active-class
 - leave-to-class
 
这些类的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">  | 
使用 JS 动画库
Vue 过渡系统还提供了一系列的钩子函数:
- before-enter:进入之前
 - enter:进入的时候
 - after-enter:进入完成
 - before-leave:离开之前
 - leave:离开时
 - after-leave:离开之后
 
这些钩子函数会在特定的阶段触发,我们可以配合 JS 动画库来实现动画效果
<div id="app">  | 







