这是我参与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链接~
未完待续, 下篇文章让我们一起来聊一下高阶函数吧!