【青训营】- 跟着月影学 JavaScript学习笔记(四) 如何写好一个组件

506 阅读3分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

承接上文

3.4 vue3写法升级

轮播的行为, 其实可以抽象为一个头尾相接的循环队列

那么为了提取业务逻辑, 精简代码, 我们可以创造一个工厂函数, 根据参数中的轮播数据, 生成一个队列实体

方案有了, 我们开始书写代码

首先, 创建文件:

根据vue官方文档中对于组合式Api的应用, 我们创建src/composables/CircularQueue.ts

第二步, 定义队列接口:

import { Ref } from 'vue';

interface ICircularQueue<T> {
  queue: T[];
  intervalTime: number;
  currentIndex: Ref<number>;
  getCurrentIndex: () => number;
  getCurrentItem: () => T;
  to: (index: number) => void;
  previous: () => void;
  next: () => void;
  start: () => void;
  stop: () => void;
  setIntervalTime: (newVal: number) => void;
}

使用ts, 最重要的就是类型定义, 在写业务逻辑代码之前, 请务必提前设计和思考

在轮播图中, 队列内容为图片地址, 即字符串数组, 但其它的需求并不一定, 所以我们使用泛型, 即ICircularQueue<T>

不了解泛型的同学可以阅读TypeScript中文网

轮播过程中, currentIndex的变化可能会触发页面重新渲染, 所以我们将其类型声明为响应式Ref<number>

第三步, 创建实现队列接口的实体:

接口可以理解成一种类型的描述, 但要在代码中使用, 则需要一个实现此接口的实体

创建实体的方式有很多种, 根据vue3中组合式Api的示例, 笔者采取工厂函数:

function createCirularQueue<T>(items: T[]): ICircularQueue<T> {
  let timer: number | undefined = undefined;
  let intervalTime = 3000;
  const queue = items;
  const currentIndex = ref(0);
  const getCurrentIndex = (): number => {
    return currentIndex.value;
  };
  const getCurrentItem = (): T => {
    return queue[currentIndex.value];
  };
  const to = (index: number): void => {
    currentIndex.value = index;
  };
  const previous = (): void => {
    to((queue.length + currentIndex.value - 1) % queue.length);
  };
  const next = (): void => {
    to((currentIndex.value + 1) % queue.length);
  };
  const stop = (): void => {
    clearInterval(timer);
  };
  const start = (): void => {
    stop();
    timer = setInterval(() => next(), intervalTime);
  };
  const setIntervalTime = (newVal: number): void => {
    intervalTime = newVal;
    if (timer) {
      stop();
      start();
    }
  };
  return {
    queue,
    intervalTime,
    currentIndex,
    getCurrentIndex,
    getCurrentItem,
    to,
    previous,
    next,
    stop,
    start,
    setIntervalTime
  } as ICircularQueue<T>;
}

业务代码很简单, 关键还是ts的类型声明

写到这里, 大部分功能已经完成, 但总觉得需要调整些什么, 例如:

  • 一定要从第一个item开始循环吗?
  • 间隔时间必须要3秒吗?

所以, 工厂函数应该增加第二个参数, 也就是第四步, 增加配置参数:

将刚才提出问题涉及的变量进行类型声明:

interface CircularQueueOptions {
  interval?: number;
  startIndex?: number;
}

?代表此属性非必填, 即{}{interval: 1000}都符合CircularQueueOptions的类型检查

再重构下工厂函数中的部分代码:

function createCirularQueue<T>(items: T[], options?: CircularQueueOptions): ICircularQueue<T> {
  // 业务代码...
  let intervalTime = options?.interval || 3000;
  // 业务代码...
  const currentIndex = ref(options?.startIndex || 0);
  // 业务代码...
}

代码写完了, 但还没完全写完

业务代码实现后, 组件还没创建呢, 来编写CarouselThree.vue:

import { defineComponent, PropType, toRefs, watch } from 'vue';
import createCirularQueue from '../composables/CircularQueue';

export default defineComponent({
  setup(props) {
    const cirularQueue = createCirularQueue(props.images, { interval: props.cycle });
    watch(toRefs(props).cycle, (newValue) => {
      cirularQueue.setIntervalTime(newValue);
    });
    return {
      currentIndex: cirularQueue.currentIndex,
      slideTo: cirularQueue.to,
      slideNext: cirularQueue.next,
      slidePrevious: cirularQueue.previous
    };
  }
});

除了setup函数, 其它内容与CarouselTwo.vue几乎没有区别

因为拥有了循环队列这一抽象业务逻辑, 在这个版本中, 笔者只暴露了三个方法, 并增加了一个监听器, 用于动态改变轮播间隔时间

3.5 总结or拓展?

本来关于轮播图的内容到这里就应该结束了, 但月影大大实现的优雅插件结构让笔者始终不能忘怀

加之在书写vue3版本时, 详细阅读了ant design vue的代码, 了解了插槽的使用方式

所以, 笔者最后会实现一个基于插槽的jsx版本, 期望的使用形式和ant design vue类似:

<vue-koala-carousel :intervalTime="3000">
  <li v-for="(image, index) in images" :key="index">
    <img :src="image" />
  </li>
</vue-koala-carousel>

项目完成后更新Github链接~

未完待续, 下篇文章让我们一起来聊一下高阶函数吧!