transition 动画与过渡的实现

在 Vue 中推荐使用 CSS3 来完成动画效果。当在插入、更新或从 DOM 中移除项时,Vue 提供了多种应用转换效果的方法。

transition 动画

Vue 中通过两个内置的组件来实现动画与过渡效果,分别是:<transition><transition-group>,代码如下:

<template>
  <div>
    <h2>hello transition</h2>
    <button @click=" isShow = !isShow ">点击</button>
    <transition name="slide" mode="out-in">
      <div v-if="isShow" class="box"></div>
      <div v-else class="box2"></div>
    </transition>
  </div>
</template>
<script>
  export default {
    data(){
      return {
        isShow: true
      }
    }
  }
</script>
<style scoped>
.box{
  width: 200px;
  height: 200px;
  background: skyblue;
}
.box2{
  width: 200px;
  height: 200px;
  background: pink;
}
.slide-enter-from{
  opacity: 0;
  transform: translateX(200px);
}
.slide-enter-to{
  opacity: 1;
  transform: translateX(0);
}
.slide-enter-active{
  transition: 1s;
}

.slide-leave-from{
  opacity: 1;
  transform: translateX(0);
}
.slide-leave-to{
  opacity: 0;
  transform: translateX(200px);
}
.slide-leave-active{
  transition: 1s;
}
</style>


其中<transition>组件通过name属性去关联 CSS 中的选择器,CSS 中的选择器主要有 6 种,分别:

  • v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
  • v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
  • v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
  • v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
  • v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
  • v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

默认情况下,进入和离开在两个元素身上是同时执行的,如果想改变其顺序,需要用到mode属性,其中out-in表示先离开再进入,而in-out表示先进入再离开。

动态组件与 keep-alive 组件缓存

动态组件

动态组件可以实现在同一个容器内动态渲染不同的组件,依一个内置组件<component>is属性的值,来决定使用哪个组件进行渲染。

<template>
  <div>
    <h2>动态组件</h2>
    <button @click=" nowCom = 'my-com1' ">组件1</button>
    <button @click=" nowCom = 'my-com2' ">组件2</button>
    <button @click=" nowCom = 'my-com3' ">组件3</button>
 	<component :is="nowCom"></component>
  </div>
</template>

<script>
import MyCom1 from '@/13_MyCom1.vue'
import MyCom2 from '@/14_MyCom2.vue'
import MyCom3 from '@/15_MyCom3.vue'
  export default {
    data(){
      return {
        nowCom: 'my-com1'
      }
    },
    components: {
      'my-com1': MyCom1,
      'my-com2': MyCom2,
      'my-com3': MyCom3
    }
  }
</script>


keep-alive 组件

当我们点击的时候,就会进行组件的切换。在每次切换的过程中都会重新执行组件的渲染,这样组件操作的行为就会还原,而我们如何能够保证组件不变呢?可以利用<keep-alive>对组件进行缓存,这样不管如何切换,都会保持为初始的组件渲染,这样可以很好的保留之前组件的行为。

组件的切换也可以配合<transition>完成动画的切换。

<template>
  <div>
    <h2>动态组件</h2>
    <button @click=" nowCom = 'my-com1' ">组件1</button>
    <button @click=" nowCom = 'my-com2' ">组件2</button>
    <button @click=" nowCom = 'my-com3' ">组件3</button>
    <transition name="slide" mode="out-in">
      <keep-alive>
        <component :is="nowCom"></component>
      </keep-alive>
    </transition>
  </div>
</template>


异步组件与 Suspense 一起使用

异步组件

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

在上一个小节的动态组件的基础上,进行异步组件的演示。首先可以打开 chrome 浏览器的 network 网络,可以观察到在动态组件切换的时候,network 网络中没有进行任何请求的加载,这证明了在初始的时候,相关的动态组件就已经加载好了。

所以对于大型项目来说,如果能实现按需载入的话,那么势必会对性能有所提升,在 Vue 中主要就是利用 defineAsyncComponent 来实现异步组件的。

<script>
import { defineAsyncComponent } from 'vue'
export default {
    data(){
        return {
            nowCom: 'my-com1'
        }
    },
    components: {
        'my-com1': defineAsyncComponent(() => import('@/MyCom1.vue')),
        'my-com2': defineAsyncComponent(() => import('@/MyCom2.vue')),
        'my-com3': defineAsyncComponent(() => import('@/MyCom3.vue'))
    }
}
</script>


Suspense 组件

由于异步组件是点击切换的时候才去加载的,所以可能会造成等待的时间,那么这个时候可以配合一个 loading 效果,在 Vue 中提供了一个叫做<Suspense>的组件用来完成 loading 的处理。

<template>
	<suspense>
        <component :is="nowCom"></component>
        <template #fallback>
            <div>loading...</div>
		</template>
	</suspense>
</template>


跨组件间通信方案 Provide_Inject

跨组件通信方案

正常情况下,我们的组件通信是需要一级一级的进行传递,通过父子通信的形式,那么如果有多层嵌套的情况下,从最外层把数据传递给最内层的组件就非常的不方便,需要一级一级的传递下来,那么如何才能方便的做到跨组件通信呢?

可以采用 Provide 和 inject 依赖注入的方式来完成需求,代码如下:

// provide.vue
<script>
export default {
    provide(){
        return {
            message: 'hello provide', //传递数据
            count: this.count,        //传递响应式数据
            getInfo(data){            //获取数据
                console.log(data);
            }
        }
    }
}
</script>

// inject.vue
<template>
<div>
    hello inject, {{ message }}, {{ count }}
 </div>
</template>

<script>
export default {
    inject: ['message', 'getInfo', 'count'],
    mounted(){
        this.getInfo('hello inject');
    }
}
</script>


Provide 与 Inject 注意点

  • 保证数据是单向流动的,从一个方向进行数据的修改
  • 如果要传递响应式数据,需要把 provide 改造成工厂模式发送数据
export default {
    //普通模式,无法传递响应式数据
    //provide: {
    //    message: 'hello provide',
    //    getInfo(data) {
    //        console.log(data);
    //    }
    //}
    
    //工厂模式,可以传递响应式数据
    provide(){
        return {
            message: 'hello provide', //传递数据
            count: this.count,        //传递响应式数据
            getInfo(data){            //获取数据
                console.log(data);
            }
        }
    }
}


Teleport 实现传送门功能

Teleport 组件

Teleport 可以实现传送门功能,也就是说逻辑属于当前组件中,而结构需要在组件外进行渲染,例如:按钮模态框组件。

// 模态框.vue
<template>
  <div>
    <button @click=" isShow = true ">点击</button>
    <teleport to="body">
      <div v-if="isShow">模态框</div>
    </teleport>
  </div>
</template>

<script>
  export default {
    data(){
      return {
        isShow: false
      }
    }
  }
</script>
// 调用模态框.vue
<template>
  <div>
    <h2>传送门</h2>
    <my-modal></my-modal>
  </div>
</template>

<script>
import MyModal from '@/模态框.vue'
  export default {
    components: {
      'my-modal': MyModal
    }
  }
</script>


这样渲染出的模态框组件就是在 body 下,而不是在 button 按钮下面。我们经常会有这种需求,比如需要组件的位置相对于 body 偏移而不是当前组件。

逻辑组件

但是往往我们需要的并不是普通组件的调用方式,而是逻辑组件的调用方式,那么如何实现逻辑组件呢?代码如下:

//  定义逻辑组件,modal.js

import { createApp } from 'vue';
import ModalVue from '@/模态框.vue';

function modal(){
  let div = document.createElement('div');
  createApp(ModalVue).mount(div);
  document.body.append(div);
}

export default modal;


// 调用逻辑组件
<template>
  <div>
    <h2>传送门</h2>
    <button @click="handleClick">点击</button>
  </div>
</template>

<script>
import modal from '@/modal.js'
export default {
  methods: {
    handleClick(){
      modal();
    }
  }
}
</script>


Vue 是一个渐进式框架,也就是说一个网页可以用 Vue,也可以不用 Vue。也可以一个页面采用两个容器,分别创建 Vue 的实例对象。所以初始有一个<div></div>容器,可以创建一个 vue 实例。我们自己创建一个 div 作为容器,也可以再次创建一个 vue 实例,那么两个实例都可以完成 Vue 的功能,互相不影响。这里就是创建了一个新的实例作为模态框应用。