大家好,我是鱼樱!!!
关注公众号【鱼樱AI实验室】
持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~
一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享~~~
接下来将为你提供一个高度灵活 的 Vue3 + TypeScript 版本的 MeScroll.js 组件封装方案,保证继承原有的所有方法、属性和插槽功能,同时提供最简单的调用方式。
1. 安装依赖
首先需要安装必要的依赖:
bash
npm install mescroll.js
# 或
yarn add mescroll.js
2. 创建 MeScroll 组件
创建一个 MeScroll.vue
文件:
html
<template>
<div ref="mescrollRef" class="mescroll">
<div class="mescroll-content">
<!-- 下拉刷新区域 -->
<slot name="header"></slot>
<!-- 内容区域 -->
<slot></slot>
<!-- 上拉加载区域 -->
<slot name="footer"></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onBeforeUnmount, computed, getCurrentInstance, watch } from 'vue';
import MeScroll from 'mescroll.js';
import 'mescroll.js/mescroll.min.css';
export default defineComponent({
name: 'MeScrollComponent',
props: {
// 下拉刷新的配置
down: {
type: Object,
default: () => ({})
},
// 上拉加载的配置
up: {
type: Object,
default: () => ({})
},
// 高度
height: {
type: String,
default: '100%'
},
// 是否自动初始化
autoInit: {
type: Boolean,
default: true
}
},
emits: ['init', 'down-callback', 'up-callback'],
setup(props, { emit, expose }) {
const mescrollRef = ref<HTMLElement | null>(null);
const mescroll = ref<any>(null);
const instance = getCurrentInstance();
// 合并下拉刷新配置
const downOption = computed(() => {
return {
callback: (mescroll: any) => emit('down-callback', mescroll),
...props.down
};
});
// 合并上拉加载配置
const upOption = computed(() => {
return {
callback: (page: any, mescroll: any) => emit('up-callback', page, mescroll),
...props.up
};
});
// 初始化 MeScroll
const initMeScroll = () => {
if (!mescrollRef.value) return;
mescroll.value = new MeScroll(mescrollRef.value, {
down: downOption.value,
up: upOption.value
});
// 发出初始化事件
emit('init', mescroll.value);
};
// 监听配置变化
watch(() => props.down, (newVal) => {
if (mescroll.value && mescroll.value.optDown) {
Object.assign(mescroll.value.optDown, newVal);
}
}, { deep: true });
watch(() => props.up, (newVal) => {
if (mescroll.value && mescroll.value.optUp) {
Object.assign(mescroll.value.optUp, newVal);
}
}, { deep: true });
// 挂载时初始化
onMounted(() => {
if (props.autoInit) {
initMeScroll();
}
});
// 销毁时清理
onBeforeUnmount(() => {
if (mescroll.value) {
mescroll.value.destroy();
}
});
// 暴露方法给父组件
expose({
// 暴露 mescroll 对象
getMeScroll: () => mescroll.value,
// 手动初始化
init: initMeScroll,
// 代理 mescroll 的主要方法
triggerDownScroll: () => mescroll.value?.triggerDownScroll(),
triggerUpScroll: () => mescroll.value?.triggerUpScroll(),
resetUpScroll: () => mescroll.value?.resetUpScroll(),
setPageNum: (num: number) => mescroll.value?.setPageNum(num),
setPageSize: (size: number) => mescroll.value?.setPageSize(size),
endByPage: (dataSize: number, totalPage: number, systime?: number) =>
mescroll.value?.endByPage(dataSize, totalPage, systime),
endBySize: (dataSize: number, totalSize: number, systime?: number) =>
mescroll.value?.endBySize(dataSize, totalSize, systime),
endSuccess: (dataSize?: number, hasNext?: boolean, systime?: number) =>
mescroll.value?.endSuccess(dataSize, hasNext, systime),
endErr: () => mescroll.value?.endErr()
});
return {
mescrollRef,
mescroll
};
}
});
</script>
<style scoped>
.mescroll {
width: 100%;
height: v-bind(height);
overflow-y: auto;
}
</style>
3. 创建 TypeScript 类型声明文件
创建 mescroll.d.ts
文件,为 MeScroll 提供更好的 TypeScript 支持:
typescript
declare module 'mescroll.js' {
interface MeScrollDown {
use?: boolean;
auto?: boolean;
autoShowLoading?: boolean;
isLock?: boolean;
offset?: number;
outOffsetRate?: number;
minAngle?: number;
mustToTop?: boolean;
hardwareClass?: string;
warpId?: string;
warpClass?: string;
resetClass?: string;
textInOffset?: string;
textOutOffset?: string;
textLoading?: string;
textSuccess?: string;
textErr?: string;
beforeLoading?: (mescroll: MeScroll) => void;
showLoading?: (mescroll: MeScroll) => void;
afterLoading?: (mescroll: MeScroll) => void;
onMoving?: (mescroll: MeScroll, rate: number, downHight: number) => void;
beforeEndDownScroll?: (mescroll: MeScroll) => boolean;
endDownScroll?: (mescroll: MeScroll) => void;
callback?: (mescroll: MeScroll) => void;
}
interface MeScrollUp {
use?: boolean;
auto?: boolean;
isLock?: boolean;
isBoth?: boolean;
isBounce?: boolean;
callback?: (page: MeScrollPage, mescroll: MeScroll) => void;
page?: MeScrollPage;
noMoreSize?: number;
offset?: number;
toTop?: MeScrollToTop;
loadFull?: MeScrollLoadFull;
empty?: MeScrollEmpty;
clearId?: string;
clearEmptyId?: string;
hardwareClass?: string;
warpId?: string;
warpClass?: string;
htmlLoading?: string;
htmlNodata?: string;
inited?: (mescroll: MeScroll, upwarp: HTMLElement) => void;
showLoading?: (mescroll: MeScroll, upwarp: HTMLElement) => void;
showNoMore?: (mescroll: MeScroll, upwarp: HTMLElement) => void;
onScroll?: (mescroll: MeScroll, y: number, isUp: boolean) => void;
scrollbar?: MeScrollBar;
lazyLoad?: MeScrollLazyLoad;
}
interface MeScrollToTop {
src?: string;
offset?: number;
warpId?: string;
warpClass?: string;
onClick?: (mescroll: MeScroll) => void;
}
interface MeScrollLoadFull {
use?: boolean;
delay?: number;
}
interface MeScrollEmpty {
warpId?: string;
icon?: string;
tip?: string;
btntext?: string;
btnClick?: (mescroll: MeScroll) => void;
}
interface MeScrollBar {
use?: boolean;
barClass?: string;
show?: number;
hide?: number;
}
interface MeScrollLazyLoad {
use?: boolean;
attr?: string;
showClass?: string;
delay?: number;
offset?: number;
}
interface MeScrollPage {
num?: number;
size?: number;
time?: number;
}
interface MeScrollOptions {
down?: MeScrollDown;
up?: MeScrollUp;
}
class MeScroll {
constructor(mescrollId: string | HTMLElement, options?: MeScrollOptions);
optDown: MeScrollDown;
optUp: MeScrollUp;
scrollTo(y: number, duration?: number): void;
resetUpScroll(): void;
triggerDownScroll(): void;
triggerUpScroll(): void;
setPageNum(num: number): void;
setPageSize(size: number): void;
lockDownScroll(isLock: boolean): void;
lockUpScroll(isLock: boolean): void;
endByPage(dataSize: number, totalPage: number, systime?: number): void;
endBySize(dataSize: number, totalSize: number, systime?: number): void;
endSuccess(dataSize?: number, hasNext?: boolean, systime?: number): void;
endErr(): void;
showDownScroll(): void;
showUpScroll(): void;
hideUpScroll(): void;
hideDownScroll(): void;
clearDataList(): void;
clearEmptyId(): void;
destroy(): void;
removeEmpty(): void;
}
export default MeScroll;
}
4. 创建简化的 Composable 辅助函数
创建 useMeScroll.ts
文件,提供更简洁的使用方式:
typescript
import { ref, Ref } from 'vue';
interface MeScrollInstance {
getMeScroll: () => any;
triggerDownScroll: () => void;
triggerUpScroll: () => void;
resetUpScroll: () => void;
setPageNum: (num: number) => void;
setPageSize: (size: number) => void;
endByPage: (dataSize: number, totalPage: number, systime?: number) => void;
endBySize: (dataSize: number, totalSize: number, systime?: number) => void;
endSuccess: (dataSize?: number, hasNext?: boolean, systime?: number) => void;
endErr: () => void;
[key: string]: any;
}
interface UseMeScrollOptions<T> {
// 初始请求参数
params?: Record<string, any>;
// 数据请求函数
requestData: (params: any) => Promise<{ list: T[], total: number }>;
// 下拉刷新配置
down?: Record<string, any>;
// 上拉加载配置
up?: Record<string, any>;
// 自动初始化
autoInit?: boolean;
}
export default function useMeScroll<T = any>(options: UseMeScrollOptions<T>) {
const {
params = {},
requestData,
down = {},
up = {},
autoInit = true
} = options;
const meScrollRef: Ref<MeScrollInstance | null> = ref(null);
const list: Ref<T[]> = ref([]);
const isLoading = ref(false);
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const searchParams = ref({ ...params });
// 下拉刷新回调
const downCallback = async () => {
page.value = 1;
await loadData(true);
};
// 上拉加载回调
const upCallback = async (mescrollPage: any) => {
page.value = mescrollPage.num;
pageSize.value = mescrollPage.size;
await loadData(false);
};
// 加载数据
const loadData = async (isRefresh: boolean) => {
if (isLoading.value) return;
isLoading.value = true;
try {
const params = {
page: page.value,
pageSize: pageSize.value,
...searchParams.value
};
const { list: newList, total: newTotal } = await requestData(params);
total.value = newTotal;
// 更新列表数据
if (isRefresh) {
list.value = newList;
} else {
list.value = [...list.value, ...newList];
}
// 通知 mescroll 加载完成
if (meScrollRef.value) {
meScrollRef.value.endBySize(newList.length, newTotal);
}
} catch (error) {
console.error('加载数据失败:', error);
if (meScrollRef.value) {
meScrollRef.value.endErr();
}
} finally {
isLoading.value = false;
}
};
// 重新加载数据
const refresh = () => {
if (meScrollRef.value) {
meScrollRef.value.resetUpScroll();
}
};
// 搜索方法 - 更新参数并重新加载
const search = (newParams: Record<string, any>) => {
searchParams.value = { ...searchParams.value, ...newParams };
refresh();
};
// 重置所有参数和数据
const reset = () => {
searchParams.value = { ...params };
refresh();
};
return {
meScrollRef,
list,
isLoading,
page,
pageSize,
total,
searchParams,
downCallback,
upCallback,
refresh,
search,
reset,
// MeScroll 配置
meScrollOptions: {
down: {
callback: downCallback,
...down
},
up: {
callback: upCallback,
...up
},
autoInit
}
};
}
5. 组件注册 (可选)
创建 index.ts
用于全局注册组件:
typescript
import { App } from 'vue';
import MeScrollComponent from './MeScroll.vue';
import useMeScroll from './useMeScroll';
export { MeScrollComponent, useMeScroll };
export default {
install(app: App) {
app.component('MeScroll', MeScrollComponent);
}
};
6. 使用示例
基础使用方式
html
<template>
<div class="container">
<MeScroll
ref="meScrollRef"
:down="{ use: true }"
:up="{ use: true }"
@down-callback="downCallback"
@up-callback="upCallback"
>
<div class="list-item" v-for="(item, index) in dataList" :key="index">
{{ item.title }}
</div>
</MeScroll>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { MeScrollComponent } from './path-to-mescroll';
const meScrollRef = ref(null);
const dataList = ref<any[]>([]);
// 下拉刷新回调
const downCallback = (mescroll: any) => {
// 重置页码
fetchData(1, () => {
// 结束下拉刷新
mescroll.endSuccess();
});
};
// 上拉加载回调
const upCallback = (page: any, mescroll: any) => {
fetchData(page.num, (curPageData) => {
// 更新列表
if (page.num === 1) dataList.value = curPageData;
else dataList.value = dataList.value.concat(curPageData);
// 结束上拉加载
mescroll.endSuccess(curPageData.length);
});
};
// 模拟请求数据
const fetchData = (pageNum: number, callback: (data: any[]) => void) => {
setTimeout(() => {
const curPageData = Array(10).fill(0).map((_, i) => ({
id: (pageNum - 1) * 10 + i + 1,
title: `Item ${(pageNum - 1) * 10 + i + 1}`
}));
callback(curPageData);
}, 500);
};
</script>
<style scoped>
.container {
height: 100vh;
}
.list-item {
padding: 15px;
border-bottom: 1px solid #eee;
}
</style>
使用 Composable 方式 (更简便)
html
<template>
<div class="container">
<MeScroll
ref="meScrollRef"
:down="meScrollOptions.down"
:up="meScrollOptions.up"
@down-callback="downCallback"
@up-callback="upCallback"
>
<div class="list-item" v-for="(item, index) in list" :key="index">
{{ item.title }}
</div>
</MeScroll>
</div>
</template>
<script lang="ts" setup>
import { MeScrollComponent, useMeScroll } from './path-to-mescroll';
interface ListItem {
id: number;
title: string;
}
// 使用 useMeScroll 封装复杂逻辑
const {
meScrollRef,
list,
downCallback,
upCallback,
meScrollOptions,
search,
refresh
} = useMeScroll<ListItem>({
params: { keyword: '' },
requestData: async (params) => {
// 模拟API请求
return new Promise((resolve) => {
setTimeout(() => {
const { page, pageSize, keyword } = params;
const mockList = Array(pageSize).fill(0).map((_, i) => ({
id: (page - 1) * pageSize + i + 1,
title: `${keyword || ''}Item ${(page - 1) * pageSize + i + 1}`
}));
resolve({
list: mockList,
total: 100
});
}, 500);
});
},
// MeScroll 配置
down: {
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '加载中...'
},
up: {
textLoading: '加载中...',
textNoMore: '-- 没有更多了 --'
}
});
// 可以直接使用封装好的方法
const searchByKeyword = (keyword: string) => {
search({ keyword });
};
</script>
<style scoped>
.container {
height: 100vh;
}
.list-item {
padding: 15px;
border-bottom: 1px solid #eee;
}
</style>
7. 优势特点
- 完全类型化: 提供了完整的 TypeScript 类型支持
- 高度灵活: 保留了 MeScroll.js 的所有原始功能和配置
- 简便使用: 通过 Composable 函数提供更简洁的调用方式
- 满足 Vue3 特性: 使用 Composition API 实现,符合 Vue3 生态
- 插槽完整: 提供完整的插槽支持,可自定义头部、内容及底部
- 防止内存泄漏: 在组件卸载时自动销毁实例
- 响应式配置: 监听配置变化并自动应用
这个封装设计易于使用 ,同时保持了高度的可定制性,满足不同场景的需求,是一个非常可靠的 Vue3 + TypeScript 环境下的 MeScroll.js 解决方案。