本篇文章的目的是快速的入门 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
中访问组件中定义的数据(如data
computed
等)- 注意在 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"> |