這個例子會示範以 Compositions API 開發一個簡單的圖片輪播。先打 API 從遠端取得資料,之後把資料渲染到畫面,並加入輪播功能,而且輪播功能使用了 Vue 的 transition 來製作。
此例子是參考六角學院的直播裏 Kuro 大大提到的常見面試題之一以及他的例子做法,並加入遠端抓資料(從 random user 抓取 5 筆 user 資料)的功能和客製樣式來改寫。
先展示完成效果:

以下會說明步驟,但只會針對重點部分來解說。
首先,先從 random user 取得資料:
import { reactive } from 'vue';
export default {
  setup() {
    const users = reactive({ results: [] });
    (async () => {
      try {
        const res = await fetch('https://randomuser.me/api/?results=5');
        const resJSON = await res.json();
        users.results = resJSON.results;
      } catch (err) {
        console.log(err);
      }
    })();
    
    return {
      users
    };
  }
};
在 Vue 3,setup 函式的生命週期等於 created,因此可以直接在 setup 裏打 API 取資料。或者使用 onMounted 或 onBeforeMount 也可以。
把資料渲染到網頁上,並使用 currentIndex 來指定見前要顯示哪筆資料 :
<!-- 需要加上 v-if 判斷是否已抓到遠端的資料,不然會報錯 -->
<div v-if="users.results[currentIndex]" class="card">
<div class="card-img">
  <transition :name="imgTransition">
    <img
      :key="users.results[currentIndex]"
      :src="users.results[currentIndex].picture.large"
    />
  </transition>
</div>
<ul>
  <li>
    {{ users.results[currentIndex].name.title }}
    {{ users.results[currentIndex].name.last }}
    {{ users.results[currentIndex].name.first }}
  </li>
  <li>{{ users.results[currentIndex].email }}</li>
  <li>{{ users.results[currentIndex].phone }}</li>
</ul>
</div>
注意,最外層需要加上 v-if 來判斷是否已抓到遠端的資料。因為在打 API 取資料是非同步程式。因此,即使我們在 setup 裏已執行打 API的程式碼,但在接收到資料之前,程式會一直繼續執行,包括渲染畫面。當畫面渲染好但資料還沒回來時,就不能在畫面渲染資料,不然會出現例如是 Cannot read property 'name' of undefined 這種錯誤。
transition 的部分會留待最後才解說。
按鈕功能包括:
<div class="carousel-btns">
<a @click.prevent="swipeSlide('prev')" href="#"> prev </a>
<div class="carousel-btn-group">
  <a
    @click.prevent="selectSlide(index)"
    href="#"
    v-for="(n, index) in users.results.length"
    :key="n"
    :class="[
      { 'carousel-btn-active': currentIndex === index },
      'carousel-btn'
    ]"
  >
  </a>
</div>
<a @click.prevent="swipeSlide('next')" href="#"> next </a>
</div>
setup() {
    const imgTransition = ref('');
    const currentIndex = ref(0);
    
    // ...
    
    
    // next, prev 切換資料功能
    const swipeSlide = direction => {
      imgTransition.value = `${direction}-img`;
      if (direction === 'next') {
        currentIndex.value = (currentIndex.value + 1) % users.results.length;
      } else {
        currentIndex.value === 0
          ? (currentIndex.value = users.results.length - 1)
          : currentIndex.value--;
      }
    };
    // 指定顯示單一資料功能
    const selectSlide = index => {
      if (currentIndex.value > index) {
        imgTransition.value = 'prev-img';
      } else {
        imgTransition.value = 'next-img';
      }
      currentIndex.value = index;
    };
    
    return {
      users,
      currentIndex,
      swipeSlide,
      selectSlide,
      imgTransition
    };
}
關於 next, prev 切換資料功能,如果 direction 是 next,寫法是:
currentIndex.value = (currentIndex.value + 1) % users.results.length;
這寫法是為了當 currentIndex 的數值大於 users 陣列長度時,即代表要切回第一筆資料。currentIndex 是由 0 為開始,這時候會顯示第一筆資料。目前資料長度是 5,因此,當顯示最後一筆資料時, currentIndex 會是 4 。如果再按下 next, currentIndex 會加一變成 5,而 5 % 5 的餘數會是 0,因此回到第一筆資料。其餘頁碼,例如 1, 2。即是 1 % 5、2 % 5 等,答案會是 1, 2。即是等於該頁碼的數值。
有兩點要處理:
overflow: hidden
overflow: hidden切換圖片時,當前一張圖片還沒完全滑走,它就會繼續佔用位置,因此即將要顯示的圖片就會被推擠到下面,情況如下:

因為此例中的img 是被 transition 包著,transition 在切換圖片時,會在舊 img 後再加即將要顯示的新 img:

切換資料時,使用了 transition 來製作切換動畫。
<div class="card-img">
  <transition :name="imgTransition">
    <img
      :key="users.results[currentIndex]"
      :src="users.results[currentIndex].picture.large"
    />
  </transition>
</div>
因此新的 img 會被推擠到下面。解決方法就是在 card-img 設定 overflow: hidden。
setup() {
    const swipeSlide = direction => {
      imgTransition.value = `${direction}-img`;
      // ...
    };
    
    const selectSlide = index => {
      if (currentIndex.value > index) {
        imgTransition.value = 'prev-img';
      } else {
        imgTransition.value = 'next-img';
      }
      currentIndex.value = index;
    };
}
SCSS:
.prev-img-enter-active,
.prev-img-leave-active,
.next-img-enter-active,
.next-img-leave-active {
  transition: all 0.5s;
}
.next-img-leave-to {
  transform: translate(-100%);
}
.prev-img-leave-to {
  transform: translate(100%);
}
有幾點要注意:
img,因此要加上 key。<transition name=""> 的 name 屬性來定義,因此,當 name 的值是 next-img 時,所有 transition 用到的 class 名稱就會以 next-img 作為前輟,例如是 next-img-leave-active、next-img-leave-to。currentIndex 是前進,動畫就會向左滑動。相反的話,就是向右滑動。關於動畫 class 名稱的作用,看此圖就懂了:

截圖自官方文件
因此,當圖片要消失時,如果要設定它的目的地,對應的 class 就是 xxx-leave-to。
https://codesandbox.io/s/composition-api-tu-pian-lun-bo-x27ht?file=/src/App.vue
setup、onMounted、onBeforeMount 裏進行。v-if 避免資料還沒抓到的情況。% 的方式來製作循環的頁碼。transition 切換動畫時,會同時存在舊和新的 DOM,也會為它們加上不同的 class。<transition name=""> 的 name 屬性。如沒有指明,預設就會是 v-xxxx。transition 裏切換的元素有同一標籤,就需使用 key 屬性。呼~ 終於到了最後一天了,感謝團友和讀者,技術分享就到此為止了,明天會總結一下這 30 天的鐵人賽心得,會以個人感受與心得為主,明天見~