唐抉的个人博客

前端框架之Vue.js(五)

字数统计: 6.8k阅读时长: 33 min
2022/11/10

深入了解组件

动态组件&异步组件

在动态组件上使用keep-alive

之前曾在一个多标签的页面使用is attribute来切换不同的组件:

1
<component v-bind:is="currentTabComponent"></component>

当这些组件之间来回切换时,会发现原来点击的内容被销毁了,由于 每次切换新标签时,Vue都创建了一个新实例,因此切回去时是不会保留之前选择的选项的。

若想把那些标签的组件实例能够被在它们第一次被创建时缓存下来,可以使用<keep-alive>元素把动态组件包裹起来,如:

1
2
3
4
<!-- 失活的组件将会被缓存 -->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

动态组件及使用keep-alive的例子如下:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<body>
<div id="example" class="demo">

<button
v-for="tab in tabs"
v-bind:key="tab"
v-on:click="currentTab=tab"
>
{{tab}}
</button>
<!-- <component v-bind:is="currentTabComponent"></component> -->

<!-- 失活的组件将会被缓存 -->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
</div>
</body>
<script>
var postOne={
id:1,
title:'Cat Ipsum',
content:'<p>That is a content of Cat Ipsum </p>'
};
var postTwo={
id:2,
title:'Hipster Ipsum',
content:'<p>That is a content of Hipster Ipsum </p>'
};
var postThree={
id:3,
title:'Cupcake Ipsum',
content:'<p>That is a content of Cupcake Ipsum </p>'
};
Vue.component('tab-posts',{
data:function(){
return{
posts:[postOne,postTwo,postThree],
selectedPost:null
}
},
template:`
<div>
<ul>
<li
v-for="post in posts"
v-bind:key="post.id"
v-on:click="selectedPost=post"
>
{{post.title}}
</li>
</ul>
<div>
<div v-if="selectedPost">
<div v-html="selectedPost.content"></div>
</div>
<strong v-else>
Click on a blog title to the left to view it.
</strong>
</div>
</div>
`
})
Vue.component('tab-archive',{
template:'<div>Archive component</div>'
})
new Vue({
el:'#example',
data:{
currentTab:'Posts',
tabs:['Posts','Archive']
},
computed:{
currentTabComponent:function(){
return 'tab-'+this.currentTab.toLowerCase()
}
}
})
</script>

<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

若使用v-if也想保留组件内容,可以用<keep-alive>包裹:

1
2
3
4
<keep-alive>
<component-a v-if="a>1"></component-a>
<component-b v-else></component-b>
</keep-alive>

若想使用include&exclude属性也保存组件内容,也能用<keep-alive>包裹。其中,与include的值匹配的路由/组件会被缓存,不匹配的不会被缓存

1
2
3
<keep-alive include="tab-archive">
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

可以通过逗号分隔的字符串形式、正则形式、数组形式进行匹配:

1
2
3
<keep-alive include="['tab-archive','tab-posts']">
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

还可以利用<keep-alive>来设计最多可以缓存多少组件实例,一旦这个数字达到了,则在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁

1
2
3
<keep-alive max="10">
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

异步组件

在大型应用中,可能需要将应用分割成小一些的代码块,并且只在需要时才从服务器加载一个模块。

为了简化,Vue允许以一个工厂函数的方式定义组件,这个工厂函数会异步解析组件的定义,只在这个组件需要被渲染时才会触发该工厂函数,并把结果缓存起来供后续重渲染。

1
2
3
4
5
6
7
8
Vue.component('async-example',function(resolve,reject){
seTimeout(function(){
//向resolve回调传递组件定义
resolve({
template:'<div> I am async! </div>'
})
},1000)
})

上述代码的工厂函数会收到一个resolve回调,这个回调函数会在从服务器得到组件定义时被调用,也可以调用reject(reason)来表示加载失败。

将异步组件和webpack的code-splitting功能一起配合使用时,可以自动将所构建的代码切割成多个包,这些包会通过Ajax请求加载:

1
2
3
4
Vue.component('async-webpack-example',function(resolve){
//告诉webpack自动切割构建代码
require(['./my-async-component'],resolve)
})

将webpack 2和ES2015语法结合使用动态导入时,可以在工厂中返回一个Promise

1
2
3
4
5
Vue.component(
'async-webpack-example',
//这个动态导入会返回一个Promise对象
()=>import('./my-async-component')
)

当使用局部注册时,也可以直接提供一个返回Promise的函数:

1
2
3
4
5
6
new Vue({
...
components:{
'my-component':()=>import('./my-async-component')
}
})

处理加载状态

异步组件工厂函数也可以返回一个如下格式的对象:

1
2
3
4
5
6
7
const AsyncComponent=()=>({
component:import('./MyComponent.vue'),//需要加载的组件,返回一个promise对象
loading:LoadingComponent,//异步组件加载时使用的组件
error:ErrorComponent,//加载失败时使用的组件
delay:200,//展示加载时组件的延时时间,默认值是200毫秒
timeout:3000//若提供了超时时间且组件也加载超时了,则使用加载失败时使用的组件。默认值是infinity
})

处理边界情况

访问元素&组件

访问根实例

在每个new Vue实例的子组件中,其根示例可以通过$root property进行访问,如this.$root.data

访问父级组件实例

可以通过$parent property来从一个子组件访问父组件的实例,如this.$parent.getData

访问子组件实例或子元素

在JavaScript里直接访问一个子组件,可以先通过ref attribute为子组件赋予一个ID引用:

1
<base-input ref="usernameInput"></base-input>

然后便可以使用this.$refs.usernameInput来访问这个<base-input>实例。

注意:refv-for一起使用时,得到的ref将会是一个包含了对应数据源的子组件数组。

$refs只会在组件渲染完成后生效,且它们不是响应式的,因此应该避免在模板或计算属性中访问$refs

依赖注入

依赖注入可以将子组件的共用方法拓展到更深层级的嵌套组件上,使得任意后代组件中都能访问该方法,且不需要暴露该组件实例。

依赖注入用到了两个实例选项:provideinject

provide选项允许指定想要提供给后代组件的数据/方法。

inject选项用于接收指定的想要添加在实例上的property。

1
2
3
4
5
6
provide:function(){
return{
getMap:this.getMap
}
},
inject:['getMap']

程序化的事件侦听器

处理可以被v-on侦听的$emit外,Vue实例还同时在其事件接口提供了其他的方法:

  • $on(eventName,eventHandler):侦听一个事件
  • $once(eventName,eventHandler):一次性侦听一个事件
  • $off(eventName,eventHandler):停止侦听一个事件

循环引用

递归组件

组件可以通过name选项在自己的模板中调用自身。当使用Vue.component全局注册一个组件时,全局的ID会自动设置为该组件的name选项。

组件之间的循环引用

有两个组件A和组件B,A依赖B,B又依赖A,两者之间反复进行循环引用,模块系统不知道要如果不经过其中一个组件而完全解析另一个组件而报错。

为了解决这个问题,需要把其中一个组件设为一个点,如将<tree-folder>组件设为哪个点,另一个子组件为<tree-folder-contents>,此时会等到生命周期钩子beforeCreate时去注册它:

1
2
3
beforeCreate:function(){
this.$options.components.TreeFolderContents=require('./tree-folder-contents.vue').default
}

或者在本地注册组件时,用webpack的异步import来解决:

1
2
3
components:{
TreeFolderContents:()=>import('./tree-folder-contents.vue')
}

模板定义的替代品

内联模板

当子组件中出现inline-template attribute时,这个组件将会使用其里面的内容作为模板,而不是作为被分发的内容:

1
2
3
4
5
<my-component inline-template>
<div>
<p>123</p>
</div>
</my-component>

内联模板需定义在Vue所属的DOM元素内。

X-Template

另一个定义模板的方式是在一个<script>元素中,并为其带上text/x-template的类型,然后通过一个id将模板引用过去:

1
2
3
<script type="text/x-template" id="hello-world-template">
<p>Hello world </p>
</script>

控制更新

强制更新

可以使用$forceUpdate来进行强制更新。

通过v-once创建低开销的静态组件

在包括了大量静态内容的组件里,可以在其根元素上添加v-once attribute来确保这些内容只计算一次后缓存起来。

过渡&动画

进入/离开&列表过渡

单元素/组件的过渡

Vue提供了transition封装组件,在以下情形中,可以给任何元素和组件添加进入/离开过渡:

  • 条件渲染(使用v-if
  • 条件展示(使用v-show
  • 动态组件
  • 组件根节点

例如:

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
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="demo">
<button v-on:click="show=!show">Toggle</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
</body>
<script>
new Vue({
el:'#demo',
data:{
show:true
}
})
</script>
<style>
.fade-enter-active,.fade-leave-active{
transition:opacity .5s;
}
.fade-enter,.fade-leave-to{
opacity: 0;
}
</style>
</html>

当插入或删除包含在transition组件中的元素时,Vue将会做以下处理:

  1. 自动嗅探目标元素是否应用了CSS过渡或动画,若是,在恰当的时机添加/删除CSS类名
  2. 若过渡组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用
  3. 若没有找到JavaScript钩子函数,也没有检测到CSS过渡/动画,DOM操作(插入/删除)在下一帧中立即执行

过渡的类名

在进入/离开的过渡中,会有6个class切换:

  • v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间、延迟和曲线函数。
    • v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除
    • v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效,在过渡/动画完成之后移除
  • v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间、延迟和曲线函数。
    • v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除
    • v-leave-to:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效,在过渡/动画完成之后移除

对于在过渡切换的来命名来说,若使用的是没有名字的<transition>,则v-是这些类名的默认前缀。若使用了<transition name="my-transition">,那么v-enter会替换成my-transition-enter

CSS过渡

常用的过渡都是使用CSS过渡。

例如:

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
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="demo">
<button @click="show=!show">Toggle render</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
</body>
<script>
new Vue({
el:'#demo',
data:{
show:true
}
})
</script>
<style>
.slide-fade-enter-active{
transition:all .3s ease;
}
.slide-fade-leave-active{
transition:all .8s cubic-bezier(1.0,0.5,0.8,1.0);
}
.slide-fade-enter,.slide-fade-leave-to{
transform: translateX(10px);
opacity: 0;
}
</style>
</html>

CSS动画

CSS动画用法与CSS过渡相同,其区别是在动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend时间触发时删除。

例如:

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
40
41
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="demo">
<button @click="show=!show">Toggle show</button>
<transition name="bounce">
<p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.</p>
</transition>
</div>
</body>
<script>
new Vue({
el:'#demo',
data:{
show:true
}
})
</script>
<style>
.bounce-enter-active{
animation: bounce-in .5s;
}
.bounce-leave-active{
animation: bounce-in .5s reverse;
}
@keyframes bounce-in{
0%{
transform: scale(0);
}
50%{
transform: scale(1.5);
}
100%{
transform: scale(1);
}
}
</style>
</html>

自定义过渡的类名

可以通过以下attribute来自定义过渡类名:

  • enter-active-class
  • enter-class
  • enter-to-class
  • leave-active-class
  • leave-class
  • leave-to-class

这些attribute的优先级高于普通的类名,这对Vue的过渡系统和其他第三方CSS动画库的使用十分有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<div id="demo">
<button @click="show=!show">Toggle render</button>
<transition
name="custom-classes-transition"
enter-active-class="animated data"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
</body>
<script>
new Vue({
el:'#demo',
data:{
show:true
}
})
</script>

同时使用过渡和动画

Vue为了知道过渡的完成,必须设置相应的事件监听器。其可以是transitionendanimationend,这取决于给元素应用的CSS规则,若使用其中任何一种,Vue能自动识别类型并设置监听。

若给同一种元素同时设置两种过渡动效时,需要使用type attribute来设置animationendtransitionend来明确声明需要Vue监听的类型。

显性的过渡持续时间

大多数情况下,Vue可以自动得出过渡效果的完成时机。默认情况下,Vue会等待其在过渡效果根元素的第一个transitionendanimationend时间。

也可以使用transition组件上的duration prop来定制一个显性的过渡持续时间(以毫秒为单位):

1
2
3
<transition :duration="1000">...</transition>   
<!-- 也可以定制进入和移除的持续时间 -->
<transition :duration="{enter:500,leave:800}">...</transition>

JavaScript钩子

可以在attribute中声明JavaScript钩子:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<body>
<div id="demo">
<button @click="show=!show">Toggle render</button>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"

v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<p v-if="show">hello</p>
</transition>
</div>
</body>
<script>
new Vue({
el:'#demo',
data:{
show:true
},
methods:{
//进入中
beforeEnter:function(el){
//...
},
//当与CSS结合使用时,回调函数done是可选的
enter:function(el,done){
//...
done()
},
afterEnter:function(el){
//...
},
enterCancelled:function(el){
//...
},
//离开时
beforeLeave:function(el){
//...
},
//当与CSS结合使用时,回调函数done是可选的
leave:function(el,done){
//...
done()
},
afterLeave:function(el){
//...
},
leaveCancelled:function(el){
//...
},
}
})
</script>

这些钩子函数可以结合CSS transitions/animations使用,也可以单独使用。

当只用JavaScript过渡时,在enterleave中必须使用done进行回调,否则两者将被同步调用,过渡会立即完成。

对于仅使用JavaScript过渡的元素添加v-bind:css="false",Vue会跳过CSS的检测。这可以避免过渡过程中CSS的影响。

初始渲染的过渡

可以通过appear attribute来设置节点在初始渲染的过渡:

1
2
3
4
5
6
7
8
9
 <!-- 可以自定义CSS类名,也可以自定义JavaScript钩子 -->
<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class"(2.1.8)
appear-active-class="custom-appear-active-class"
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook">
</transition>

多个元素的过渡

对于原生标签可以使用v-if/v-else来实现多个组件的过渡,最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:

1
2
3
4
5
<transition>
<!-- 当有相同标签名的元素切换时,需要通过key attribute来设置唯一的值来标记 -->
<buttion v-if="isEditing" key="save">Save</buttion>
<buttion v-else key="edit">Edit</buttion>
</transition>

在一些场景中,也可以通过给同一个元素的key attribute设置不同的状态来代替v-ifv-else

1
2
3
4
5
<transition>
<buttion v-bind:key="isEditing">
{{isEdting?"Save":"Edit"}}
</buttion>
</transition>

使用多个v-if的多个元素过渡,可以重写为绑定了动态property的单个元素过渡,如:

1
2
3
4
5
6
<transition>
<buttion v-if="docState==='saved'" key="saved">Edit</buttion>
<buttion v-if="docState==='edited'" key="edited">Save</buttion>
<buttion v-if="docState==='editing'" key="editing">Cancel</buttion>
</buttion>
</transition>

可以重写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<transition>
<button v-bind:key="docState">
{{buttonMessage}}
</button>
</transition>

<script>
//...
computed:{
buttonMessage:function(){
switch(this.docSate){
case 'saved':return 'Edit'
case 'edited':return 'Save'
case 'editing':return 'Cancel'
}
}
}
</script>

过渡模式

由于同时生效的进入和离开的过渡不能满足所有要求,因此Vue提供了过渡模式:

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开
  • out-in:当前元素先进行过渡,完成之后新元素过渡进入
1
<transition name="fade" mode="out-in"></transition>

多个组件的过渡

只需要使用动态组件便可以使用key attribute:

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
<body>
<div id="demo">
<input type="radio" @click="view='v-a'" name="view" checked>
<label for="a">A</label>
<input type="radio" @click="view='v-b'" name="view">
<label for="b">B</label>
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
</div>
</body>
<script>

new Vue({
el:'#demo',
data:{
view:'v-a'
},
components:{
'v-a':{
template:'<div>Component A</div>'
},
'v-b':{
template:'<div>Component B</div>'
}
}
})
</script>
<style>
.component-fade-enter-active,.component-fade-leave-active{
transition:opacity .3s ease;
}
.component-fade-enter,.component-fade-leave-to{
opacity: 0;
}
</style>

列表过渡

需要同时渲染整个列表时,使用<transition-group>组件,这个组件的特点如下:

  • 不同于<transition>,它会以一个真实元素呈现:默认为一个<span>,也可以通过tag attribute更换为其他元素
  • 过渡模式不可用
  • 内部元素总是需要提供一个唯一的key attribute
  • CSS过渡的类将会应用在内部的元素中,而不是这个组/容器本身。

列表的进入/离开过渡

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
40
41
42
43
44
45
46
<body>
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{item}}
</span>
</transition-group>
</div>
</body>

<script>
new Vue({
el:'#list-demo',
data:{
items:[1,2,3,4,5,6,7,8,9],
nextNum:10
},
methods:{
randomIndex:function(){
return Math.floor(Math.random()*this.items.length)
},
add:function(){
this.items.splice(this.randomIndex(),0,this.nextNum++)
},
remove:function(){
this.items.splice(this.randomIndex(),1)
},
}
})
</script>

<style>
.list-item{
display: inline-block;
margin-right: 10px;
}
.list-enter-active,.list-leave-active{
transition: all 1s;
}
.list-enter,.list-leave-to{
opacity: 0;
transform: translateY(30px);
}
</style>

列表的排序过渡

<transition-group>组件不仅可以进入和离开动画,还可以使用v-model class改变定位。对于v-model class,可以通过name attribute来自定义前缀,也可以通过move-class attribute手动设置。

v-model class对于设置过渡的切换时机和过渡曲线非常有用,以下例子使用了一个FLIP的简单动画队列,使用transforms将元素从之前的位置平滑过渡到新位置中:

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
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
</head>

<body>
<div id="flip-list-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<transition-group name="flip-list" tag="ul">
<li v-for="item in items" v-bind:key="item">
{{item}}
</li>
</transition-group>
</div>
</body>

<script>
new Vue({
el:'#flip-list-demo',
data:{
items:[1,2,3,4,5,6,7,8,9],
},
methods:{
shuffle:function(){
this.items=_.shuffle(this.items)
}
}
})
</script>

<style>
.flip-list-move{
transition: transform 1s;
}
</style>

将上述代码与列表进入/离开过渡的例子结合,可式列表的一切变动都会有动画过渡:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
</head>

<body>
<div id="list-complete-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list-complete" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-complete-item">
{{item}}
</span>
</transition-group>
</div>
</body>

<script>
new Vue({
el:'#list-complete-demo',
data:{
items:[1,2,3,4,5,6,7,8,9],
nextNum:10
},
methods:{
randomIndex:function(){
return Math.floor(Math.random()*this.items.length)
},
add:function(){
this.items.splice(this.randomIndex(),0,this.nextNum++)
},
remove:function(){
this.items.splice(this.randomIndex(),1)
},
shuffle:function(){
this.items=_.shuffle(this.items)
}
}
})
</script>

<style>
.list-complete-item{
transition: all 1s;
display: inline-block;
margin-right: 10px;
}
.list-complete-leave-active{
position: absolute;
}
.list-complete-enter,.list-complete-leave-to{
opacity: 0;
transform: translateY(30px);
}
</style>

</html>

注意:使用FLIP过渡的元素不能设置为display:inline,可以设为display:inline-block或者放置于flex中。

列表的交错过渡

通过data attribute与JavaScript通信,可以实现列表的交错过渡:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
</head>

<body>
<div id="staggered-list-demo">
<input v-model="query">
<transition-group
name="staggered-fade"
tag="ul"
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<li
v-for="(item,index) in computedList"
v-bind:key="item.msg"
v-bind:data-index="index"
>{{item.msg}}</li>
</transition-group>
</div>
</body>

<script>
new Vue({
el:'#staggered-list-demo',
data:{
query:'',
list:[
{msg:'Bruce Lee'},
{msg:'Jackie Chan'},
{msg:'Chuck Norris'},
{msg:'Jet Li'},
{msg:'Kung Fury'},
]
},
computed:{
computedList:function(){
var vm=this
return this.list.filter(function(item){
return item.msg.toLowerCase().indexOf(vm.query.toLowerCase())!==-1
})
}
},
methods:{
beforeEnter:function(el){
el.style.opacity=0
el.style.height=0
},
enter:function(el,done){
var delay=el.dataset.index*150
setTimeout(function(){
Velocity(
el,
{opacity:1,height:'1.6em'},
{complete:done}
)
},delay)
},
leave:function(el,done){
var delay=el.dataset.index*150
setTimeout(function(){
Velocity(
el,
{opacity:0,height:0},
{complete:done}
)
},delay)
}
}
})
</script>

</html>

可复用的过渡

过渡可以通过Vue的组件系统实现复用。创建一个可复用过渡组件,只需将<transition><transition-group>作为根组件,然后将任何子组件放置在其中即可:

动态过渡

在Vue中即使是过渡也是数据驱动的,通过name attribute来绑定动态之是动态过渡最基本的例子:

1
<transition v-bind:name="transitionName"></transition>

使用Vue的过渡系统来定义CSS过渡/动画在不同过渡间切换会非常有用。

所有过渡attribute都可以动态绑定,不仅只有attribute可以利用,还可以通过事件钩子获取上下文中的所有数据,即可实现根据组件状态不同,JavaScript过渡也会有不同的表现。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
</head>

<body>
<div id="dynamic-fade-demo" class="demo">
Fade In:<input type="range" v-model="fadeInDuration" min="0" v-bind:max="maxFadeDuration">
Fade Out:<input type="range" v-model="fadeOutDuration" min="0" v-bind:max="maxFadeDuration">
<transition
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
>
<p v-if="show">hello</p>
</transition>
<button
v-if="stop"
v-on:click="stop=false;show=false"
>Start animating</button>
<button
v-else
v-on:click="stop=true"
>Stop it!</button>
</div>
</body>

<script>
new Vue({
el:'#dynamic-fade-demo',
data:{
show:true,
fadeInDuration:1000,
fadeOutDuration:1000,
maxFadeDuration:1500,
stop:true
},
mounted:function(){
this.show=false
},
methods:{
beforeEnter:function(el){
el.style.opacity=0
},
enter:function(el,done){
var vm=this
Velocity(el,
{opacity:1},
{
duration:this.fadeInDuration,
complete:function(){
done()
if(!vm.stop) vm.show=false
}
}
)
},
leave:function(el,done){
var vm=this
Velocity(el,
{opacity:0},
{
duration:this.fadeOutDuration,
complete:function(){
done()
vm.show=true
}
}
)
}
}
})
</script>
</html>

创建动态过渡的最终方案是组件通过接收props来动态修改之前的过渡。

CATALOG
  1. 1. 深入了解组件
    1. 1.1. 动态组件&异步组件
      1. 1.1.1. 在动态组件上使用keep-alive
      2. 1.1.2. 异步组件
        1. 1.1.2.1. 处理加载状态
    2. 1.2. 处理边界情况
      1. 1.2.1. 访问元素&组件
        1. 1.2.1.1. 访问根实例
        2. 1.2.1.2. 访问父级组件实例
        3. 1.2.1.3. 访问子组件实例或子元素
        4. 1.2.1.4. 依赖注入
      2. 1.2.2. 程序化的事件侦听器
      3. 1.2.3. 循环引用
        1. 1.2.3.1. 递归组件
        2. 1.2.3.2. 组件之间的循环引用
      4. 1.2.4. 模板定义的替代品
        1. 1.2.4.1. 内联模板
        2. 1.2.4.2. X-Template
      5. 1.2.5. 控制更新
        1. 1.2.5.1. 强制更新
        2. 1.2.5.2. 通过v-once创建低开销的静态组件
  2. 2. 过渡&动画
    1. 2.1. 进入/离开&列表过渡
      1. 2.1.1. 单元素/组件的过渡
        1. 2.1.1.1. 过渡的类名
        2. 2.1.1.2. CSS过渡
        3. 2.1.1.3. CSS动画
        4. 2.1.1.4. 自定义过渡的类名
        5. 2.1.1.5. 同时使用过渡和动画
        6. 2.1.1.6. 显性的过渡持续时间
        7. 2.1.1.7. JavaScript钩子
      2. 2.1.2. 初始渲染的过渡
      3. 2.1.3. 多个元素的过渡
        1. 2.1.3.1. 过渡模式
      4. 2.1.4. 多个组件的过渡
      5. 2.1.5. 列表过渡
        1. 2.1.5.1. 列表的进入/离开过渡
        2. 2.1.5.2. 列表的排序过渡
        3. 2.1.5.3. 列表的交错过渡
      6. 2.1.6. 可复用的过渡
      7. 2.1.7. 动态过渡