Vue3 + TypeScript 整合 MeScroll.js 组件

大家好,我是鱼樱!!!

关注公众号【鱼樱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. 优势特点

  1. 完全类型化: 提供了完整的 TypeScript 类型支持
  2. 高度灵活: 保留了 MeScroll.js 的所有原始功能和配置
  3. 简便使用: 通过 Composable 函数提供更简洁的调用方式
  4. 满足 Vue3 特性: 使用 Composition API 实现,符合 Vue3 生态
  5. 插槽完整: 提供完整的插槽支持,可自定义头部、内容及底部
  6. 防止内存泄漏: 在组件卸载时自动销毁实例
  7. 响应式配置: 监听配置变化并自动应用

这个封装设计易于使用 ,同时保持了高度的可定制性,满足不同场景的需求,是一个非常可靠的 Vue3 + TypeScript 环境下的 MeScroll.js 解决方案。

相关推荐
思考的Joey6 分钟前
Docker入门:手把手教你前端容器化部署全流程
前端·docker·devops
gqkmiss15 分钟前
Chrome 浏览器 134 版本新特性
前端·chrome·浏览器·chrome 浏览器
Mswanga19 分钟前
探秘 CSS 盒子模型:构建网页布局的基石
前端·css
I will.87422 分钟前
如何使用 CSS 实现黑色遮罩效果
前端·javascript·css
巧克力力克巧!37 分钟前
uni-app+vue3学习随笔
vue.js·学习·uni-app
守城小轩39 分钟前
Chrome 扩展开发 API实战:Bookmarks(二)
前端·javascript·chrome
gqkmiss43 分钟前
Chrome 浏览器 133 版本新特性
前端·chrome·浏览器·chrome 浏览器
A阳俊yi1 小时前
SpringMVC中有关请求参数的问题(映射路径,传递不同的参数)
java·前端·javascript
鱼樱前端1 小时前
Vue3 + TypeScript + Better-Scroll 极简上拉下拉组件
前端·javascript·vue.js