動態組件

語法

有些場景會需要在兩個組件間來回切換,比如 Tab 界面, 可通過<component>動態組件實現

<!-- Home為組件名 -->
<component is="Home"></component>
<component :is="'Home'"></component>
<!-- currentTab 改變時組件也改變 -->
<component :is="currentTab"></component>

案例

Home.vue

<template>
    <div class="tab">
        Home component
    </div>
</template>

Posts.vue

<template>
    <div class="tab">
        Posts component
    </div>
</template>

Archive.vue

<template>
    <div class="tab">
        Archive component
    </div>
</template>

App.vue

<script>
import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'

export default {
  components: {
    Home,
    Posts,
    Archive
  },
  data() {
    return {
      currentTab: 'Home',
      tabs: ['Home', 'Posts', 'Archive']
    }
  }
}
</script>
<template>
  <div class="demo">
    <button
       v-for="tab in tabs"
       :key="tab"
       :class="['tab-button', { active: currentTab === tab }]"
       @click="currentTab = tab"
     >
      {{ tab }}
    </button>
    <component :is="currentTab" class="tab"></component>
  </div>
</template>
<style>
.demo {
  font-family: sans-serif;
  border: 1px solid #eee;
  border-radius: 2px;
  padding: 20px 30px;
  margin-top: 1em;
  margin-bottom: 40px;
  user-select: none;
  overflow-x: auto;
}
.tab-button {
  padding: 6px 10px;
  border-top-left-radius: 3px;
  border-top-right-radius: 3px;
  border: 1px solid #ccc;
  cursor: pointer;
  background: #f0f0f0;
  margin-bottom: -1px;
  margin-right: -1px;
}
.tab-button:hover {
  background: #e0e0e0;
}
.tab-button.active {
  background: #e0e0e0;
}
.tab {
  border: 1px solid #ccc;
  padding: 10px;
}
</style>

Transition 過渡組件

Vue 提供了兩個內置組件,可以幫助你製作基於狀態變化的過渡和動畫:

Transition 組件

認識Transition組件

Transition 是一個內置組件,這意味着它在任意別的組件中都可以被使用,無需註冊。它可以將進入和離開動畫應用到通過默認插槽傳遞給它的元素或組件上。進入或離開可以由以下的條件之一觸發:

  • 由 v-if 所觸發的切換
  • 由 v-show 所觸發的切換
  • 由特殊元素 <component> 切換的動態組件
  • 改變特殊的 key 屬性
基礎用法
<button @click="show = !show">Toggle</button>
<Transition name="fade">
  <p v-if="show">hello</p>
</Transition>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

TIP<Transition> 僅支持單個元素或組件作為其插槽內容。如果內容是一個組件,這個組件必須僅有一個根元素。 當一個 <Transition> 組件中的元素被插入或移除時,會發生下面這些事情: Vue 會自動檢測目標元素是否應用了 CSS 過渡或動畫。如果是,則一些 CSS 過渡 class 會在適當的時機被添加和移除。

CSS****過渡class

![[e3760062-2101-4ccb-bbbe-9e978971acf1.png]]

CSS****的animation
<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Hello here is some bouncy text!
  </p>
</Transition>
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}
可複用過渡效果

得益於 Vue 的組件系統,過渡效果是可以被封裝複用的。要創建一個可被複用的過渡,我們需要為 <Transition> 組件創建一個包裝組件,並向內傳入插槽內容

<!-- MyTransition.vue -->
<script>
</script>
<template>
  <!-- 包裝內置的 Transition 組件 -->
  <Transition
    name="my-transition">
    <slot></slot> <!-- 向內傳遞插槽內容 -->
  </Transition>
</template>
<style>
/*必要的 CSS...
  注意:避免在這裏使用 <style scoped>
  因為那不會應用到插槽內容上
*/
</style>

使用自定義過渡組件

<MyTransition>
  <div v-if="show">Hello</div>
</MyTransition>
出現時過渡

如果你想在某個節點初次渲染時應用一個過渡效果,你可以添加 appear

prop:

<Transition appear>
</Transition>
過渡模式

先執行離開動畫,然後在其完成之後再執行元素的進入動畫

<Transition mode="out-in">
</Transition>
組件間過渡

<Transition> 也可以作用於動態組件之間的切換

<Transition name="fade" mode="out-in">
  <component :is="activeComponent"></component>
</Transition>
動態過渡
<Transition :name="transitionName">
</Transition>

TransitionGroup 組件

會在一個 v-for 列表中的元素或組件被插入,移動,或移除時應用動畫。

<script>
import { shuffle } from 'lodash'
const getInitialItems = () => [1, 2, 3, 4, 5]
let id = getInitialItems().length + 1
export default {
  data() {
    return {
      items: getInitialItems()
    }
  },
  methods: {
    insert() {
      const i = Math.round(Math.random() * this.items.length)
      this.items.splice(i, 0, id++)
    },
    reset() {
      this.items = getInitialItems()
    },
    shuffle() {
      this.items = shuffle(this.items)
    },
    remove(item) {
      const i = this.items.indexOf(item)
      if (i > -1) {
        this.items.splice(i, 1)
      }
    }
  }
}
</script>
<template>
  <button @click="insert">insert at random index</button>
  <button @click="reset">reset</button>
  <button @click="shuffle">shuffle</button>
  <TransitionGroup tag="ul" name="fade" class="container">
    <div v-for="item in items" class="item" :key="item">
      {{ item }}
      <button @click="remove(item)">x</button>
    </div>
  </TransitionGroup>
</template>
<style>
.container {
  position: relative;
  padding: 0;
}
.item {
  width: 100%;
  height: 30px;
  background-color: #f3f3f3;
  border: 1px solid #666;
  box-sizing: border-box;
}
/* 1. 聲明過渡效果 */
.fade-move,
.fade-enter-active,
.fade-leave-active {
  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}
/* 2. 聲明進入和離開的狀態 */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: scaleY(0.01) translate(30px, 0);
}
/* 3. 確保離開的項目被移除出了佈局流
      以便正確地計算移動時的動畫效果。 */
.fade-leave-active {
  position: absolute;
}
</style>

Vue2和Vue3的區別

Vue2

/* vue2的類名沒有from和to */
.fade-enter,
.fade-leave{
  opacity: 0;
}

Vue3

/* vue3需要添加from和to*/
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

KeepAlive****組件 (重要)

<KeepAlive> 是一個內置組件,它的功能是在多個組件間動態切換時緩存被移除的組件實例。

案例

<!-- 非活躍的組件將會被緩存! -->
<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>

包含/排除

include和exclude中的標識符為組件定義中的name選項的值

<!-- 以英文逗號分隔的字符串 -->
<!-- 包含 -->
<KeepAlive include="a,b">
  <component :is="view" />
</KeepAlive>

<!-- 排除 -->
<KeepAlive exclude="a,b">
  <component :is="view" />
</KeepAlive>

<!-- 正則表達式 (需使用 v-bind) -->
<KeepAlive :include="/a|b/">
  <component :is="view" />
</KeepAlive>

<!-- 數組 (需使用 v-bind) -->
<KeepAlive :include="['a', 'b']">
  <component :is="view" />
</KeepAlive>

最大緩存實例數

如果緩存的實例數量即將超過指定的那個最大數量,則最久沒有被訪問的緩存實例將被銷燬,以便為新的實例騰出空間。

<KeepAlive :max="10">
  <component :is="activeComponent" />
</KeepAlive>

緩存實例的生命週期

一個持續存在的組件可以通過 activated(激活) 和 deactivated(失活) 選項來註冊相應的兩個狀態的生命週期鈎子

export default {
  activated() {
    // 在首次掛載、
    // 以及每次從緩存中被重新插入的時候調用
  },
  deactivated() {
    // 在從 DOM 上移除、進入緩存
    // 以及組件卸載時調用
  }
}

這兩個鈎子不僅適用於 <KeepAlive> 緩存的根組件,也適用於緩存樹中的後代組件

Teleport組件

vue2沒有Teleport組件

<Teleport> 是一個內置組件,它可以將一個組件內部的一部分模板“傳送”到該組件的 DOM 結構外層的位置去。

應用場景

有時我們可能會遇到這樣的場景:一個組件模板的一部分在邏輯上從屬於該組件,但從整個應用視圖的角度來看,它在 DOM 中應該被渲染在整個 Vue 應用外部的其他地方。

這類場景最常見的例子就是全屏的模態框。理想情況下,我們希望觸發模態框的按鈕和模態框本身是在同一個組件中,因為它們都與組件的開關狀態有關。但這意味着該模態框將與按鈕一起渲染在應用 DOM 結構裏很深的地方。這會導致該模態框的 CSS 佈局代碼很難寫。

全屏模態框案例

Parent.vue

<script>
import Modal from './Modal.vue'
export default {
  components: {
    Modal
  },
  data() {
    return {
      showModal: false
    }
  }
}
</script>
<template>
  <div class="top">
    <button id="show-modal" @click="showModal = true">Show Modal</button>
    <Teleport to="body">
    <!-- 使用這個 modal 組件,傳入 prop -->
    <modal :show="showModal" @close="showModal = false">
      <template #header>
        <h3>custom header</h3>
      </template>
    </modal>
    </Teleport>
  </div>
</template>
<style lang="scss" scoped>
.top {
  /* --------------------------------注意transform屬性會影響子組件    position:fixed的定位 */
  transform: translateX(300px);
  width: 400px;
  height: 300px;
  border: 1px solid #000;
}
</style>

Modal.vue

<script>
export default {
  props: {
    show: Boolean
  }
}
</script>
<template>
  <Transition name="modal">
    <div v-if="show" class="modal-mask">
      <div class="modal-container">
        <div class="modal-header">
          <slot name="header">default header</slot>
        </div>
        <div class="modal-body">
          <slot name="body">default body</slot>
        </div>
        <div class="modal-footer">
          <slot name="footer">
            default footer
            <button
              class="modal-default-button"
              @click="$emit('close')"
            >OK</button>
          </slot>
        </div>
      </div>
    </div>
  </Transition>
</template>
<style>
.modal-mask {
  position: fixed;
  z-index: 9998;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  transition: opacity 0.3s ease;
}
.modal-container {
  width: 300px;
  margin: auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  transition: all 0.3s ease;
}
.modal-header h3 {
  margin-top: 0;
  color: #42b983;
}
.modal-body {
  margin: 20px 0;
}
.modal-default-button {
  float: right;
}
/*
對於 transition="modal" 的元素來説
當通過 Vue.js 切換它們的可見性時
以下樣式會被自動應用。
*
你可以簡單地通過編輯這些樣式
來體驗該模態框的過渡效果。
*/
.modal-enter-from {
  opacity: 0;
}
.modal-leave-to {
  opacity: 0;
}
.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}
</style>