不只懂 Vue 语法:请用图片轮播的例子示范 Composition API?

问题回答

这个例子会示范以 Compositions API 开发一个简单的图片轮播。先打 API 从远端取得资料,之後把资料渲染到画面,并加入轮播功能,而且轮播功能使用了 Vue 的 transition 来制作。

此例子是参考六角学院的直播里 Kuro 大大提到的常见面试题之一以及他的例子做法,并加入远端抓资料(从 random user 抓取 5 笔 user 资料)的功能和客制样式来改写。

完成结果

先展示完成效果:

以下会说明步骤,但只会针对重点部分来解说。

打 API 取得资料

首先,先从 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 取资料。或者使用 onMountedonBeforeMount 也可以。

把资料渲染到网页上,并使用 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 的部分会留待最後才解说。

新增按钮功能

按钮功能包括:

  • 根据目前资料的数量,印出同等数量的按钮。
  • 加入 next、prev 按钮,前後切换显示资料。
  • 按下单一按钮时,会切换显示该笔资料。
<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 。如果再按下 nextcurrentIndex 会加一变成 5,而 5 % 5 的余数会是 0,因此回到第一笔资料。其余页码,例如 1, 2。即是 1 % 52 % 5 等,答案会是 1, 2。即是等於该页码的数值。

动画设定

有两点要处理:

  • 外层设定 overflow: hidden
  • 使用 transition 标签设定动画

外层设定 overflow: hidden

切换图片时,当前一张图片还没完全滑走,它就会继续占用位置,因此即将要显示的图片就会被推挤到下面,情况如下:

因为此例中的img 是被 transition 包着,transition 在切换图片时,会在旧 img 後再加即将要显示的新 img

transition 设定

切换资料时,使用了 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%);
}

有几点要注意:

  • 如果切换的元素,它们拥有相同的标签,就需要加上 key 属性,否则动画会无效。在此例中,切换的元素全都是 img,因此要加上 key。
  • transition 会在旧和新的 DOM 上加入相应的 class。Class 名称可以使用 <transition name=""> 的 name 属性来定义,因此,当 name 的值是 next-img 时,所有 transition 用到的 class 名称就会以 next-img 作为前辍,例如是 next-img-leave-activenext-img-leave-to
  • 此例中,当 currentIndex 是前进,动画就会向左滑动。相反的话,就是向右滑动。

关於动画 class 名称的作用,看此图就懂了:

截图自官方文件

因此,当图片要消失时,如果要设定它的目的地,对应的 class 就是 xxx-leave-to

完整程序码

https://codesandbox.io/s/composition-api-tu-pian-lun-bo-x27ht?file=/src/App.vue

总结

  • 如果一载入页面时就要打 API 取资料,可在 setuponMountedonBeforeMount 里进行。
  • 因为打 API 是非同步程序,因此如果画面需要操作那些资料,可以使用 v-if 避免资料还没抓到的情况。
  • 可以使用取余数 % 的方式来制作循环的页码。
  • 使用 transition 切换动画时,会同时存在旧和新的 DOM,也会为它们加上不同的 class。
  • class 名称的前辍是根据你在 <transition name=""> 的 name 属性。如没有指明,预设就会是 v-xxxx
  • 如果 transition 里切换的元素有同一标签,就需使用 key 属性。

呼~ 终於到了最後一天了,感谢团友和读者,技术分享就到此为止了,明天会总结一下这 30 天的铁人赛心得,会以个人感受与心得为主,明天见~


<<:  Day 24 - Travserable

>>:  Day25 Bootstrap简介

[Day 29] 资料产品开发实务 - 自动贴标系统

「懒惰是进步的原动力」 科技的进步降低了许多事情的门槛,例如过去要会换牌档才能开车,现在基本上就自排...

Day28 - TimePickerDialog

一般来说日期、时间几乎都同时出现 既然昨天学了Android的日期交谈视窗 今天就来学时间的交谈视窗...

Day 19 (Xd)

1.制作UI按纽 (影片Xd03 档案Xd02) https://neumorphism.io/#2...

JavaScript学习日记 : Day11 - 函数绑定

当object中的function作为callback function传递给setTimeout时...

Day19 X Application Shell Architecture

昨天介绍 Service Workers 後我们知道它是 PWA 的要素之一,且也是让 Web A...